[CSP 2019]划分[dp][贪心][单调队列优化]
原题:https://loj.ac/problem/3212
CSP 结束了快一年了我终于来补这道题了、、
首先考虑最暴力的做法:设 $f[i,j]$ 表示左端点为 j,右端点为 i 的这一段卷起来最小的答案,容易得到转移方程
$$f[i,j]=\min_k\{f[j-1,k]+(sum[i]-sum[j-1])^2\}(sum[i]-sum[j-1]\geq sum[j-1]-sum[k-1])$$
复杂度 $O(n^3)$,可以获得 36pts 的好成绩
考虑优化,因为要最小化平方的和,可以贪心的想到我们需要保证每一段的和尽可能的小,这样才能使贡献尽可能小
也就是说,假设需要把 $[i,j]$ 这段卷起来,我们要尽量保证 $i$ 和 $j$ 靠近(使每一段和尽可能小)
因此转移的时候直接取最后一个点就可了,复杂度 $O(n^2)$,可以获得 64pts 的好成绩
吐槽:我考场想出来了这个做法结果读入的时候没开 long long 直接 64->52
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <cstdlib> 6 #include <cctype> 7 #include <vector> 8 #include <queue> 9 #define inf 100010 10 #define Inf ((int)5e6+10) 11 #define ll long long 12 13 namespace chiaro { 14 15 template <class T> 16 inline void read(T& num) { 17 num = 0; register char c = getchar(), up = c; 18 while(!isdigit(c)) up = c, c = getchar(); 19 while(isdigit(c)) num = (num * 10) + c - '0', c = getchar(); 20 up == '-' ? num = -num : 0; 21 } 22 template <class T> 23 inline void read(T& a, T& b) {read(a); read(b);} 24 25 ll n, type; 26 ll a[inf]; 27 ll sum[inf]; 28 ll f[inf]; 29 ll l[inf]; 30 31 inline void setting() { 32 #ifdef ONLINE_JUDGE 33 freopen("partition.in", "r", stdin); 34 freopen("partition.out", "w", stdout); 35 #endif 36 } 37 38 inline int main () { 39 setting(); 40 read(n, type); 41 for(int i = 1; i <= n; i++) read(a[i]); 42 for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i]; 43 l[1] = a[1]; f[1] = a[1] * a[1]; 44 for(int i = 2; i <= n; i++) { 45 for(int j = 0; j < i; j++) { 46 if(sum[i] - sum[j] < l[j]) continue; 47 l[i] = sum[i] - sum[j]; 48 f[i] = f[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]); 49 } 50 } 51 printf("%lld\n", f[n]); 52 return 0; 53 } 54 55 } 56 57 signed main () {return chiaro::main();}
再来优化
我们可以设 $g[i]$ 表示右端点为 $i$ 的这一卷旁边的那一卷的右端点,我们只需要线性求出 g 本题就解决了
那么有公式
$$设 p=g_i,有 sum_i-sum_p\geq sum_p-sum_{g_p}\rightarrow sum_i\geq 2\times sum_p-sum_{g_p}$$
我们可以设 $calc(x)=2\times sum_x-sum_{g_x}$,那么一个位置 $i$ 合法当且仅当:$sum_i\geq calc(g_i)$
求 g 的时候根据 64pts 的贪心可以得到我们要取能满足合法条件的最靠右侧的点,使用队列
然后 $g_i$ 就是队首,我们再把 $i$ 加入队列,因为如果一个位置 $j$ 他比 $i$ 靠左,贡献还比 $i$ 大,那么显然就不可能是答案,直接弹出,所以这是个单调队列
这样就求玩了 g,然后根据 g 数据线性跳回去统计答案
复杂度 $O(n)$,能获得 88pts 的好成绩
再使用常数小的高精或者 __int128 可以获得 100pts 的成绩
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <cstdlib> 6 #include <cctype> 7 #include <vector> 8 #define inf ((int)4e7 + 10) 9 #define N (int)(1e5 + 10) 10 #define ll long long 11 #ifndef ONLINE_JUDGE 12 #define __int128 long long 13 #endif 14 15 namespace chiaro { 16 17 template <class T> 18 inline void read(T& num) { 19 num = 0; register char c = getchar(), up = c; 20 while(!isdigit(c)) up = c, c = getchar(); 21 while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar(); 22 up == '-' ? num = -num : 0; 23 } 24 template <class T> 25 inline void read(T& a, T& b) {read(a); read(b);} 26 template <class T> 27 inline void read(T& a, T& b, T& c) {read(a); read(b); read(c);} 28 template <class T> 29 void print(T num) {(num > 9) ? (print(num / 10), 0) : 0; putchar(num % 10 + '0'); return;} 30 31 const int MOD = (1 << 30) - 1; 32 33 ll n, type, a; 34 ll sum[inf]; 35 int g[inf]; 36 __int128 ans; 37 38 inline void setting() { 39 #ifdef ONLINE_JUDGE 40 freopen("partition.in", "r", stdin); 41 freopen("partition.out", "w", stdout); 42 #endif 43 } 44 45 inline void input() { 46 if(type){ 47 int x, y, z, m, now = 1; ll b1, b2, b3, w; 48 read(x, y, z); read(b1, b2); read(m); 49 for (register int i = 1; i <= m; ++i) { 50 int p, l, r; read(p, l, r); 51 while (now <= p) { 52 if (now == 1) w = (b1 % (r - l + 1)) + l, sum[1] = w; 53 else if (now == 2) w = (b2 % (r - l + 1)) + l, sum[2] = sum[1] + w; 54 else { 55 b3 = (x * b2 + y * b1 + z) & MOD; 56 w = (b3 % (r - l + 1)) + l, sum[now] = sum[now - 1] + w; 57 b1 = b2, b2 = b3; 58 } 59 ++now; 60 } 61 } 62 } else for(register int i = 1; i <= n; sum[i] = sum[i - 1] + a, ++i) read(a); 63 } 64 65 class queue { 66 private: 67 int a[inf]; 68 int l, r; 69 70 public: 71 queue () {l = 1, r = 0;} 72 inline void push(const int& v) {a[++r] = v;} 73 inline bool empty() {return l > r;} 74 inline void pop_back() {--r;} 75 inline void pop_front() {++l;} 76 inline int back() {return a[r];} 77 inline int front() {return a[l];} 78 inline bool have_next() {return r > l;} 79 inline int next() {return a[l + 1];} 80 }; 81 82 queue Q; 83 84 #define calc(x) ((sum[x] << 1) - sum[g[x]]) 85 86 inline int main () { 87 setting(); 88 read(n, type); input(); 89 Q.push(0); 90 for(int i = 1; i <= n; ++i) { 91 while(Q.have_next() && calc(Q.next()) <= sum[i]) Q.pop_front(); 92 g[i] = Q.front(); 93 while(!Q.empty() && calc(Q.back()) >= calc(i)) Q.pop_back(); 94 Q.push(i); 95 } 96 register int now = n; 97 while(now) { 98 ans += (__int128)(sum[now] - sum[g[now]]) * (sum[now] - sum[g[now]]); 99 now = g[now]; 100 } 101 print(ans); 102 return 0; 103 } 104 105 } 106 107 signed main () {return chiaro::main();}

浙公网安备 33010602011771号