[LG4890]Never·island DP
题解:
感到此题非常的神奇。。。看了大佬的题解才懂的。
首先建模:
先把所有队伍的出去回来时间都放在一个数组里,然后排序,从左到右扫一边,给每个队伍都建一个带权点,进行如下操作:
(s表示出发,t表示回家, len表示两个点之间的时间差)
1,对于s --- > t的情况,如果两者属于一个队伍,那么给这个队伍加上len,表示如果给这个队伍钥匙可以获得的贡献。
如果两者不属于同一个队伍,那么在这两个队伍之间连一条边权为len的边,表示如果同时给这2个队伍钥匙可以获得的贡献。
2,对于s ---> s的情况,给左边的队伍加上len,表示如果给左边队伍钥匙可以获得的贡献。
3,对于t ---> t的情况,给右边队伍加上len,表示如果给右边队伍钥匙可以获得的贡献。
4,对于t ---> s的情况,直接给ans += len,因为不需要钥匙就可以获得这个贡献。
然后考虑如何DP。
我们观察到每个点最多只有一条入边,最多只有一条出边,因此这些点和边构成了由一堆链组成的图。而这些链两两不相交(不互相干扰)
因此我们只需要保证在DP时,一条链中的点都按顺序放在一起即可。所以我们对整张图做一个拓扑排序,最后得到的DP顺序应该是类似这样的:(可能有点单独成链)
相当于把所有链一一放好,然后DP。
那么如何转移呢?
设f[i][j][0/1]表示DP到i位,当前选了j个点,当前点选or不选的贡献。
那么对于不相连的点,直接更新即可,
对于相连的点,如果用f[i - 1][j - 1][1]更新f[i][j][1],那么还需要加上两点之间边的边权,表示两个点同时选带来的多余贡献。
最后的答案等于ans = d[n * 2] - d[1] - ans - max(f[i][j][1], f[i][j][0]);
d[n * 2] - d[1]是在获取整个线段的长度,减去ans是在减掉一开始就有的贡献(无需钥匙的),max(f[i][j][0], f[i][j][1])是给钥匙带来的贡献,相减后剩下的就是开门的天数。
(一遍A,感到非常愉悦,而且速度还可以)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 2100 5 #define ac 5000 6 #define LL long long 7 8 int n, k, cnt, ans; 9 int power[AC], Next[AC], last[AC]; 10 int in[AC], d[AC], tot;//存下DP序列 11 LL s[AC], f[AC][AC][2]; 12 bool z[AC]; 13 //DP到i位,选了j个点,当前点选与不选,s是当前点的向后的边的边权 14 struct node{ 15 int x, id; 16 bool z; 17 }p[ac]; 18 19 inline int read() 20 { 21 int x = 0;char c = getchar(); 22 while(c > '9' || c < '0') c = getchar(); 23 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 24 return x; 25 } 26 27 inline bool cmp(node a, node b) 28 { 29 return a.x < b.x; 30 } 31 32 inline void upmax(int &a, int b) 33 { 34 if(b > a) a = b; 35 } 36 37 void dfs(int x) 38 { 39 d[++tot] = x, z[x] = true; 40 while(Next[x]) d[++tot] = Next[x], x = Next[x], z[x] = true; 41 } 42 43 void pre() 44 { 45 n = read(), k = read(); 46 for(R i = 1; i <= n; i ++) 47 { 48 p[++cnt] = (node){read(), i, 0}; 49 p[++cnt] = (node){read(), i, 1}; 50 } 51 sort(p + 1, p + cnt + 1, cmp); 52 int b = 2 * n; 53 for(R i = 1; i < b; i ++) 54 { 55 int len = p[i + 1].x - p[i].x, x = p[i].id, y = p[i + 1].id; 56 if(!p[i].z && p[i + 1].z) 57 { 58 if(x == y) power[p[i].id] += len; 59 else Next[x] = y, last[y] = x, s[x] = len, ++ in[y]; 60 } 61 else if(!p[i].z && !p[i + 1].z) power[x] += len; 62 else if(p[i].z && p[i + 1].z) power[y] += len; 63 else ans += len; 64 } 65 } 66 67 void work() 68 { 69 int b = 2 * n; 70 for(R i = 1; i < b; i ++) //类似于拓扑排序,要没有入度才行 71 if(!z[p[i].id] && !in[p[i].id]) dfs(p[i].id); 72 for(R i = 1; i <= n; i ++)//枚举点 73 { 74 for(R j = 1; j <= k; j ++) 75 { 76 f[i][j][0] = max(f[i - 1][j][0], f[i - 1][j][1]); 77 if(last[d[i]]) f[i][j][1] = max(f[i - 1][j - 1][0], f[i - 1][j - 1][1] + s[last[d[i]]]) + power[d[i]]; 78 else f[i][j][1] = max(f[i - 1][j - 1][0], f[i - 1][j - 1][1]) + power[d[i]]; 79 } 80 } 81 printf("%lld\n", p[n * 2].x - ans - p[1].x - max(f[n][k][1], f[n][k][0])); 82 } 83 84 int main() 85 { 86 freopen("in.in", "r", stdin); 87 pre(); 88 work(); 89 fclose(stdin); 90 return 0; 91 }