Codeforces Round 936 (Div. 2) 题解
本文网址:https://www.cnblogs.com/zsc985246/p/18090655 ,转载请注明出处。
打完比赛火速出题解。
AB 罚时及其致命。最终排名 100。
题目风格清新,整体体验不错,推荐。
F 题解请等待后续更新。
传送门
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]\):
-
\(l_1=1,r_k=n\)。
-
\(\forall i \in [2,n], r_{i-1}=l_i\)。
-
\(l_i \le r_i\)。
-
\((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.
题目大意
思路
代码实现
尾声
如果有什么问题,可以直接评论!