ZOJ Monthly, July 2010 部分题目解题报告
http://acm.zju.edu.cn/onlinejudge/showProblems.do?contestId=1&pageNumber=24
zoj 3352 - 3361
zoj 3352 Boring Board Game
题意:50个点有向无环图,每个点有0-2的数字,有黑白两旗,两个人玩游戏,轮流沿着边移动一个旗子,白旗使得赌注增加抵达点的数字,黑旗反之。无法移动旗子的人输,需要给赢的人赌注,赌注可能为负,问先手的最大收益,以及能获得此收益的第一步方案数。
分析:点数很少,而且数字变化范围小,又是有向无环图,启发我们搜索&&DP。dp[i][j][k]表示白旗在i,黑旗在j,赌注为k,现在先手的人的最大收益,记忆化搜索。因为两个人玩游戏,终结态时值为-k,状态转移取个负号,即先手转换。
1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 using namespace std; 5 6 int n, m, x, y, a, b; 7 vector<int> E[55]; 8 int dp[55][55][210], ct[55][55][210]; 9 int s[55]; 10 int dfs(int w, int b, int k) 11 { 12 if (ct[w][b][100+k] != -1) return dp[w][b][100+k]; 13 dp[w][b][100+k] = -21474836; 14 if (E[w].empty() && E[b].empty()){ 15 dp[w][b][100+k] = -k; 16 return -k; 17 } 18 int &ret = dp[w][b][100+k]; int &cnt = ct[w][b][100+k]; 19 vector<int>::iterator i; 20 for (i = E[w].begin(); i != E[w].end(); i ++){ 21 int tmp = -dfs(*i, b, k + s[*i]); 22 if (tmp > ret){ 23 ret = tmp; 24 cnt = 1; 25 } 26 else if (tmp == ret) cnt++; 27 } 28 for (i = E[b].begin(); i != E[b].end(); i ++){ 29 int tmp = -dfs(w, *i, k - s[*i]); 30 if (tmp > ret){ 31 ret = tmp; 32 cnt = 1; 33 } 34 else if (tmp == ret) cnt++; 35 } 36 return ret; 37 } 38 int main() 39 { 40 while(scanf("%d %d %d %d", &n, &m, &x, &y) != EOF) 41 { 42 for (int i = 0; i < n; i++){ 43 scanf("%d", &s[i]); 44 E[i].clear(); 45 } 46 for (int i = 0; i < m; i++){ 47 scanf("%d %d", &a, &b); 48 E[a].push_back(b); 49 } 50 memset(ct, -1, sizeof(ct)); 51 dfs(x, y, 1); 52 printf("%d %d\n", dp[x][y][101], ct[x][y][101]); 53 } 54 return 0; 55 }
zoj 3355 3356 两道很恶心的题目,精度问题。。代码都是抄的标程= =。。
推的公式是ax<x+y+z,by<x+y+z,cz<x+y+z,然后合并得到1/a+1/b+1/c<1,然后bc+ac+ab<abc,左边是投的钱,右边是收益(胜平负都是abc)。
3356的优化是让大部分的钱按比率投,之后一个一个枚举。
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 5 int T; 6 long long a, aa, b, bb, c, cc; 7 int main() 8 { 9 scanf("%d", &T); 10 while(T--) 11 { 12 scanf("%lld.%lld %lld.%lld %lld.%lld", &a, &aa, &b, &bb, &c, &cc); 13 a = a * 100 + aa; 14 b = b * 100 + bb; 15 c = c * 100 + cc; 16 if (100ll * (a*b + b*c + a*c) < a*b*c) 17 puts("Aha"); 18 else puts("No way"); 19 } 20 return 0; 21 }
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int T; 7 long long a, b, s, c[3], d[3], e[3], ans; 8 int main() 9 { 10 scanf("%d", &T); 11 while(T--) 12 { 13 scanf("%lld", &s); 14 for (int i = 0; i < 3; i++){ 15 scanf("%lld.%lld", &a, &b); 16 c[i] = a * 100 + b; 17 } 18 19 ans = 0; 20 d[0] = d[1] = d[2] = 0; 21 e[0] = e[1] = e[2] = 0; 22 for (int i = 1; i <= s; i++){ 23 int k = min_element(e, e+3) - e; 24 ++d[k]; 25 e[k] = d[k] * c[k] / 100; 26 ans = max(ans, *min_element(e, e+3) - i); 27 } 28 printf("%lld\n", ans + s); 29 } 30 return 0; 31 }
zoj 3358 Green Dam Girl
题意:题面实在猥琐。。大意就是n个点,在每个点停留获得收益,连续停留1,2,3及以上单位时间的收益不同,点与点有有向边,边有转移费用,问d时间的最大收益。
分析:dp方程还是比较好想的。因为在i和i+1时间可以连续走好几条边,点数只有100,先用floyd求出点到点最短路。dp[i][j][k]表示在第i的时间在第j个点,停留k时间的最大收益,然后转移就可以了。注意数据有在某点只停留1单位时间的收益很高的情况,导致出现走一个环回到该点,刷新停留时间的策略。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 #include<set> 8 #include<map> 9 #include<queue> 10 #include<ctime> 11 #include<string> 12 using namespace std; 13 14 int n, d; 15 int cost[110][110]; 16 int dp[110][110][5]; 17 int night[110][5]; 18 int main() 19 { 20 while(scanf("%d%d", &n, &d) != EOF) 21 { 22 memset(cost, -1, sizeof(cost)); 23 int m, y, c; 24 for (int i = 0; i < n; i++){ 25 for (int j = 1; j <= 3; j++) 26 scanf("%d", &night[i][j]); 27 scanf("%d", &m); 28 for (int j = 0; j < m; j++){ 29 scanf("%d %d", &y, &c); 30 cost[i][y] = c; 31 } 32 } 33 for (int k = 0; k < n; k++) 34 for (int i = 0; i < n; i++){ 35 if (cost[i][k] == -1) continue; 36 for (int j = 0; j < n; j++){ 37 if (cost[k][j] == -1) continue; 38 if (cost[i][j] == -1 || cost[i][j] > cost[i][k] + cost[k][j]) 39 cost[i][j] = cost[i][k] + cost[k][j]; 40 } 41 } 42 //for (int i = 0; i < n; i++) 43 // for (int j = 0; j < n; j++) 44 // printf("%d %d %d\n", i, j, cost[i][j]); 45 memset(dp, -1, sizeof(dp)); 46 dp[1][0][1] = night[0][1]; 47 dp[2][0][2] = night[0][1] + night[0][2]; 48 for (int i = 1; i < n; i++) 49 if (cost[0][i] != -1 && dp[1][0][1] >= cost[0][i]){ 50 dp[2][i][1] = dp[1][0][1] - cost[0][i] + night[i][1]; 51 } 52 d--; 53 for (int i = 2; i < d; i++) 54 for (int j = 0; j < n; j++){ 55 for (int k = 1; k <= 3; k++){ 56 //printf("%d %d %d %d\n", i, j, k, dp[i][j][k]); 57 if (dp[i][j][k] == -1) continue; 58 int tmp = k+1; 59 if (tmp > 3) tmp = 3; 60 dp[i+1][j][tmp] = max(dp[i+1][j][tmp], dp[i][j][k] + night[j][tmp]); 61 for (int to = 0; to < n; to++){ 62 if (cost[j][to] != -1 && dp[i][j][k] >= cost[j][to]){ 63 dp[i+1][to][1] = max(dp[i+1][to][1], dp[i][j][k] - cost[j][to] + night[to][1]); 64 } 65 } 66 } 67 } 68 int ans = 0; 69 for (int i = 0; i < n; i++) 70 for (int k = 1; k <= 3; k++) 71 ans = max(ans, dp[d][i][k]); 72 printf("%d\n", ans); 73 } 74 return 0; 75 }
zoj 3359 Penalties Kick 模拟题,没太多好说的,踢完一个球判断一次。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int T; 7 int home[50], away[50]; 8 char tmp[10]; 9 char win[2][10] = {"home", "away"}; 10 void output(int cas, int winner, int sh, int sa) 11 { 12 printf("Match %d:\n", cas); 13 printf("Winner: %s\n", win[winner]); 14 printf("Score: %d:%d\n", sh, sa); 15 } 16 int main() 17 { 18 scanf("%d", &T); 19 for (int cas = 1; cas <= T; cas++) 20 { 21 int sh, sa, winner; 22 bool flag = false; 23 for (int i = 1; i <= 11; i++){ 24 for (int j = 0; j < 3; j++){ 25 scanf("%s", tmp); 26 if (tmp[0] == 'y'){ 27 home[i+j*11] = 1; 28 } 29 else home[i+j*11] = 0; 30 } 31 } 32 for (int i = 1; i <= 11; i++){ 33 for (int j = 0; j < 3; j++){ 34 scanf("%s", tmp); 35 if (tmp[0] == 'y'){ 36 away[i+j*11] = 1; 37 } 38 else away[i+j*11] = 0; 39 } 40 } 41 sh = sa = 0; 42 for (int i = 1; i <= 5; i++){ 43 sh += home[i]; 44 if (sh - sa > 5 - i + 1){ 45 winner = 0; 46 flag = true; 47 break; 48 } 49 if (sa - sh > 5 - i){ 50 winner = 1; 51 flag = true; 52 break; 53 } 54 sa += away[i]; 55 if (sh - sa > 5 - i){ 56 winner = 0; 57 flag = true; 58 break; 59 } 60 if (sa - sh > 5 - i){ 61 winner = 1; 62 flag = true; 63 break; 64 } 65 } 66 if (flag){ 67 output(cas, winner, sh, sa); 68 continue; 69 } 70 for (int i = 6; i <= 33; i++){ 71 sh += home[i]; 72 sa += away[i]; 73 if (sh > sa){ 74 winner = 0; 75 break; 76 } 77 if (sa > sh){ 78 winner = 1; 79 break; 80 } 81 } 82 output(cas, winner, sh, sa); 83 } 84 return 0; 85 }
zoj 3360 Stranger Calendar II
题意:题意至今不明。。大概就是要求n1, n2 …使得1/n1-1/n1n2+1/n1n2n3-…等于给定的A/B。
分析:
只考虑A/B < 1/2的情况,另一种情况解是相同的。对于A/B,假设n1 = C,则得到A/B-1/C = AC-B/BC,后面的分数分母都有C,所以乘上C,然后因为相邻项的运算相反,我们再取相反数,得到B-AC/B,这个就是新的A/B,n2就用同n1一样的方法求解,也就是一个递归的过程。题目要求所用分数不超过5个,且希望解最少,所以用迭代加深搜索,出口是分子为0或是达到限制层数。每层枚举,C要满足 0 <= B-AC/B <= 1/2。
还有一种不用这种递归思想的是一步步枚举新的分数的因子,随着所用分数增多,肯定是不断逼近,而且是一上一下震荡式地逼近A/B的,新加上的分数要让curA/curB > A/B,新减去的分数要让curA/curB < A/B,以此剪枝,也可以通过。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int T; 7 int a, b; 8 int ans[100]; 9 int dfs(int dep, int lim, int cura, int curb) 10 { 11 if (cura == 0) return dep; 12 if (dep == lim) return 0; 13 int l = max(curb/(2*cura) - 1, 2), r = curb/cura; 14 for (int i = l; i <= r; i++){ 15 ans[dep] = i; 16 int newa = curb - cura * i, newb = curb; 17 int tmp = __gcd(newa, newb); 18 newa /= tmp; newb /= tmp; 19 tmp = dfs(dep+1, lim, newa, newb); 20 if (tmp) return tmp; 21 } 22 return 0; 23 } 24 int main() 25 { 26 scanf("%d", &T); 27 while(T--) 28 { 29 scanf("%d %d", &a, &b); 30 int gcd = __gcd(a, b); 31 a /= gcd; b /= gcd; 32 if (a > b - a) a = b - a; 33 int lim, size; 34 for (lim = 1; lim < 6; lim ++){ 35 size = dfs(0, lim, a, b); 36 if (size) break; 37 } 38 if (lim == 6){ 39 puts("Too complex"); 40 continue; 41 } 42 else{ 43 for (int i = 0; i < size; i++) 44 printf("%d%c", ans[i], i+1==size? '\n': ' '); 45 } 46 } 47 return 0; 48 }