Codeforces Round 936 (Div. 2) 题解

本文网址:https://www.cnblogs.com/zsc985246/p/18090655 ,转载请注明出处。

打完比赛火速出题解。

AB 罚时及其致命。最终排名 100。

题目风格清新,整体体验不错,推荐。

F 题解请等待后续更新。

传送门

Codeforces Round 936 (Div. 2)

A.Median of an Array

题目大意

给定长度为 \(n\) 的数组 \(a\),中位数为 \(a_{\lceil\frac{n}{2}\rceil}\)。一次操作可以选择一个数,让它自增 \(1\)。求至少需要多少次操作才能使数组的中位数改变。

多组测试,\(1 \le n \le 10^5, 1 \le a_i \le 10^9, T \le 10^4, \sum n \le 2 \times 10^5\)

思路

一直对当前中位数进行操作,直到它改变。操作次数为与中位数相同的数的个数。

直接统计即可。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;

ll n,m,k;
ll a[N],b[N];

void mian(){
	
	ll ans=1;
	scanf("%lld",&n);
	For(i,1,n){
		scanf("%lld",&a[i]);
	}
	sort(a+1,a+n+1);
	
	For(i,(n+1)/2+1,n){
		if(a[i]==a[i-1])++ans;
		else break;
	}
	
	printf("%lld\n",ans);
	
}
 
int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

B.Maximum Sum

题目大意

给定长度为 \(n\) 的数组 \(a\)。一次操作为选择一个连续区间(可以为空),并将区间中所有数的和插入到数组的任意位置。

你需要进行恰好 \(k\) 次操作,求最终数组的和的最大值。对 \(10^9+7\) 取模。

多组测试,\(1 \le n,k \le n \le 2 \times 10^5, -10^9 \le a_i \le 10^9, T \le 10^4, \sum n,\sum k \le 2 \times 10^5\)

思路

找到一个和最大的连续区间,然后将和插入到这个区间内,重复此操作。这显然是最优的。

直接计算即可。

代码实现

找和最大的连续区间时不能取模,注意开 long long

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
const ll p=1e9+7;
ll ksm(ll a,ll b){ll bns=1;while(b){if(b&1)bns=bns*a%p;a=a*a%p;b>>=1;}return bns;}

ll n,m,k;
ll a[N],b[N];
ll dp[N];

void mian(){
	
	ll ans=0,s=0;
	scanf("%lld",&n);
	scanf("%lld",&k);
	For(i,1,n){
		scanf("%lld",&a[i]);
		s+=a[i];
	}
	
	dp[1]=max(0ll,a[1]);
	For(i,2,n)dp[i]=max(0ll,dp[i-1]+a[i]);
	For(i,1,n)ans=max(ans,dp[i]);
	ans=ans%p;
	ans=(ksm(2,k)+p-1)%p*ans%p;//这里暴力计算也能过
	
	printf("%lld\n",((ans+s)%p+p)%p);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

C.Tree Cutting

题目大意

给定一棵 \(n\) 个节点的树,你需要切断恰好 \(k\) 条边,使剩下的连通块节点数的最小值最大。求这个值。

多组测试,\(1 \le k < n \le 10^5, T \le 10^4, \sum n \le 10^5\)

思路

二分答案。

check 的时候从根节点向下深搜,当一个子树当前的大小不小于 \(mid\) 时,将它与父亲节点断开。

代码实现

注意特判根节点所在连通块的大小是否满足二分值。

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k;
ll a[N],b[N];
vector<ll>e[N];
ll siz[N];
ll tmp;//断边数量

void dfs(ll x,ll fa,ll t){
	siz[x]=1;
	for(ll y:e[x]){
		if(y==fa)continue;
		dfs(y,x,t);
		siz[x]+=siz[y];
	}
	if(siz[x]>=t&&fa)siz[x]=0,++tmp;//注意根节点没有父亲节点,所以不能统计
}

bool check(ll t){
	tmp=0;
	dfs(1,0,t);
	if(siz[1]<t)--tmp;//特判
	if(tmp>=k)return true;
	return false;
}

void mian(){
	
	ll ans=0;
	scanf("%lld%lld",&n,&k);
	For(i,1,n)e[i].clear();
	For(i,1,n-1){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		e[x].pb(y),e[y].pb(x);
	}
	
	ll l=1,r=n;
	while(l<=r){
		ll mid=(l+r)>>1;
		if(check(mid))l=mid+1,ans=mid;
		else r=mid-1;
	}
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

D.

题目大意

给定一个长度为 \(n\) 的数组 \(a\) 和一个数 \(x\)。你需要找到最大的 \(k\),使存在满足以下条件的区间 \([l_1,r_1],[l_2,r_2],\dots,[l_k,r_k]\)

  1. \(l_1=1,r_k=n\)

  2. \(\forall i \in [2,n], r_{i-1}=l_i\)

  3. \(l_i \le r_i\)

  4. \((a_{l_1} \oplus a_{l_1+1} \oplus \dots \oplus a_{r_1})|(a_{l_2} \oplus a_{l_2+1} \oplus \dots \oplus a_{r_2})|\dots|(a_{l_k} \oplus a_{l_k+1} \oplus \dots \oplus a_{r_k}) \le x\)

如果 \(k\) 不存在,输出 \(-1\)

多组测试,\(1 \le n \le n \le 10^5, 0 \le x,a_i < 2^30, T \le 10^4, \sum n,\sum n \le 10^5\)

思路

考虑枚举要求 4 的最终的答案 \(ans\)

在二进制下,枚举 \(x\) 的某一位 \(1\),让 \(ans\) 的这一位为 \(0\),比这一位低的位全部为 \(1\),比这一位高的位与 \(x\) 相同,这样就能够保证 \(ans \le x\)

遍历数组 \(a\),一旦找到区间的异或和 \(s\) 满足 \(s|ans=ans\),就将区间划分开。

统计答案的最大值即可。

代码实现

注意考虑 \(ans=k\) 的情况。

代码中没有将 \(ans\) 计算出来,而是直接用位运算进行判断。

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;

ll n,m,k;
ll a[N],b[N];

void mian(){
	
	ll ans=-1;
	scanf("%lld%lld",&n,&k);
	For(i,1,n){
		scanf("%lld",&a[i]);
	}
	
	Rep(i,29,0){
		if(k&(1<<i)){
			ll s=0,cnt=0;//s为当前异或和,cnt为划分区间数
			For(j,1,n){
				s^=a[j];
				if((s&(1<<i))==0&&(((s>>i)<<i)|k)==k){
					s=0;
					++cnt;
				}
			}
			if(s==0)ans=max(ans,cnt);//此时s==0表示恰好划分完
		}
	}
	//ans=k的情况
	ll s=0,cnt=0;
	For(j,1,n){
		s^=a[j];
		if((s|k)==k){
			s=0;
			++cnt;
		}
	}
	if(s==0)ans=max(ans,cnt);
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

E.Girl Permutation

题目大意

定义 \(a_i\) 为前缀最大值当且仅当 \(\forall j \in [1,i-1],a_i>a_j\),为后缀最大值当且仅当 \(\forall j \in [i+1,n],a_i>a_j\)

给定一个长度为 \(n\)排列所有前缀最大值和后缀最大值的位置,求有多少种满足条件的排列。答案对 \(10^9+7\) 取模。

多组测试,\(1 \le n \le 2 \times 10^5,T \le 10^4, \sum n \le 2 \times 10^5\)

思路

记给定的前缀最大值和后缀最大值的数组分别为 \(a,b\),长度分别为 \(m1,m2\)

发现有解时一定满足 \(a_1=1,a_{m1}=b_1,b_{m2}=n\),且 \(a_{m1}\) 就是最大值所在的位置。

那么可以发现,\([1,a_{m1}-1],[a_{m1}+1,n]\) 成为了两个独立的区间,互不影响,因为中间有一个最大值“挡住”了“信息的传递”。

所以我们只需要从除去 \(n\) 的其它数中选出 \(a_{m1}-1\) 个放在左边,剩下的放在右边,方案为 \(C_{n-1}^{a_{m1}-1}\)

对于区间 \([1,a_{m1}-1]\) 来说,最大值位置为 \(a_{m1-1}\)。因为如果左边有更大的值,不符合前缀最大值定义;右边有更大的值,那么这个更大的值必然也是前缀最大值,矛盾。

此时 \([a_{m1-1}+1,a_{m1}-1]\) 的数可以随便排列,\([1,a_{m1-1}-1]\) 成为了一个子问题。选出 \(a_{m1-1}-1\) 个数放在左边,剩下的数放在右边随便排列,方案为 \(C_{a_{m1}-2}^{a_{m1-1}-1} \times (a_{m1}-a_{m1-1}-1)!\)

区间 \([a_{m1}+1,n]\) 同理。

将所有答案相乘即可。

代码实现

注意取模。

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
const ll p=1e9+7;
ll ksm(ll a,ll b){ll bns=1;while(b){if(b&1)bns=bns*a%p;a=a*a%p;b>>=1;}return bns;}

ll n,m1,m2,k;
ll a[N],b[N];

//组合数
ll jc[N],inv[N];
void init(ll n){
	jc[0]=1;
	For(i,1,n)jc[i]=jc[i-1]*i%p;
	inv[n]=ksm(jc[n],p-2);
	Rep(i,n-1,0)inv[i]=inv[i+1]*(i+1)%p;
}
ll C(ll n,ll m){
	return jc[n]*inv[m]%p*inv[n-m]%p;
}

void mian(){
	
	ll ans=1;
	scanf("%lld",&n);
	scanf("%lld%lld",&m1,&m2);
	For(i,1,m1)scanf("%lld",&a[i]);
	For(i,1,m2)scanf("%lld",&b[i]);
	
	if(a[m1]!=b[1]||a[1]!=1||b[m2]!=n){//无解
		printf("0\n");
		return;
	}
	For(i,1,m1-1)ans=ans*C(a[i+1]-2,a[i]-1)%p*jc[a[i+1]-a[i]-1]%p;
	ans=ans*C(n-1,a[m1]-1)%p;
	For(i,2,m2)ans=ans*C(n-b[i-1]-1,n-b[i])%p*jc[b[i]-b[i-1]-1]%p;
	
	printf("%lld\n",ans);
	
}

int main(){
	init(1e6);
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

F.

题目大意

思路

代码实现


尾声

如果有什么问题,可以直接评论!

posted @ 2024-03-23 00:59  zsc985246  阅读(360)  评论(1编辑  收藏  举报