[CSP-S2019 D2T2]划分 题解
作者:互联网
Preface
终于把三年前的坑填了QAQ。
这道题简直可以算卡常卡空间比赛了qwq,在线膜拜考场 AC 巨佬 Orz
Analysis
这道题和 [CF229D]Towers 可以说除了恶心人的数据几乎一模一样awa
- 法一:\(36pts\)
首先把前缀和数组 \(s\) 求出来。
对于 \(n \le 500\),显然直接用 \(O(N^3)\) 的区间 DP 即可。
- 法二:\(64pts\)
要想多骗点分,还需要观察出来一个性质:当最后一段尽量小的时候,答案一定最优。
这个性质虽然不一定能看出来,但应该都知道是为什么。
传统的区间 DP 显然过不了这题,考虑换一种 DP 状态:
设 \(dp(i,j)\) 表示当前第 \(i\) 个数所处的这一段开头为 \(j + 1\) 时的最小答案。
则有 \(dp(i,j) = \min{\{dp(j,k) \}} + (s_i - s_j) ^ 2\)
其中 \(k\) 要满足 \(s_j - s_k \le s_i - s_j\)
虽然这样仍是 \(O(N^3)\) 的,但它有一些神奇的性质。
首先根据上述结论,\(k\) 越大,\(dp(j,k)\) 越小,而且 \(s_j - s_k\) 会越小,满足上式的可能性越高。
考虑建立一个数组 \(pos\),令 \(pos_j\) 为使得 \(dp(j,k)\) 最小的 \(k\) 的取值。
这时发现可以将 \(dp\) 改为一维,\(dp(i)\) 表示 \(1 \sim i\) 的最优划分值。
从大到小枚举 \(j\),若满足 \(s_j - s_{pos_j} \le s_i - s_j\),则 \(dp(i) = dp(j) + (s_i - s_j) ^ 2\)
然后记录一下这个取值,即 \(pos_i = j\),目标状态即为 \(dp(n)\)。
时间复杂度:\(O(N^2)\)
到这个时候我们已经可以拿到 \(64 pts\) 的大众分了awa
接下来 \(n = 5 \times 10^5\) 是给 \(O(N\log N)\) 的决策单调性做法的,然鹅我并不会那种东西 QAQ,有兴趣可以去 [CF229D]Towers 这个题的题解里看看。
下面直接开始正解。
- 法三:\(100 pts\)
发现限制我们的是 \(s_j - s_{pos_j} \le s_i - s_j\) 这个东西,考虑把能一起维护的放到一边:
即 \(2 \times s_j - s_{pos_j} \le s_i\)
对于每个 \(i\),求出最近的满足这个式子的 \(j\) 即可。
看 \(n = 4 \times 10^7\) 的恐怖范围,加上 \(s_i\) 具有单调性,不难想到珂以用单调队列优化~
但我们平常学到的都是求最小,怎么求最近呢?
珂以用类似滑动窗口的思路解决,两头同时更新。
具体思路是仍照常维护一个 \(2 \times s_j - s_{pos_j}\) 单调递增的队列。
在扫到一个 \(i\) 时,处理一下队首,如果队首和队首后面的元素同时满足这个限制,那么两者一定都能满足 \(i+1\sim n\) 的限制,而且后面这个元素一定比队首晚入队,更优。
那么这个时候,我还要你队首干什么呢?
这就是我们常说的,如果一个选手比你小还比你强,那么你就可以退役了o(╥﹏╥)o
直接删去队首,继续判断直到队列元素数 \(\le 1\) 或者不满足这个条件即可。
其他就和普通的单调队列没区别了 (*▽*)
注意的是这题卡时空限制,而且要用 int128 或者高精度,而且 \(dp\) 数组开不下。
只能把 \(pos\) 数组处理出来,最后统一计算输出QAQ
时间复杂度:\(O(N)\)
#include <bits/stdc++.h>
#define LL __int128
using namespace std;
const int maxn = 4e7 + 5;
const int maxm = 1e5 +5;
typedef long long ll;
LL read() {
LL s = 0,f = 1;
char c = getchar();
for(;!isdigit(c);c = getchar())
if(c == '-')f = -1;
for(;isdigit(c);c = getchar())s = (s << 1) + (s << 3) + (c ^ '0');
return s * f;
}
void write(LL x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)write(x / 10);
putchar(x % 10 + '0');
return ;
}
int x,y,z;
int b[maxn];
ll s[maxn];
int a[maxn],m,n,pos[maxn];
int p[maxm],l[maxm],r[maxm];
int q[maxn],head,tail;
ll SUM(int x) {
return s[x] - s[pos[x]] + s[x];
}
void datamaker() {
scanf("%d%d%d%d%d%d",&x,&y,&z,&b[1],&b[2],&m);
for(int i = 1;i <= m;++ i)scanf("%d%d%d",&p[i],&l[i],&r[i]);
for(int i = 3;i <= n;++ i)b[i] = (x * b[i - 1] + y * b[i - 2] + z) & ((1 << 30) - 1);
for(int j = 1;j <= m;++ j) {
for(int i = p[j - 1] + 1;i <= p[j];++ i) {
a[i] = (b[i] % (r[j] - l[j] + 1)) + l[j];
s[i] = s[i - 1] + a[i];
}
}
return ;
}
int main() {
int tp;
scanf("%d%d",&n,&tp);
if(tp)datamaker();
else for(int i = 1;i <= n;++ i)scanf("%d",&a[i]),s[i] = s[i - 1] + a[i];
q[head = tail = 1] = 0;
for(int i = 1;i <= n;++ i) {
for(;head < tail&&SUM(q[head]) <= s[i]&&SUM(q[head + 1]) <= s[i];)q[head ++] = 0;
pos[i] = q[head];
for(;head < tail&&SUM(q[tail]) >= SUM(i);)q[tail --] = 0;
q[++ tail] = i;
}
LL ans = 0;
for(int cur = n;cur;cur = pos[cur])ans += ((LL)(s[cur] - s[pos[cur]]) * (s[cur] - s[pos[cur]]));
write(ans);
return 0;
}
标签:le,cur,int,题解,pos,maxn,S2019,D2T2,dp 来源: https://www.cnblogs.com/663B/p/16383223.html