- Secrete Master Plan
签到题,枚举4种情况判断相同即可。
#include<cstdio>
#include<cstring>
using namespace std;
int a[4][2];
void turn()
{
int te = a[0][0];
a[0][0] = a[1][0], a[1][0] = a[1][1], a[1][1] = a[0][1], a[0][1] = te;
}
int main()
{
int t, n, i, j, ca = 1;
scanf("%d", &t);
while (t--)
{
for (i = 0; i < 4; i++)
for (j = 0; j < 2; j++)
scanf("%d", a[i] + j);
int flag = 0;
for (i = 0; i < 4; i++)
{
turn();
if (a[0][0] == a[2][0] && a[0][1] == a[2][1] && a[1][0] == a[3][0] && a[1][1] == a[3][1])
flag = 1;
}
printf("Case #%d: %s\n", ca++, flag ? "POSSIBLE" : "IMPOSSIBLE");
}
return 0;
}
L. Huatuo's Medicine
输入输出签到题,输出2*n-1。
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int t, n, ca = 1;
scanf("%d", &t);
while (t--&&scanf("%d", &n))
printf("Case #%d: %d\n", ca++, 2 * n - 1);
return 0;
}
H. Sudoku
水题,写过搜索专题解9*9数独的再写这个完全没问题,怎么写都行。
#include<cstdio>
#include<cstring>
using namespace std;
char s[7][7];
int ok[5][5][5];
int flag;
void add(int x, int y, int z)
{
int i, j;
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
if (x == i || j == y || (i / 2 == x / 2) && (j / 2 == y / 2))
ok[i][j][z]++;
}
void del(int x, int y, int z)
{
int i, j;
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
if (x == i || j == y || (i / 2 == x / 2) && (j / 2 == y / 2))
ok[i][j][z]--;
}
void dfs(int x, int y)
{
int i, j, k;
if (x == 4)
{
flag = 1;
return;
}
for (i = x; i < 4; i++)
for (j = (i == x) ? y : 0; j < 4; j++)
if (s[i][j] == '*')
{
for (k = 1; k <= 4; k++)
if (ok[i][j][k] == 0)
{
add(i, j, k);
s[i][j] = k + '0';
if (j == 3)
dfs(i + 1, 0);
else dfs(i, j + 1);
if (flag == 1)
return;
s[i][j] = '*';
del(i, j, k);
}
return;
}
if (i == 4)
flag = 1;
}
int main()
{
int t, n, i, j, ca = 1;
scanf("%d", &t);
while (t--)
{
memset(ok, 0, sizeof(ok));
flag = 0;
for (i = 0; i < 4; i++)
scanf("%s", s[i]);
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
if (s[i][j] != '*')
add(i, j, s[i][j] - '0');
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
if (s[i][j] == '*')
{
dfs(i, j);
break;
}
printf("Case #%d:\n", ca++);
for (i = 0; i < 4; i++)
puts(s[i]);
}
return 0;
}
G. Ancient Go
简单题,问x走1步可不可能把o吃掉至少一颗。直接的想法是枚举每个空位,看x走这里会不会堵死o,复杂度O(n^4),没问题。
一个优化是直接dfs连通的o,看是否存在只连着一个空格的连通块。复杂度几乎是O(n^2)的。
#include<cstdio>
#include<cstring>
char ss[13][13];
int vis[13][13], v2[13][13], to[][2] = { 1,0,-1,0,0,1,0,-1 };
int dfs(int x, int y)
{
vis[x][y] = 1;
int i, xx, yy, sum = 0;
for (i = 0; i < 4; i++)
{
xx = x + to[i][0], yy = y + to[i][1];
if (ss[xx][yy] == '.'&&v2[xx][yy] == 0)
sum++, v2[xx][yy] = 1;
if (ss[xx][yy] == 'o'&&vis[xx][yy] == 0)
sum += dfs(xx, yy);
}
return sum;
}
int main()
{
int i, j, t, ca = 1;
scanf("%d", &t);
while (t--)
{
memset(vis, 0, sizeof(vis));
int flag = 0;
for (i = 1; i <= 9; i++)
scanf("%s", ss[i] + 1);
for (i = 1; i <= 9; i++)
for (j = 1; j <= 9; j++)
if (ss[i][j] == 'o'&&vis[i][j] == 0)
{
memset(v2, 0, sizeof(v2));
if (dfs(i, j) < 2)
flag = 1;
}
printf("Case #%d: %s\n", ca++, flag ? "Can kill in one move!!!" : "Can not kill in one move!!!");
}
return 0;
}
4个简单题,接下来是铜牌题。(这场的铜牌线还是很低..比icpc简单)
C. The Battle of Chibi
一长度为n的数组,其中选m个数,求这m个数是严格单增序列的种数。
公式不难想,设选y个数且结尾是第x个数且为严格单增序列的方案种数是dp[x][y]。
则dp[x][1]=1,dp[x][y]=∑dp[k][y-1],其中k<x且a[k]<a[x]。
可以这么写:
for (i = 1; i <= n; i++)
dp[i][1] = 1;
for (j = 2; j <= m; j++)
for (i = j; i <= n; i++)
for (int k = 1; k < i; k++)
if (a[k] < a[i])
dp[i][j] = (dp[i][j] + dp[k][j - 1]);
然而O(n^3)一定会超时,而dp[n][m]下共有n*m个数这2个循环不能删,考虑优化求和的操作。
K<x且a[k]<a[x],想到数组求和,用树状数组更新求和(复杂度O(log(n)),二维线段树不好写且时间常数很大,不推荐)。注意因为用到了树状数组,改变了dp[x][y]的含义。代码中sum(x,y)是以小于等于第x小的元素为结尾长度为y的单增序列的数目,dp[x][y]成了辅助数组,其具体含义参见树状数组专题。
但a[i]的范围是10^9,还需要hash一下。
这题用到动态规划+树状数组+hash,复杂度O(n*m*log(n))。(还是有点难度的..)
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;
#define mm 1000000007
int n, m, a[1005], b[1005], dp[1005][1005];
void add(int i,int j, int val)//单点增加val
{
for (; i <=n; i += i & -i)
dp[i][j] = (dp[i][j] + val) % mm;
}
int sum(int i,int j)//1~i的元素和
{
int s = 0;
for (; i > 0; i -= i & -i)
s = (s + dp[i][j]) % mm;
return s;
}
int main()
{
int t, i, j, z, ca = 1;
scanf("%d", &t);
while (t--&&scanf("%d%d", &n, &m))
{
map<int, int>mp;
memset(dp, 0, sizeof(dp));
for (i = 1; i <= n; i++)
scanf("%d", a + i), b[i] = a[i];
sort(b + 1, b + n + 1);
z = unique(b + 1, b + n + 1) - (b + 1);
for (i = 1; i <= z; i++)
mp[b[i]] = i;
for (i = 1; i <= n; i++)
a[i] = mp[a[i]];
//此时a[i]=k表示a的第i个数为a数组中第k小的数 hash思想
for (i = 1; i <= n; i++)
for (j = 1; j <= min(i, m); j++)
if (j == 1)
add(a[i], 1, 1);
else
add(a[i], j, sum(a[i] - 1, j - 1));
printf("Case #%d: %d\n", ca++, sum(n, m));
}
return 0;
}
D. Pick The Sticks
题意理解是一大难点系列..
就是说给你一个一维的长条容器,再给一些长条,每个长条有固定的长度和价值。把这些长条放在容器上,只要每个长条的重心不超过容器且长条不重叠即可。问可获得的最大价值。
根据贪心思想,显然如果让一条不完全落在容器里,最优解一定是让它的重心落在容器边界。相当于可以取2件商品半价的01背包(注意只取1件的时候无视价格)。
细节是为了不让半价后的价格为小数,事先把所有长度乘2。
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[2005], v[2005], n, m;
long long dp[4005][4], ans;
int main()
{
int t, i, j, k, ca = 1;
scanf("%d", &t);
while (t--&&scanf("%d%d", &n, &m))
{
ans = 0, m *= 2;
memset(dp, 0, sizeof(dp));
for (i = 0; i < n; i++)
{
scanf("%d%d", a + i, v + i);
a[i] *= 2;
ans = max(ans, 1LL * v[i]);
}
for (i = 0; i < n; i++)
for (j = m; j >= a[i] / 2; j--)
for (k = 0; k < 3; k++)
{
if (j >= a[i])
dp[j][k] = max(dp[j][k], dp[j - a[i]][k] + v[i]);
if (k != 0)
dp[j][k] = max(dp[j][k], dp[j - a[i] / 2][k - 1] + v[i]);
}
printf("Case #%d: %lld\n", ca++, max(ans, dp[m][2]));
}
return 0;
}
6题铜首银尾,接下来是银牌题目..顺便一提,一般acm现场赛第一名在60分钟左右的成绩就是最终铜牌前列的成绩。但这场第一名很强,加上铜牌难度不如icpc,58分钟时第一名就7题(最终榜的银牌中上)了...
F. The Battle of Guandu
题意比较麻烦:给n个城镇m个战场,每个城镇有如下3个属性:向x战场派正方1人同时向y战场派反方 1人的费用是c。每个战场有一个重要程度:0则无所谓双方人数,1则该战场的正方人数大于等于反方人数,2则该战场的正方人数大于反方人数。
问每个战场都达成条件,至少需要多少费用(或不能达成条件)。
还是根据贪心原则,因为费用一次支付一人的,所以最少费用时,2级战场必然是正方只多一人(否则把这个人去掉一定会更省钱),而大于等于必然是等于(多派一个人必然会多花钱)。由于双方人数守恒,2级战场正方多人则0级战场正方一定少人。
从图论角度考虑,每个城镇相当于一个边,战场相当于点,有:
对于每个城镇,相当于y到x的的长度为c的边。起点为所有0级战场(相当于从0级战场借人到2级战场),跑一遍多起点最短路,最后如果所有2级战场都可达,则所有2级战场到源的最短路和即答案,否则不可达成。
多起点最短路用迪杰斯特拉算法,堆优化后复杂度为O(nlogn)。(用优先队列实现就行,最短路常用模板)。
注意答案可能会超int。
很棒的题目..比较看思维,想到之后很快能写出来代码。
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
#define mm 100005
#define LL long long
int x[mm], y[mm], w[mm], c[mm], h[mm], vi[mm], n, m, k;
LL d[mm];
struct { int v, w, ne; }a[mm];
void add(int u, int v, int w)//初始k=1
{
a[k].v = v, a[k].w = w, a[k].ne = h[u], h[u] = k++;
}
struct node
{
int v;
LL c;
node(int _v = 0, LL _c = 0) :v(_v), c(_c) {}
bool operator <(const node &r)const
{
return c > r.c;
}
};
LL dij()
{
int i, v, u;
LL ww, ans = 0;
priority_queue<node>q;
for (i = 1; i <= m; i++)
vi[i] = 0, d[i] = 233333333333;
for (i = 1; i <= n; i++)
if (w[i] == 0)
d[i] = 0, q.push(node(i, 0));
node tmp;
while (!q.empty())
{
tmp = q.top(), q.pop(), u = tmp.v;
if (vi[u])
continue;
vi[u] = 1;
for (i = h[u]; i; i = a[i].ne)
{
v = a[i].v, ww = a[i].w;
if (!vi[v] && d[v] > d[u] + ww)
d[v] = d[u] + ww, q.push(node(v, d[v]));
}
}
for (i = 1; i <= m; i++)
if (w[i] == 2)
if (d[i] == 233333333333)
return -1;
else ans += d[i];
return ans;
}
int main()
{
int t, i, ca = 1;
scanf("%d", &t);
while (t--&&scanf("%d%d", &n, &m))
{
k = 1;
memset(h, 0, sizeof(h));
memset(a, 0, sizeof(a));
for (i = 1; i <= n; i++)
scanf("%d", x + i);
for (i = 1; i <= n; i++)
scanf("%d", y + i);
for (i = 1; i <= n; i++)
scanf("%d", c + i);
for (i = 1; i <= m; i++)
scanf("%d", w + i);
for (i = 1; i <= n; i++)
add(y[i], x[i], c[i]);
printf("Case #%d: %lld\n", ca++, dij());
}
return 0;
}
K.Game Rooms
题意很简单,相当于n个楼层,每个楼层男女各x,y人,每层只能建一个厕所,为了让所有人上厕所的距离尽量少。求最优时所有人上一次厕所的距离和。
还是挺不好想的...看了题解。
dp[i][k]相当于在i层建了第k种厕所(k=0或1)则有:
dp[i][k] = min(dp[i][k], dp[j][1 - k] + fx(j + 1, i, k));
意思是:枚举所有前一个另一种厕所的位置j(即[j+1,i]都是第k种厕所),并把 [j+1,i]这个区间的k种人分散到2边。
fx(i,j,k)表示[i,j]这个区间的所有k种人上一次厕所的距离和。(预处理前缀和就可以O(1)算出)
状态涉及是连续一段区间的dp挺常见的,公式也非常简单。
但感觉这题的思想还是很玄妙..可能是动态规划做的少。
细节是最终答案可能很大,用longlong且初始化最大值时尽量大..
#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
#define mm 4005
#define LL long long
int n;
LL dp[mm][2], s1[mm][2], s2[mm][2], ti[mm][2];
LL left(int i, int j, int k)
{//[i,j]的k种人到i-1的代价和
LL ans1 = s1[j][k] - s1[i - 1][k], ans2 = s2[j][k] - s2[i - 1][k];
return ans2 - ans1 * (i - 1);
}
LL right(int i, int j, int k)
{//[i,j]的k种人到j+1的代价和
LL ans1 = s1[j][k] - s1[i - 1][k], ans2 = s2[j][k] - s2[i - 1][k];
return ans1 * (j + 1) - ans2;
}
LL fx(int i, int j, int k)
{//[i,j]的k种人到2边的代价和
if (i == 1)
return right(i, j, k);
if (j == n)
return left(i, j, k);
int mid = (i + j) / 2;
return left(i, mid, k) + right(mid + 1, j, k);
}
int main()
{
int t, i, j, k, ca = 1;
scanf("%d", &t);
while (t--&&scanf("%d", &n))
{
for (i = 1; i <= n; i++)
for (j = 0; j < 2; j++)
{
scanf("%lld", ti[i] + j);
s1[i][j] = s1[i - 1][j] + ti[i][j];
s2[i][j] = s2[i - 1][j] + i * ti[i][j];
//注意如果ti是int型,i*ti会溢出
}
for (i = 1; i < n; i++)
for (j = 0; j < 2; j++)
dp[i][j] = fx(1, i, j);
dp[n][0] = dp[n][1] = 2333333333333333333;
for (i = 1; i <= n; i++)
for (k = 0; k < 2; k++)
for (j = 1; j < i; j++)
dp[i][k] = min(dp[i][k], dp[j][1 - k] + fx(j + 1, i, k));
printf("Case #%d: %lld\n", ca++, min(dp[n][0], dp[n][1]));
}
return 0;
}
再后面题就明显困难起来了,看文字题解仍然写不出代码(dfs树上的所有独立回路再高斯消元解异或方程组,dp套dp,奇怪的计算几何..)..不勉强了..(现场赛再多ac一题兴许就有金牌,这场金牌难度感觉和icpc差不多)