比赛记录:Codeforces Round 936 (Div. 2) A~E

传送门:CF

[前题提要]:赛时一小时过了A~D,E感觉也不是很难(甚至感觉思维难度是小于D的),感觉这回是自己不够自信了,要是自信一点深入想一下应该也能做出来,咱就是说,如果E和D换一下,结果也是一样的,虽上大分,但是心里很不服,故记录一下


A - Median of an Array

当时网卡加载了5min,去了副站才看到题,真是醉了.

对于本题,观察一下,什么时候我们给中位数改变了之后,中位数依旧不变.不难发现应该是原本处于中位数右边的数此时被挤到了现在中位数的位置.那么本题的答案显然就是中位数右边和中位数相等的数的个数.

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
int main() {
	int T=read();
	while(T--) {
		int n=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();
		}
		sort(a+1,a+n+1);
		int num=a[(n+1)/2];
		int cnt=0;
		for(int i=(n+1)/2;i<=n;i++) {
			if(a[i]==num) {
				cnt++;
			}
		}
		cout<<cnt<<endl;
	}
	return 0;
}

B - Maximum Sum

很显然我们每一次操作应该选最大的一个字段和.那么此时问题就变成了求最大子段和的问题 .对于如何求最大子段和,是个简单的dp问题,此处就不在赘述了.

对于我们选择的最大子段和 s u m sum sum,我们刚开始对其进行一次操作,显然我们的总和多了 s u m sum sum的贡献,我们再操作一次,多了 s u m ∗ 2 sum*2 sum∗2的贡献,不难发现最后显然多了 2 k ∗ s u m − s u m 2^k*sum-sum 2k∗sum−sum的贡献,直接求即可.

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const int mod=1e9+7;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
signed main() {
	int T=read();
	while(T--) {
		int n=read();int k=read();
		int sum1=0;
		for(int i=1;i<=n;i++) {
			a[i]=read();
			sum1+=a[i];sum1=sum1%mod;
		}
		int ans=-int_INF;int sum=0;
		for(int i=1;i<=n;i++) {
			sum+=a[i];
			if(sum<0) {
				ans=max(ans,sum);
				sum=0;
			}
			else {
				ans=max(ans,sum);
			}
		}
		if(ans<0) {
			cout<<(sum1%mod+mod)%mod<<endl;
		}
		else {
			int ANS=1;
			for(int i=1;i<=k;i++) {
				ANS=ANS*2%mod;
			}
			ans=(ans%mod+mod)%mod;
			cout<<((ANS*ans%mod+sum1-ans)%mod+mod)%mod<<endl;
		}
		
	}
	return 0;
}

C - Tree Cutting

典题.这种二分答案+贪心切割的 t r i c k trick trick经常出现.当时看到直接秒了.

不难发现答案满足单调性,因为我们切割的次数越多,就会导致我们每一块的大小越小,那么我们二分出来的答案

越小就越容易满足我们的限制,答案越大就越不满足.

所以我们进行二分答案.对于我们二分出来的答案 m i d mid mid,我们不难想到从下往上进行切割,此时应该有一个贪心的想法,就是一旦当前子树的大小大于我们的 m i d mid mid,我们就把它切割了.然后以此来满足我们的最大切割数.

现在来想一下这样做的正确性.考虑这样的一个子树 u u u,它有着 v 1 , v 2 , v 3 . . . v n v_1,v_2,v_3...v_n v1,v2,v3...vn,不妨假设此时的 u u u是从下往上第一个满足子树大小大于等于 m i d mid mid的情况.我们会发现此时我们只能删掉u和它的父亲这条连边才能使我们的贡献+1,对于删掉u子树的任意一条边都是不满足我们的情况的(因为这种删法都会导致一个小于mid的子树出现 ).此时来想一下如果我们不删掉u是否更优.如果我们此时不删掉u,意味着u要和它的某个祖先一起删除,但是此时u自己就满足了删除情况,所以我们为什么要分配u更多的节点呢,我们为什么不把这部分多余的节点给其他部分呢?

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
vector<int>edge[maxn];int n,k;
int Size[maxn];int cnt;
void dfs(int u,int pre_u,int mid) {
	Size[u]=1;
	for(auto v:edge[u]) {
		if(v==pre_u) continue;
		dfs(v,u,mid);
		Size[u]+=Size[v];
	}
	if(cnt) {
		if(Size[u]>=mid) {
			cnt--;
			Size[u]=0;
		}
	}
}
int check(int mid) {
	cnt=k;
	dfs(1,0,mid);
	return cnt<=0&&Size[1]>=mid;
}
int main() {
	int T=read();
	while(T--) {
		n=read();k=read();
		for(int i=1;i<=n-1;i++) {
			int u=read();int v=read();
			edge[u].push_back(v);
			edge[v].push_back(u);
		}
		int l=1,r=n;int ans=1;
		while(l<=r) {
			int mid=(l+r)>>1;
			if(check(mid)) {
				ans=mid;l=mid+1;
			}
			else {
				r=mid-1;
			}
		}
		cout<<ans<<endl;
		for(int i=1;i<=n;i++) {
			Size[i]=0;edge[i].clear();
		}
	}
	return 0;
}

D - Birthday Gift

对于这种位运算题,最一般的想法应该是逐位考虑.

考虑对所有数字进行拆分.

考虑如果x的某一位是0,那么显然我们每一段的异或结果该位都应该是0,那么贪心的去想,如果某个数字该位是0,我们应该尽量的将其自行当做一块,否则我们需要再找一个该位是1的数字和他放在一块.方便起见,可以使用并查集来完成这一部分.当然,如果一个数字找不到和他一起的数字,此时显然是不行的,之后也就没必要讨论下去了.

cpp 复制代码
for(int j=1;j<=n;j++) {
	if(v[j][i]) {
		int l=j;
	while(j+1<=n&&v[j+1][i]==0) {
		j++;	
		fa[find(j)]=find(l);
	}
	if(j+1>n) {
		flag=1;break;
	}
	else {
		fa[find(j+1)]=find(l);
		j++;
	}
}

考虑如果x的某一位是1,那么此时我们有两种情况,我们可以使当前位是0,或者是1.

如果当前位我们使其变为0,那么后面所有位我们都将可以随便取,也就是后面的情况将不会影响我们的分块,所以此时的情况就是我们答案的一种情况 ,那么此时怎么分配才最优呢,不难发现其实我们在x的某一位是0的时候已经考虑过了,那时的分配方案就是最优的 .

如果当前位我们还是选择将其变为1,那么只要所有数字当前位存在1即可,我们此时的最优分配方案就是不进行任何操作 .此时细心的同学可能存在疑问,"因为存在之前位置的影响,或者当前位根本没有1,我们此时可能不存在使当前位变为1的情况".对于上述这种情况,我们发现它其实是我们将其选择变为0的一种特殊情况,所以此时的答案其实是已经考虑进去了,所以虽然此时情况不存在,但是后续必然是没有直接将其变为0优秀的,因为我们把其当做1,后续会增加更多的限制,所以我们可以将其忽略.

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
vector<int>v[maxn];int fa[maxn];int temp[maxn];
int find(int x) {
	if(fa[x]==x) return fa[x];
	else return fa[x]=find(fa[x]);
}
signed main() {
	int T=read();
	while(T--) {
		int n=read();int x=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();fa[i]=i;
		}
		for(int i=1;i<=n;i++) {
			for(int j=0;j<=30;j++) {
				if(a[i]&(1<<j)) {
					v[i].push_back(1);
				}
				else {
					v[i].push_back(0);
				}
			}
		}
		vector<int>vx;
		for(int j=0;j<=30;j++) {
			if(x&(1<<j)) vx.push_back(1);
			else vx.push_back(0);
		}
		int flag=0;int kkk=0;int ANS=-int_INF;
		for(int i=30;i>=0;i--) {
			if(vx[i]==0) {
				int cnt=0;
				for(int j=1;j<=n;j++) {
					if(v[j][i]) {
						int l=j;
						while(j+1<=n&&v[j+1][i]==0) {
							j++;	
							fa[find(j)]=find(l);
						}
						if(j+1>n) {
							flag=1;break;
						}
						else {
							fa[find(j+1)]=find(l);
							j++;
						}
					}
				}
			}
			else {
				for(int j=1;j<=n;j++) {
					temp[j]=fa[j];
				}
				int cnt=0;int kkk=0;
				for(int j=1;j<=n;j++) {
					if(v[j][i]) {
						int l=j;
						int flag=0;
						while(j+1<=n&&v[j+1][i]==0) {
							j++;	
							fa[find(j)]=find(l);
						}
						if(j+1>n) {
							kkk=1;break;
						}
						else {
							fa[find(j+1)]=find(l);
							j++;
						}
					}
				}
				if(kkk==0) {
					int ans=0;
					for(int i=1;i<=n;i++) {
						if(find(i)==i) {
							ans++;
						}
					}
					ANS=max(ANS,ans);
				}
				for(int j=1;j<=n;j++) {
					fa[j]=temp[j];
				}
			}
			if(flag) {
				break;
			}
		}
		if(flag) {
			if(ANS!=-int_INF) cout<<ANS<<endl;
			else cout<<-1<<endl;
		} 
		else {
			int ans=0;
			for(int i=1;i<=n;i++) {
				if(find(i)==i) {
					ans++;
				}
			}
			ANS=max(ANS,ans);
			cout<<ANS<<endl;		
		}
		//clear
		for(int i=1;i<=n;i++) {
			v[i].clear();
		}
		vx.clear();
	}
	return 0;
}

E - Girl Permutation

令我意难平的一道题.感觉当时只差一点点深入思考就可以想出来了,但是当时心里想它是一道E题而放弃了(哭.

首先不难发现序列最大值的位置是固定的,显然是 p [ m 1 ] p[m1] p[m1],或者是 s [ 1 ] s[1] s[1]位置.显然我们应该左右分开来考虑,那么对于任意一遍,我们先进行一波选数操作,此时贡献多了个 C ( n , p [ m 1 ] − 1 ) C(n,p[m1]-1) C(n,p[m1]−1).

考虑左边,我当时赛时的想法是直接再来波 C ( p [ m 1 ] − 1 , m 1 − 1 ) C(p[m1]-1,m1-1) C(p[m1]−1,m1−1)但是这种想法显然是错的.虽然我们任意取 m 1 − 1 m1-1 m1−1个数字,此时m1-1个数字的顺序是固定的,但是此时会多很多种情况,就比如可能会存在1 2 3这种选择,但是此时我们的1不在最开始的位置,但是因为是前缀最大值,所以1的左边的数字必须都比1要小,但是显然是不存在比1小的数字的,所以此时这种情况是错误的 .那么我们该怎么避免上述这种情况呢.其实我们可以逐位进行考虑.

显然此时7的位置的数只能选最大值.并且此时我们会发现6这个位置的数字需要比5和7要小,并且1~4的数字也要比5要小,这一点十分关键.这意味着,对于剩下来的6个数字,我们必须将最大的那个数字分配给5,然后6这个位置是可以在剩下的5个数中随便选一个数的 .上面那句话是本题的关键,需细细理解.考虑完5和6之后,我们再来看一下3,4的位置该如何取数,类似的,我们不难会发现2这个位置此时应该填剩下的4个数中的最大值,然后3和4在2填完之后可以随便填.诶,我们会发现这种填数方式似乎存在的某种有趣的规律.

我们会发现我们当前的前缀最大值其实是除它之后的那个前缀最大值之外的最大值,所以这个数的取数的固定的,并且在这个数取完之后,他之后的区间中的数字是可以任意取的

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const int mod=1e9+7;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int qpow(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1) ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
int fac[maxn],in_fac[maxn];
void init(int limit=2e5) {
	fac[0]=1;
	for(int i=1;i<=limit;i++) {
		fac[i]=fac[i-1]*i%mod;
	}
	in_fac[limit]=qpow(fac[limit],mod-2);
	for(int i=limit-1;i>=0;i--) {
		in_fac[i]=in_fac[i+1]*(i+1)%mod;
	}
}
int C(int a,int b) {
	return fac[a]*in_fac[b]%mod*in_fac[a-b]%mod;
}
int A(int a,int b) {
	return fac[a]*in_fac[a-b]%mod;
}
int p[maxn],s[maxn];
signed main() {
	init();
	int T=read();
	while(T--) {
		int n=read();int m1=read();int m2=read();
		for(int i=1;i<=m1;i++) {
			p[i]=read();
		}
		for(int i=1;i<=m2;i++) {
			s[i]=read();
		}
		if(p[m1]!=s[1]||p[1]!=1||s[m2]!=n) {
			cout<<0<<endl;
			continue;
		}
		int ans=1;int num=p[m1]-1;
		for(int i=m1-1;i>=1;i--) {
			ans=ans*A(num-1,p[i+1]-p[i]-1)%mod;
			num=p[i]-1;
		}
		num=n-s[1];
		for(int i=2;i<=m2;i++) {
			ans=ans*A(num-1,s[i]-s[i-1]-1)%mod;
			num=n-s[i];
		}
		cout<<ans*C(n-1,p[m1]-1)%mod<<endl;
	}
	return 0;
}
相关推荐
爱吃生蚝的于勒1 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
小白学大数据3 小时前
Python爬虫开发中的分析与方案制定
开发语言·c++·爬虫·python
versatile_zpc6 小时前
C++初阶:类和对象(上)
开发语言·c++
小鱼仙官6 小时前
MFC IDC_STATIC控件嵌入一个DIALOG界面
c++·mfc
神仙别闹6 小时前
基本MFC类框架的俄罗斯方块游戏
c++·游戏·mfc
ChoSeitaku6 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
娅娅梨6 小时前
C++ 错题本--not found for architecture x86_64 问题
开发语言·c++
兵哥工控6 小时前
MFC工控项目实例二十九主对话框调用子对话框设定参数值
c++·mfc
Fuxiao___7 小时前
不使用递归的决策树生成算法
算法
我爱工作&工作love我7 小时前
1435:【例题3】曲线 一本通 代替三分
c++·算法