https://jzoj.net/senior/#contest/show/2775/2
给你 n ( 偶 数 ) n(偶数) n(偶数)张卡片,第 i i i张价值为 s [ i ] s[i] s[i]
一开始编号奇数的卡片属于A,偶数的属于B。
一共有 n − 1 n-1 n−1轮操作,第 i i i轮, i i i是奇数A操作, i i i是偶数B操作。
操作可以将第 i i i或 i + 1 i+1 i+1卡的所属权反转,或者不操作。
两人绝顶聪明,希望自己的卡片和最大,求A最大的卡片和。
有m次修改,每次减小一张卡片的价值,再问A最大的卡片和。
任意时刻满足 s [ i ] > = 0 , 1 < = n , m < = 200000 s[i]>=0,1<=n,m<=200000 s[i]>=0,1<=n,m<=200000
不难想到一个暴力的dp:
f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示,倒着考虑 i i i轮以后的操作,第 i i i张卡在开始时有没有被反转,先手-后手的最大值。
显然可以得到 f [ i ] [ 0 ] > = f [ i ] [ 1 ] f[i][0]>=f[i][1] f[i][0]>=f[i][1]的结论,转移如下。
A n s = ( f [ 1 ] [ 0 ] + ∑ s [ i ] ) / 2 Ans=(f[1][0]+\sum s[i])/2 Ans=(f[1][0]+∑s[i])/2
感觉有很多相同的项,不妨差分试试,设 d e l t a [ i ] = f [ i ] [ 0 ] − f [ i ] [ 1 ] delta[i]=f[i][0]-f[i][1] delta[i]=f[i][0]−f[i][1]
那么发现 d e l t a [ i ] delta[i] delta[i]就是一个后缀 m i n min min,那么正着做一个递增的单调栈,即可得到每一段的 d e l t a [ i ] delta[i] delta[i]
再观察 f [ i ] [ 0 ] f[i][0] f[i][0]的转移:
f [ i ] [ 0 ] = s [ i ] − f [ i + 1 ] [ 0 ] + d e l t a [ i + 1 ] f[i][0]=s[i]-f[i+1][0]+delta[i+1] f[i][0]=s[i]−f[i+1][0]+delta[i+1]
不难得到: f [ 1 ] [ 0 ] = ∑ i = 1 n s [ i ] ∗ ( − 1 ) i + 1 + ∑ i = 2 n d e l t a [ i ] ∗ ( − 1 ) i f[1][0]=\sum_{i=1}^n s[i]*(-1)^{i+1}+\sum_{i=2}^n delta[i]*(-1)^{i} f[1][0]=∑i=1ns[i]∗(−1)i+1+∑i=2ndelta[i]∗(−1)i
现在的问题在于维护 d e l t a delta delta,也就是动态维护单调栈,参见【WinterCamp 2013】楼房重建。
线段树维护单调栈,时间复杂度 O ( n l o g 2 n ) O(n~log^2n) O(n log2n)。
注意到这题的 s [ i ] s[i] s[i]只会变小,但是上面的做法可以做变大的,只会变小的话,单调栈每次要不弹掉一段连续的元素,要不就加入一个元素,所以可以直接用平衡树维护这个东西,时间复杂度 O ( n l o g n ) O(n~log~n) O(n log n)。
但是这个做法会比上面的难写一点。
Code:
#include
#define fo(i, x, y) for(int i = x, B = y; i <= B; i ++)
#define ff(i, x, y) for(int i = x, B = y; i < B; i ++)
#define fd(i, x, y) for(int i = x, B = y; i >= B; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;
const int N = 2e5 + 5;
int n, Q, x, y, s[N];
ll sum, t[N * 4], fx[N * 4];
int pl, pr;
#define fu(x) (((x) & 1) ? -1 : 1)
#define i0 i + i
#define i1 i + i + 1
ll query(int i, int x, int y, int z) {
if(x == y) return 2 * min(s[x], z);
int m = x + y >> 1;
if(z < t[i1]) return query(i0, x, m, z) + 2 * z * fu(m - x + 1) * ((y - m) & 1);
return fx[i] + query(i1, m + 1, y, z) * fu(m - x + 1);
}
void ch(int i, int x, int y) {
if(x == y) { t[i] = s[x]; return;}
int m = x + y >> 1;
if(pl <= m) ch(i0, x, m); else ch(i1, m + 1, y);
t[i] = min(t[i0], t[i1]);
fx[i] = query(i0, x, m, t[i1]);
}
int main() {
freopen("flip.in", "r", stdin);
freopen("flip.out", "w", stdout);
scanf("%d", &n);
fo(i, 1, n) scanf("%d", &s[i]), sum += s[i] * -fu(i) + s[i];
fo(i, 1, n) pl = pr = i, ch(1, 2, n);
scanf("%d", &Q);
pp("%lld\n", (query(1, 2, n, 1e9) + sum) / 2);
fo(ii, 1, Q) {
scanf("%d %d", &x, &y);
sum -= y * -fu(x) + y;
s[x] -= y;
pl = pr = x;
ch(1, 2, n);
pp("%lld\n", (query(1, 2, n, 1e9) + sum) / 2);
}
}