单挑养成计划【1】 AtCoder Grand Contest 004
撇个题目链接,http://agc004.contest.atcoder.jp/assignments
B Colorful Slimes
题意:给你一个长为n的序列要填满,已知填上第i个数花费的代价为ai,把当前序列循环右移一位花费的代价为x,求最小代价。
题解:一开始想岔了,只针对前i位填满的情况考虑递推,漏了一些填不满的状态。对于一个循环右移k次的序列,对每个数我们都可以任意选择在哪一次右移后填数。于是枚举右移次数,通通扫描一遍就能得到最优解。

1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstring> 4 #include <iostream> 5 #include <algorithm> 6 using namespace std; 7 typedef long long LL; 8 const int maxn = 2005; 9 const int mod = 1e9 + 7; 10 LL a[maxn], n, x, b[maxn], ans; 11 int main() { 12 cin>>n>>x; 13 for (int i=0; i<n; i++) cin>>a[i]; 14 ans = 1LL * maxn * 1e9; 15 memset(b, 0x3f, sizeof(b)); 16 for (int i=0; i<n; i++) { 17 LL tmp = 0; 18 for (int j=0; j<n; j++) 19 b[j] = min(b[j], a[(j-i+n)%n]); 20 for (int j=0; j<n; j++) 21 tmp += b[j]; 22 ans = min(ans, i * x + tmp); 23 } 24 cout<<ans<<endl; 25 return 0; 26 }
C AND Grid
题意:有两张n*m的图,一张上有一个红色四连通块,另一张上有一个蓝色四连通块,两张重叠在一起生成新图,规则是若某格既有红色又有蓝色便染成紫色,否则不染色。保证最边缘的一圈没有紫色格。现在给出新图,问红蓝染色的一种可行方案。
题解:傻x构造,只要保证红蓝两色可以接触到内层所有格子即可。

1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstring> 4 #include <iostream> 5 #include <algorithm> 6 using namespace std; 7 typedef long long LL; 8 const int maxn = 505; 9 const int mod = 1e9 + 7; 10 int h,w; 11 char mp[maxn][maxn]; 12 int main() { 13 cin>>h>>w; 14 for (int i=0; i<h; i++) { 15 cin>>mp[i]; 16 } 17 for (int i=0; i<h; i++) { 18 for (int j=0; j<w; j++) { 19 if (j == 0) putchar('.'); 20 else if (!(i & 1)) putchar('#'); 21 else if ((i & 1) && j == w-1) putchar('#'); 22 else if (mp[i][j] == '#') putchar('#'); 23 else putchar('.'); 24 } 25 puts(""); 26 } 27 puts(""); 28 for (int i=0; i<h; i++) { 29 for (int j=0; j<w; j++) { 30 if (j == w-1) putchar('.'); 31 else if (i & 1) putchar('#'); 32 else if (!(i & 1) && j == 0) putchar('#'); 33 else if (mp[i][j] == '#') putchar('#'); 34 else putchar('.'); 35 } 36 puts(""); 37 } 38 return 0; 39 }
D Teleporter
题意:给你一张n个点n条边的有向图(每个点指向另一个点),要求从任一点出发走恰好k步能到达顶点1,问最少需要修改多少条边。
题解:首先容易想到顶点1应该指向自己。因为易得从1出发再回到1的路径长度总是k的约数,假设有1->r,那么从r顶点出发走k步到达的是顶点r,所以r=1。这样我们就得到了一个树。对这棵树简单贪心,从底向上dfs,子树高度够k-1就把当前顶点指向1即可。注意一定要从底向上,反向的话很容易举出反例。

1 #include <cstdio> 2 #include <vector> 3 #include <cstdlib> 4 #include <cstring> 5 #include <iostream> 6 #include <algorithm> 7 using namespace std; 8 typedef long long LL; 9 const int maxn = 100005; 10 const int mod = 1e9 + 7; 11 int n,k,a[maxn],ans,h[maxn]; 12 vector<int> son[maxn]; 13 void dfs(int x) { 14 h[x] = 0; 15 for (int i=0; i<son[x].size(); i++) { 16 dfs(son[x][i]); 17 h[x] = max(h[x], h[son[x][i]] + 1); 18 } 19 if (a[x] != 1 && h[x] == k-1) { 20 ans++; 21 h[x] = -1; 22 } 23 } 24 int main() { 25 cin>>n>>k; 26 cin>>a[1]; 27 for (int i=2; i<=n; i++) { 28 cin>>a[i]; 29 son[a[i]].push_back(i); 30 } 31 if (a[1] != 1) { 32 ans++; 33 a[1] = 1; 34 } 35 dfs(1); 36 cout<<ans<<endl; 37 return 0; 38 }
E Salvage Robots
题意:在一张n*m的图上,E代表出口,o代表机器人。现在对于每一步选择一个方向,所有机器人向那个方向移动一格,移出边界就死掉,移到出口就得救。问最多能救出多少个小机器人。
题解:solo到这里的时候时间就到了。正着想很麻烦,就先不要想移动机器人。问题相当于我们拿着一个矩形框,矩形框上面有个扫描点,机器人向左走一格等价于拿着矩形框向右移一格。那么分别设l.u.r.d是扫描左上右下边界距扫描点的距离,把扫描点和出口重合,看一眼就很容易搞出来边界上已经死掉的格子和目前没有扫到也没有死掉的格子,转移的决策就是枚举四个方向,然后算一算能新吃到多少机器人。
数组开int的时候MLE了,强行用了一波short,之后学习了一下其他AC代码,发现省略第一维或者把形如(x,y)的坐标哈希成一个数都是可以的。

1 #include <map> 2 #include <cmath> 3 #include <cstdio> 4 #include <vector> 5 #include <cstdlib> 6 #include <cstring> 7 #include <iostream> 8 #include <algorithm> 9 using namespace std; 10 typedef long long LL; 11 const int maxn = 105; 12 const int mod = 1e9 + 7; 13 short dp[maxn][maxn][maxn]; 14 short row[maxn][maxn], col[maxn][maxn]; 15 char mp[maxn][maxn]; 16 short max(short a, short b) { 17 return a>b? a : b; 18 } 19 int main() 20 { 21 int n, m, ex, ey; 22 scanf("%d%d", &n, &m); 23 for (int i=1; i<=n; i++) scanf("%s", mp[i]+1); 24 for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) { 25 if (mp[i][j] == 'E'){ 26 ex = i, ey = j; 27 break; 28 } 29 } 30 for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) { 31 row[i][j] = row[i][j-1] + (mp[i][j] == 'o'); 32 col[i][j] = col[i-1][j] + (mp[i][j] == 'o'); 33 } 34 int L = ey - 1, R = m - ey, U = ex - 1, D = n - ex; 35 memset(dp, -1, sizeof dp); 36 dp[0][0][0] = 0; 37 short ans = 0; 38 for (int l=0; l<=L; l++){ 39 for (int u=0; u<=U; u++){ 40 for (int r=0; r<=R; r++){ 41 for (int d=0; d<=D; d++) if(dp[u][r][d] != -1){ 42 int up = max(ex - u, d + 1), down = min(ex + d, n - u); 43 int left = max(ey - l, r + 1), right = min(ey + r, m - l); 44 if (up > down || left > right) continue; 45 ans = max(ans, dp[u][r][d]); 46 short add; 47 //printf("\n=========%d %d %d %d -> %d %d %d %d=========\n",l,u,r,d,left,up,right,down); 48 if(u+d < U){ 49 add = row[ex-u-1][right] - row[ex-u-1][left-1]; 50 //printf("u: %d ",add); 51 dp[u+1][r][d] = max(dp[u+1][r][d], dp[u][r][d] + add); 52 } 53 if(l+r < R){ 54 add = col[down][ey+r+1] - col[up-1][ey+r+1]; 55 //printf("r: %d ",add); 56 dp[u][r+1][d] = max(dp[u][r+1][d], dp[u][r][d] + add); 57 } 58 if(u+d < D){ 59 add = row[ex+d+1][right] - row[ex+d+1][left-1]; 60 //printf("d: %d ",add); 61 dp[u][r][d+1] = max(dp[u][r][d + 1], dp[u][r][d] + add); 62 } 63 if (l+r < L){ 64 add = col[down][ey-l-1] - col[up-1][ey-l-1]; 65 //printf("l: %d ",add); 66 dp[u][r][d] = max(dp[u][r][d], dp[u][r][d] + add); 67 } 68 } 69 } 70 } 71 } 72 printf("%d\n", ans); 73 return 0; 74 }
F Namori
题意:n个点m(n-1 <= m <= n)条边的无向联通图,一开始都是白色的,每次操作可以选择相邻的2个相同颜色的点,然后改变这2个点的颜色(白->黑,黑->白).求最后所有点都是黑色,所需的最少操作次数.
题解:非常巧妙啊。先考虑m=n-1的情况,因为树是一个二分图,所以先给树二染色,将其中一种颜色的节点取反,问题就从原来两个相邻且相同颜色的节点可以反色,变成了两个相邻的不同颜色节点可以反色。即把一个节点的“黑色”移动给了另外一个节点的“白色”。m=n的时候本质差不多,只需要断开一条边然后针对奇偶环判一判就好了(?)
我貌似需要再想一想_(:з」∠)_
手速不快不可怕,可怕的是手速慢还在简单题上WA……做题的时候思路还是不够严谨,等发现错误再抢救再摊上写不明白代码就耽误了很多时间。果然四省赛的惨案不是没有道理的_(:з」∠)_要加油了_(:з」∠)_