Codeforces-417D总结&题解
来源:互联网 发布:淘宝左右香水是正品吗 编辑:程序博客网 时间:2024/05/18 18:00
在分析题目前,一定要完全读懂题目意思,否则一些都是白搭。这道理谁都懂,但是能每次都做到的人不多。比如本人,这次又粗心地在这里疯狂地踩坑了。
(1)本题有这么一句话:But Gena's friends won't agree to help Gena for nothing: the i-th friend asks Gena xi rubles for his help in solving all the problems he can. 当时就没看清,然后,根据自己的一知半解去套样例,结果刚好符合我的理解(这也从侧面说明了一点,样例通常都是误导粗心大意的、naive的人):每个朋友,AC一题,交一次钱,如果当前已经买了的显示器足够这个朋友需要的,不用再额外支付显示器的钱,否则,还差多少个显示器,就要再付多少个显示器的钱。于是乎,就有了下面这份代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<set>#include<vector>using namespace std;typedef __int64 I64;I64 n, m, b;typedef struct { I64 x, k, m; set<I64> pl;} Friend;set<I64> st;Friend fds[105];I64 dp[22][105];void init() { st.clear(); for(I64 i = 0;i < 105;i++){ fds[i].x = 0, fds[i].k = 0, fds[i].m = 0; fds[i].pl.clear(); }}void solve() { memset(dp, 0x5f, sizeof(dp)); I64 t; for(I64 i = 1; i <= n; i++)dp[0][i] = 0; for(I64 i = 1; i <= n; i++){ if(!fds[i].pl.count(1))continue; dp[1][i] = fds[i].x + fds[i].k * b; //printf("dp[1][%I64d] = %I64d\n", i, dp[1][i]); } for(I64 p = 2; p <= m; p++) { for(I64 j = 1; j <= n; j++) { if(!fds[j].pl.count(p))continue; for(I64 r = 1; r <= n; r++) { //if(!fds[r].pl.count(p))continue; if(dp[p - 1][r] == 0x5f5f5f5f5f5f5f5f)continue; t = (fds[j].k > fds[r].k ? (fds[j].k - fds[r].k) * b : 0); dp[p][j] = min(dp[p][j], dp[p - 1][r] + fds[j].x + t); //printf("dp[%I64d][%I64d] = %I64d\n", p, j, dp[p][j]); } } } I64 res = dp[m][1]; for(I64 i = 1;i <= n;i++)res = min(res, dp[m][i]); printf("%I64d\n", res);}int main() {#ifndef ONLINE_JUDGE freopen("Einput.txt", "r", stdin);#endif // ONLINE_JUDGE I64 p; while(~scanf("%I64d%I64d%I64d", &n, &m, &b)) { init(); for(I64 i = 1; i <= n; i++) { scanf("%I64d%I64d%I64d", &fds[i].x, &fds[i].k, &fds[i].m); for(I64 j = 0; j < fds[i].m;j++) { scanf("%I64d", &p); fds[i].pl.insert(p); st.insert(p); } } if(st.size() < m)cout << -1 << endl; else solve(); } return 0;}
简要解释下:dp[p][j]表示解决前p题,需要数量最多的显示器的朋友是第i个,所需花费的最小代价。
于是,连续修修补补、添添改改交了N发,WA了N发。直到测试结束也没有A出来。(2)然而,本题的真正原意是:对于每个朋友,如果他需要的显示器足够,那么,只要支付一次他需要的AC题目的费用,他就会把他会做的题目全部做掉。当然,AC了的题目再做一遍也是可以的。所以,本题应该要用状态压缩DP来做。
dp[s]表示当做了题的状态为s时,所需花费的最小代价。然后,不难想到递推式:dp[s | 第i个朋友会做的题号使用二进制法编码成的整数] = min(dp[s | 第i个朋友会做的题号使用二进制法编码成的整数], dp[s] + xi)。咦?不是还要付显示器的钱吗?怎么搞?dp数组弄成结构体,成员为v和moni。dp[s].moni记录做了题的状态为s时,最多买了多少个显示器。
(3)还有一点就是,对状态的循环要放在对朋友的循环的里面,这个也是我没想明白的地方。暂时先搁着吧。再过段时间水平更高了,自然就明白了。
然后,就有了如下代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<set>using namespace std;typedef __int64 I64;const I64 INF = ((I64)1) << 62;I64 n, m, b;typedef struct { I64 x, k, m; set<I64> pl; I64 ac;} Friend;set<I64> st;Friend fds[105];typedef struct { I64 v, moni;} DP;DP dp[1050000];void init() { st.clear(); for(I64 i = 0; i < 105; i++) { fds[i].x = 0, fds[i].k = 0, fds[i].m = 0; fds[i].pl.clear(); }}void solve() { int ak = (1 << m) - 1; I64 t; for(int i = 0; i < (1 << 20); ++i) { dp[i].v = INF; dp[i].moni = 0; } dp[0].v = 0, dp[0].moni = 0; for(int i = 1; i <= n; i++) { for(int s = 0; s <= ak; ++s) { if((s & fds[i].ac) == fds[i].ac)continue; t = (fds[i].k > dp[s].moni ? fds[i].k - dp[s].moni : 0); if(dp[s | fds[i].ac].v > dp[s].v + fds[i].x + t * b) { dp[s | fds[i].ac].v = dp[s].v + fds[i].x + t * b; dp[s | fds[i].ac].moni = dp[s].moni + t; } } } printf("%I64d\n", dp[ak].v);}int main() {#ifndef ONLINE_JUDGE freopen("Einput.txt", "r", stdin);#endif // ONLINE_JUDGE I64 p; while(~scanf("%I64d%I64d%I64d", &n, &m, &b)) { init(); for(I64 i = 1; i <= n; i++) { scanf("%I64d%I64d%I64d", &fds[i].x, &fds[i].k, &fds[i].m); for(I64 j = 0; j < fds[i].m; j++) { scanf("%I64d", &p); fds[i].pl.insert(p); st.insert(p); } } for(I64 i = 1; i <= n; ++i) { I64 t = 0; for(set<I64>::iterator it = fds[i].pl.begin(); it != fds[i].pl.end(); ++it) t |= (((I64)1) << (*it - 1)); fds[i].ac = t; } if(st.size() < m)cout << -1 << endl; else solve(); } return 0;}然后,交上去在第24个测试样例挂了,结果如下所示。
Test: #24, time: 0 ms., memory: 18488 KB, exit code: 0, checker exit code: 1, verdict: WRONG_ANSWERInput59 11 830067068735007990 107101946 61 2 4 5 6 10603600908 880756312 81 2 4 7 8 9 10 11659716960 174388278 52 5 7 9 10899506226 611183471 82 4 5 7 8 9 10 11571272365 68726728 64 5 7 8 10 11330538629 560221018 63 4 5 8 10 11686021635 919974878 61 4 5 6 8 92687375 998065071 71 3 4 5 8 9 11141387697 606296659 52 3 4 7 9287220643 466057605 62 4 6 7 8 11822768448 695179109 42 3 4 10696850261 919174199 71 4 6 7 8 9 1071824861 156690160 61 3 6 7 10 11875086545 ...Output83528377136277204Answer83528376862632587Checker Logwrong answer expected 83528376862632587, found 83528377136277204对比一下可以发现,错误的结果前7个数字还是对的上的,就是后面的对不上了。错的我不明不白。想不明白。
然后,到网上看了别人的题解后,理解了下,普遍的做法是:dp还是简单数据类型数组,只是在dp到达目标状态时,使用min函数更新我们需要的答案,最大需要的显示器数量为当前朋友需要的显示器数量。还有就是,都说要对Friend结构体按需要的显示器个数k自然排序,我就想:为什么要排序呢?每种状态不是都能遍历到吗,为何还要排下序?因为,如果不排序,按照这种写法,就会出现这么一种情况:
2 1 b1 3 112 2 11对于该测试样例,得到这么一个结果,ans = 1 + 2 * b。即选第一个的费用x,然后由于题目已经刷完,达到了目标状态,对ans更新,ans = min(1 + 3 * b, 1 + 2 * b)。即用前面做题花的费用再加上当前朋友所需要的显示器*单个显示器价格,即:1 + 2 * b。显然,3 * b < 2 * b,所以,ans被更新为1 + 2 * b。然而,这种情况实际上是不合法的。所以,我们需要把这种情况干掉,就是通过排序。
(4)接下来,就是第4个坑了。排序的时候,因为是按照朋友所需要的显示器的数量从小大自然排序,于是我写了个这样的比较函数:
bool cmp(Friend f1, Friend f2) { return f1.k <= f2.k;}样例一跑,发现都没错,果断交一发,嘣,我擦,RE。点开Codeforces提供的错误样例,发现输入数据全是最上界的值,然后我开始找数据越界的地方,找了半天还是没找到,最终一点一点注释,定位到了sort函数这里。按理来说,sort函数限定了上下界指针,在里面自由活动应该不会出问题的。然后我把cmp函数里面的“=”去掉,跑一下,竟然没事了。真坑。。。。。。这个问题,我也没想明白,为什么会这样。
于是最终的AC代码就是这样了。
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<set>using namespace std;typedef __int64 I64;const I64 INF = 1LL << 62;I64 n, m, b;typedef struct { I64 x, k, m, ac;} Friend;set<I64> st;Friend fds[105];I64 dp[1050000];bool cmp(Friend f1, Friend f2) { return f1.k < f2.k;}void solve() { I64 ak = (I64)(1 << m) - 1; I64 t, ans = INF; sort(fds + 1, fds + n + 1, cmp); for(int i = 0; i < (1 << 20); i++)dp[i] = INF; dp[0] = 0; for(I64 i = 1; i <= n; i++) { for(I64 s = 0; s <= ak; ++s) { if((s & fds[i].ac) == fds[i].ac)continue; dp[s | fds[i].ac] = min(dp[s | fds[i].ac], dp[s] + fds[i].x); } if(dp[ak] != INF)ans = min(ans, dp[ak] + fds[i].k * b); } printf("%I64d\n", ans == INF ? -1 : ans);}int main() {#ifndef ONLINE_JUDGE freopen("Einput.txt", "r", stdin);#endif // ONLINE_JUDGE I64 p, t; while(~scanf("%I64d%I64d%I64d", &n, &m, &b)) { st.clear(); for(I64 i = 1; i <= n; i++) { scanf("%I64d%I64d%I64d", &fds[i].x, &fds[i].k, &fds[i].m); t = 0; for(I64 j = 0; j < fds[i].m; j++) { scanf("%I64d", &p); st.insert(p); t |= 1LL << (p - 1); } fds[i].ac = t; } if(st.size() < m)cout << -1 << endl; else solve(); } return 0;}(5)最后再做一次总结:
- 看题一定要仔细,否则做再多也是白搭。这个道理很多地方都是相通的,如果不理解人家,没搞明白人家的意思,做再多又有什么用?谁会买你的苦劳?别人只看结果的。
- 使用sort函数时,需要注意cmp函数的写法。一定不能再吃这样的哑亏了。
- 对于不明白的,不必急着刻意去想懂它,先搁一搁,平时走路、洗澡的时候可以想想,有了一段时间就能彻底明白了。现在还不懂就记住这些坑,下次写代码的时候注意好。明白了以后就自然不会再放错了。
- Codeforces-417D总结&题解
- codeforces 594D题解
- codeforces D. Ice Sculptures 题解
- Codeforces 325D Reclamation 题解
- Codeforces 475D CGCDSSQ 题解
- codeforces round338 总结&&题解
- Codeforces 835D总结
- codeforces #235 D. Roman and Numbers 题解
- Codeforces D. Giving Awards 412 题解
- Codeforces 343D Water Tree 题解&代码
- Codeforces 338D GCD Table 题解&代码
- Codeforces 570D Tree Requests 题解&代码
- CodeForces 160D Edges in MST 题解
- Codeforces Round #361 (Div. 2)-D 题解
- Codeforces 586D Phillip and Trains 题解
- Codeforces 9D How many trees? 题解
- Codeforces 493D Vasya and Chess 题解
- Codeforces 803D Magazine Ad 题解
- 函数重载解析
- SPOJ
- 今日计划
- 数据结构8:希尔排序专题
- Java 生成随机日期,时间
- Codeforces-417D总结&题解
- servletServletConfigAndServletContext
- CF734E:Anton and Tree(缩点 & 直径)
- LeetCode500. Keyboard Row
- 轻松将电气数据集成到您的机械设计
- CodeForces
- a.length==0、a==null、a == ""、a.equals("") 的区别
- vue中的video
- 【引用】web性能测试基本知识