【NOI2019模拟2019.6.17】互膜

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 n1轮操作,第 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]>=01<=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]

【NOI2019模拟2019.6.17】互膜_第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);
	}
}

你可能感兴趣的:(线段树,单调队列,单调栈)