CF321E Ciel and Gondolas
题意:给定序列,将其分成k段。如果[l, r]在一段,那么每对不相同的i,j∈[l, r]都会有ai,j的代价。求最小总代价。
解:提供两种方案。第三种去bzoj贞鱼的n²算法。
决策单调性优化:
对于两个转移点j1 < j2,若在某个点i上j2更优,则i后面的j2全部更优。这就是决策单调性。
有两种写法。一种是维护决策栈(???),我自己YY了一个线段树写法WA飞了。
还有一种是分治。对于一段待转移的区间[l, r],它们的最优转移来自于[L, R]
则对于mid = (l + r) >>1,我们扫一遍[L, R],得到mid的最优转移p
然后分治下去。
1 #include <cstdio> 2 #include <cstring> 3 4 const int N = 4010, K = 810, INF = 0x3f3f3f3f; 5 6 inline void read(int &x) { 7 x = 0; 8 char c = getchar(); 9 while(c < '0' || c > '9') { 10 c = getchar(); 11 } 12 while(c >= '0' && c <= '9') { 13 x = (x << 3) + (x << 1) + c - 48; 14 c = getchar(); 15 } 16 return; 17 } 18 19 int f[N][K], n, k, a[N][N]; 20 int tag[N], turn; 21 22 inline void exmin(int &a, int b) { 23 a > b ? a = b : 0; 24 return; 25 } 26 27 void solve(int L, int R, int l, int r) { 28 if(R < L || r < l) return; 29 //printf("solve %d %d -> %d %d \n", L, R, l, r); 30 if(L == R) { 31 for(int i = l; i <= r; i++) { 32 f[i][turn] = f[R][turn - 1] + a[i][R + 1]; 33 //printf("f %d %d = %d + %d \n", i, turn, f[R][turn - 1], a[i][R + 1]); 34 } 35 return; 36 } 37 int mid = (l + r) >> 1, p = L; 38 for(int i = L; i < mid && i <= R; i++) { 39 if(f[mid][turn] > f[i][turn - 1] + a[mid][i + 1]) { 40 p = i; 41 f[mid][turn] = f[i][turn - 1] + a[mid][i + 1]; 42 //printf("f %d %d = %d from %d \n", mid, turn, f[mid][turn], p); 43 } 44 } 45 solve(L, p, l, mid - 1); 46 solve(p, R, mid + 1, r); 47 return; 48 } 49 50 int main() { 51 52 //freopen("in.in", "r", stdin); 53 //freopen("my.out", "w", stdout); 54 55 read(n); read(k); 56 for(register int i = 1; i <= n; i++) { 57 for(register int j = 1; j <= n; j++) { 58 read(a[i][j]); 59 if(i < j) a[i][j] = 0; 60 } 61 } 62 for(register int i = 1; i <= n; i++) { 63 for(register int j = n; j >= 1; j--) { 64 a[i][j] += a[i][j + 1]; 65 } 66 } 67 for(register int i = 1; i <= n; i++) { 68 for(register int j = n; j >= 1; j--) { 69 a[i][j] += a[i - 1][j]; 70 } 71 } 72 memset(f, 0x3f, sizeof(f)); 73 f[0][0] = 0; 74 for(int j = 1; j <= k; j++) { 75 turn = j; 76 //printf("turn = %d \n", j); 77 solve(0, n - 1, 1, n); 78 /*for(int i = 1; i <= n; i++) { 79 printf("%d ", f[i][turn]); 80 } 81 printf("\n");*/ 82 } 83 84 printf("%d\n", f[n][k]); 85 return 0; 86 }
带权二分:我们假装那个函数是凸的。
然后按照套路带权二分一波。
记得一定要让k的变化和D的变化同步,这一点可以通过调整±D来实现。
之后尽量让k小,l端mid,r端要mid - 1。
1 #include <cstdio> 2 #include <algorithm> 3 4 typedef long long LL; 5 const int N = 4010; 6 LL INF = 1e17; 7 8 inline void read(LL &x) { 9 x = 0; 10 char c = getchar(); 11 while(c < '0' || c > '9') { 12 c = getchar(); 13 } 14 while(c >= '0' && c <= '9') { 15 x = (x << 3) + (x << 1) + c - 48; 16 c = getchar(); 17 } 18 return; 19 } 20 21 int n, k, g[N]; 22 LL f[N], a[N][N], D, ans; 23 24 inline int check(LL mid) { 25 D = mid; 26 for(register int i = 1; i <= n; i++) { 27 f[i] = INF; 28 for(register int j = 0; j < i; j++) { 29 if(f[i] > f[j] + a[i][j + 1] - D) { 30 f[i] = f[j] + a[i][j + 1] - D; 31 g[i] = g[j] + 1; 32 } 33 else if(f[i] == f[j] + a[i][j + 1] - D) { 34 g[i] = std::min(g[i], g[j] + 1); 35 } 36 } 37 } 38 ans = f[n]; 39 return g[n]; 40 } 41 42 int main() { 43 scanf("%d%d", &n, &k); 44 for(register int i = 1; i <= n; i++) { 45 for(register int j = 1; j <= n; j++) { 46 read(a[i][j]); 47 if(i < j) a[i][j] = 0; 48 } 49 } 50 for(register int i = 1; i <= n; i++) { 51 for(register int j = n; j >= 1; j--) { 52 a[i][j] += a[i][j + 1]; 53 } 54 } 55 for(register int i = 1; i <= n; i++) { 56 for(register int j = n; j >= 1; j--) { 57 a[i][j] += a[i - 1][j]; 58 } 59 } 60 // 61 62 LL l = -a[n][1], r = a[n][1]; 63 while(l < r) { 64 LL mid = (l + r + 1) >> 1; 65 int t = check(mid); 66 if(t == k) { 67 printf("%lld\n", ans + k * mid); 68 return 0; 69 } 70 if(t < k) l = mid; 71 else r = mid - 1; 72 } 73 check(r); 74 printf("%lld\n", ans + k * r); 75 return 0; 76 }