Codeforces Round #672 (Div. 2) Solution

前言

第一次冲到 r k 4 ( o f f i c i a l ) rk4(official) rk4(official),但是 C , E C,E C,E两道套路题做得还是太慢.
在这里插入图片描述

A A A

冒泡排序是否能在 n ( n − 1 ) 2 − 1 \dfrac{n(n-1)}{2}-1 2n(n1)1次内完成.
n ≤ 1 e 5 n\le 1e5 n1e5.

显然至多 n ( n − 1 ) 2 \dfrac{n(n-1)}{2} 2n(n1)次,这种情况当且仅当序列为严格下降序列.

void solve() {
	qr(n);
	bool flag=1;
	for(int i=1;i<=n;i++) qr(a[i]);
	for(int i=2;i<=n;i++) if(a[i-1]<=a[i]) flag=0;
	if(!flag) puts("YES");
	else puts("NO");
}

B B B

给定一个数组 a ( 1 ≤ a i ≤ 1 0 9 ) a(1\le a_i\le 10^9) a(1ai109).
求有序数对 ( i < j ) (i<j) (i<j)满足 a i & a j ≥ a i ⊕ a j a_i\&a_j\ge a_i\oplus a_j ai&ajaiaj的数量.
n ≤ 1 e 5 n\le 1e5 n1e5.

一眼可以猜到用最高位来作为判定基准.
如果最高位相同, & \& &得到最高位的1, ⊕ \oplus 没有最高位的1,显然满足.
反之,一定不满足.

如果题目允许 a i = 0 a_i=0 ai=0,那么 0 0 0只能和 0 0 0形成配对,所以我们记一下 0 0 0的个数照样可做.

int n,a[N],c[33];

void solve() {
	qr(n);
	memset(c,0,sizeof c); ll ans=0;
	for(int i=1,x;i<=n;i++) {
		qr(x);
		for(int j=30;j>=0;j--)
			if(x>>j&1) {c[j]++; break;}
	}
	for(int j=30;j>=0;j--) ans += (ll)c[j]*(c[j]-1)/2;
	pr2(ans);
}

C C C

给定一个 n n n的排列 a a a,求一个子序列的权值最大值.
1 ≤ b 1 < b 2 < . . . < b k ≤ n 1\le b_1<b_2<...<b_k\le n 1b1<b2<...<bkn表示子序列的对应下标,
我们定义权值为 a b 1 − a b 2 + a b 3 − a b 4 . . . a_{b_1}-a_{b_2}+a_{b_3}-a_{b_4}... ab1ab2+ab3ab4...(奇数位置为+,偶数位置为-).
q q q次修改操作,每一次交换两个位置 a l , a r a_l,a_r al,ar.
对于每个历史状态,输出子序列权值的最大值.
n , q ≤ 3 e 5 n,q\le 3e5 n,q3e5

看到数据范围,直接想到用线段树做 d p dp dp.
我们只要记录奇偶长度的最大和最小权值即可.
转移如下:

struct rec {
	ll l[2],r[2],res;//最小值,最大值,答案,l[0]/r[0]表示偶数长度的权值.
	rec() {l[0]=r[0]=0; l[1]=r[1]=0; res=0;}
	rec operator +(rec b) const {
		rec c=*this;
		c.res=max(c.res,b.res);
		for(int i=0;i<2;i++)
			c.l[i]=min(c.l[i],b.l[i]),
			c.r[i]=max(c.r[i],b.r[i]);
		for(int i=0;i<2;i++)
			for(int j=0;j<2;j++) {
				c.l[(i+j)&1]=min(c.l[(i+j)&1],l[i]+(i&1?-b.r[j]:b.l[j]));
				c.r[(i+j)&1]=max(c.r[(i+j)&1],r[i]+(i&1?-b.l[j]:b.r[j]));
			}
		for(int i=0;i<2;i++) c.res=max(c.res,c.r[i]);
		return c;
				
	}
} f[N<<2];

转移的时候考虑符号即可.


int n,q,a[N];

struct rec {
	ll l[2],r[2],res;
	rec() {l[0]=r[0]=0; l[1]=r[1]=0; res=0;}
	rec operator +(rec b) const {
		rec c=*this;
		c.res=max(c.res,b.res);
		for(int i=0;i<2;i++)
			c.l[i]=min(c.l[i],b.l[i]),
			c.r[i]=max(c.r[i],b.r[i]);
		for(int i=0;i<2;i++)
			for(int j=0;j<2;j++) {
				c.l[(i+j)&1]=min(c.l[(i+j)&1],l[i]+(i&1?-b.r[j]:b.l[j]));
				c.r[(i+j)&1]=max(c.r[(i+j)&1],r[i]+(i&1?-b.l[j]:b.r[j]));
			}
		for(int i=0;i<2;i++) c.res=max(c.res,c.r[i]);
		return c;
				
	}
} f[N<<2];

void bt(int x,int l,int r) {
	if(l == r) {
		f[x]=rec();
		f[x].l[1]=f[x].r[1]=f[x].res=a[l];
		return ;
	}
	int mid=(l+r)/2;
	bt(lc,l,mid);
	bt(rc,mid+1,r);
	f[x]=f[lc]+f[rc];
}

void change(int x,int l,int r,int pos) {
	if(l == r) {
		f[x]=rec();
		f[x].l[1]=f[x].r[1]=f[x].res=a[l];
		return;
	}
	int mid=(l+r)/2;
	if(pos<=mid) change(lc,l,mid,pos);
	else change(rc,mid+1,r,pos);
	f[x]=f[lc]+f[rc];
}

void solve() {
	qr(n); qr(q);
	for(int i=1;i<=n;i++) qr(a[i]);
	bt(1,1,n);
	pr2(f[1].res);
	while(q--) {
		int l,r; qr(l); qr(r);
		swap(a[l],a[r]);
		change(1,1,n,l);
		change(1,1,n,r);
		pr2(f[1].res);
	}
}

int main() {
	int T;
	qr(T);
	while(T--)
		solve();
}

l o c k lock lock之后,到 r o o m room room内看到神奇贪心做法…
显然,子序列的长度为奇数.
然后序列可以看作若干段极大下降序列的拼接.
我们对于一个下降子数组,在头加,在尾减一定最优.
这里的头定义为局部极大值,尾定义尾局部极小值.
显然序列的第一个头一定前于第一个尾,最后一个头一定在最后一个尾的后面.
特别的,令 a 0 = a n + 1 = 0 a_0=a_{n+1}=0 a0=an+1=0.

int n,q,a[N];
ll ans;

void insert(int x,int d=1) {
	if(1 <= x && x <= n) {
		if(a[x-1]<a[x]&&a[x]>a[x+1]) ans += a[x]*d;
		if(a[x-1]>a[x]&&a[x]<a[x+1]) ans -= a[x]*d;
	}
}

void solve() {
	qr(n); qr(q); ans=0; a[n+1]=a[0]=0;
	for(int i=1;i<=n;i++) qr(a[i]);
	for(int i=1;i<=n;i++) insert(i);
	pr2(ans); while(q--) {
		int l,r,i; qr(l); qr(r);
		if(l == r) {pr2(ans); continue;}
		for(i=l-1;i<=l+1;i++) insert(i,-1);
		for(i=max(i,r-1);i<=r+1;i++) insert(i,-1);
		swap(a[l],a[r]);
		for(i=l-1;i<=l+1;i++) insert(i);
		for(i=max(i,r-1);i<=r+1;i++) insert(i);
		pr2(ans);
	}
}

D D D

n n n盏灯,灯亮的时间为 [ l i , r i ] [l_i,r_i] [li,ri].
给定常数 k k k.问有多少种大小为 k k k的灯集合在某一时刻能够都亮.

计数题,我们要选好计数的位置.
显然,一个子集对应的所有线段的交的左端点唯一.
我们以这个左端点作为计数点.

我们容易用差分记录一个位置被多少盏灯覆盖.
设前面转移过来的灯有 a a a盏,当前位置有 b b b盏.
那么为了保证不重不漏我们必须保证至少选择 b b b盏之一.(左端点固定)
所以总方案为 C a + b k − C a k C_{a+b}^k-C_a^k Ca+bkCak.

int n,a,b,k;
map<pii,int> s;
ll ans,jc[N],inv[N];

ll C(int x,int y) {
	return x<y?0:jc[x]*inv[y]%mod*inv[x-y]%mod;
}

void solve() {
	qr(n);  qr(k);
	jc[0]=1; for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
	inv[n]=power(jc[n]); for(int i=n;i;i--) inv[i-1]=inv[i]*i%mod;
	for(int i=1,l,r;i<=n;i++) {
		qr(l); qr(r);
		s[mk(l,1)]++;
		s[mk(r+1,-1)]++;
	}
	for(auto it:s) {
		pii p=it.fi; b=it.se;
		if(p.se == -1) a -= b;
		else {
			ans += C(a+b,k)-C(a,k);
			a += b;
		}
	}
	pr2((ans%mod+mod)%mod);
}

E E E

n n n只战斗旅鼠,其中有若干只持盾.
对于一个防御队形,我们定义保护值为数对 ( i , j ) (i,j) (i,j)[ i < j i<j i<j i , j i,j i,j不持盾但是中间有一个盾(即位于 ( i , j ) (i,j) (i,j))].
对于每一个移动步数 k ∈ [ 0 , n ∗ ( n − 1 ) 2 ] k\in [0,\dfrac {n*(n-1)}{2}] k[0,2n(n1)],求移动次数至多 k k k次的最大保护值.
n ≤ 80 n\le 80 n80.

设总共有 k k k个盾,位置为 a a a: a 1 < a 2 < a 3 < . . . < a k a_1<a_2<a_3<...<a_k a1<a2<a3<...<ak.
特别的,定义 a 0 = 0 , a k + 1 = n + 1 a_0=0,a_{k+1}=n+1 a0=0,ak+1=n+1.
那么 a n s = C n − k 2 − ∑ i = 1 k C a i − a i − 1 − 1 2 ans=C_{n-k}^2 -\sum_{i=1}^k C_{a_i-a_{i-1}-1}^2 ans=Cnk2i=1kCaiai112.

所以我们 d p dp dp求后面部分的最小值即可.
状态参数:盾的数量,最后一个盾的位置,总移动步数.
状态意义: ∑ i = 1 k C a i − a i − 1 − 1 2 \sum_{i=1}^k C_{a_i-a_{i-1}-1}^2 i=1kCaiai112的最小值.

总复杂度为 O ( n 5 ) O(n^5) O(n5)(可以轻松通过此题)

int n,a[N],tot,f[N][N][N*N/2];

void upd(int &x,int y) {x=max(x,y);}

void solve() {
	qr(n);  int m=n*(n-1)/2;
	for(int i=1,x;i<=n;i++) {
		qr(x);
		if(x) a[++tot]=i;
	}
	int ans=(n-tot)*(n-tot-1)/2;
	memset(f,-2,sizeof f); int init=f[0][0][0]; f[0][0][0]=0;
	a[++tot]=n+1;
	for(int i=0;i<tot;i++)
		for(int j=i;j<=n;j++) 
			for(int x=j+1;x<=n+1;x++) {
				int d=abs(a[i+1]-x),val=(x-j-1)*(x-j-2)/2;
				for(int k=0;k<=m;k++) if(f[i][j][k] > init)
					upd(f[i+1][x][k+d],f[i][j][k]-val);
			}
	for(int i=0;i<=m;i++) 
		pr1(ans+f[tot][n+1][i]),upd(f[tot][n+1][i+1],f[tot][n+1][i]);
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值