CF#680 Div.2赛后总结

前言

哭了啊,又被同机房那几个奆佬摁在地上摩擦,平均比每人少做一道题目。

比赛链接:https://codeforc.es/contest/1445

A

题意:给你两个数组\(a,b\),让你判断能不能通过对\(b\)重新的排序,让其满足:\(a_{i}+b_{i}≤k(1≤i≤n)\),其中\(k\)是给定的常数。

做法:不难发现,\(a\)升序,\(b\)降序,然后暴力做即可。

时间复杂度:\(O(nlogn)\)

#include<cstdio>
#include<cstring>
#define  N  110
using  namespace  std;
int  a[N],b[N],n,k;
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		for(int  i=1;i<=n;i++)scanf("%d",&a[i]);
		for(int  i=1;i<=n;i++)scanf("%d",&b[i]);
		bool  bk=0;
		for(int  i=1;i<=n;i++)
		{
			if(a[i]+b[n-i+1]>k)
			{
				bk=1;
				break;
			}
		}
		if(!bk)printf("Yes\n");
		else  printf("No\n");
	}
	return  0;
}

B

题意: 一场比赛有不知道多少人(>100)个人参与,在淘汰赛有两场比赛,两场比赛的分数和总分数都按按降序排序,如果有同分按字典序排序,然后现在两场的分数排名给出来,但是不知道具体的分数,问你最后总排名第\(100\)名的分数最少能是多少。

当然,两场比赛的分数还是知道一点信息的,第一场比赛第\(100\)名是\(a\)分,然后前\(100\)名在第二场比赛至少得了\(b(b≤c)\)分,第二场比赛第\(100\)名是\(c\)分,前\(100\)名在第一场比赛至少得了\(d(d≤a)\)分。

做法:艹,题意就看了半天,贪心:首先为了第\(100\)名分数最小,肯定第一场比赛前\(100\)名都拿\(a\)分,然后这些人在第二场比赛都拿了\(b\)分,第二场比赛前\(100\)名都拿\(c\)分,在第一场比赛都拿\(d\)分能保证第\(100\)名分数最小。

时间复杂度:\(O(1)\)

C

题意:给你\(q,p\),求最大的整数\(x\),满足\(x\)整数\(1\)但没有被\(p\)整除。

做法:把\(p\)质因数分解为:\(a_{1}^{b_{1}}a_2^{b_2}...a_{k}^{b_{k}}\),然后设\(x=q\),只要\(x\)中含任意一个\(a_{i}\)因子个数的数量小于\(b_{i}\)即可,用一个变量记录让哪个因子小于\(b_{i}\)的代价最小,最后除一下就行了。

时间复杂度:\(O(很小)\)

#include<cstdio>
#include<cstring>
#include<cmath>
using  namespace  std;
typedef  long  long  LL;
inline  LL  ksm(LL  x,LL  y)
{
	LL  ans=1;
	for(LL  i=1;i<=y;i++)ans*=x;
	return  ans;
}
inline  LL  mymin(LL  x,LL  y){return  x<y?x:y;}
int  main()
{
//	freopen("std.in","r",stdin);
//	freopen("vio.out","w",stdout);
	int  T;
	scanf("%d",&T);
	while(T--)
	{
		LL  x,y;
		scanf("%lld%lld",&x,&y);
		LL  ed=sqrt(y)+1;
		
		LL  ans=x;
		LL  shit=(LL)999999999999999999;//赛后才发现这个地方少打了几个9
		for(LL  i=2;i<=ed;i++)
		{
			if(y%i==0)
			{
				LL  cnt=0;
				while(y%i==0)y/=i,cnt++;
				LL  pre=0;
				while(x%i==0)x/=i,pre++;
				if(pre<cnt)
				{
					shit=1;
					break;
				}
				else  shit=mymin(ksm(i,pre-cnt+1),shit);
			}
		}
		if(y>1  &&  shit>1)
		{
			LL  pre=0;
			while(x%y==0)x/=y,pre++;
			if(pre<1)shit=1;
			else  shit=mymin(ksm(y,pre),shit);
		}
		printf("%lld\n",ans/shit);
	}
	return  0;
}

D

题意: 给你长度为\(2n\)的数组,将其分成两个数组\(q,p\),然后\(q\)从大到小排序,\(p\)从小到大排序,这次拆分的价值为:\(\sum\limits_{i=1}^{n}|q_{i}-p_{i}|\)

然后问你所有拆分的价值,模\(998244353\)

做法:额,首先,如果所有数字不同,不难发现,不管你怎么分,最大的那\(n\)个数字刚好分在\(q,p\)大的那一端,无法互相减去,所以不管你怎么分,价值都等于最大的\(n\)个数字减最小的\(n\)个数字,那万一数字相同呢?我们认为同样的数字,在原数组中所处下标越小,其就越小,且在排序的时候比较大小也这么认为,那么一样可以得到同样的结论,然后只需要乘上拆分的个数即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  310000
using  namespace  std;
typedef  long  long  LL;
LL  mod=998244353;
inline  LL  ksm(LL  x,LL  y)
{
	LL  ans=1;
	while(y)
	{
		if(y&1)ans=(ans*x)%mod;
		x=(x*x)%mod;y>>=1;
	}
	return  ans;
}
LL  a[N],fc[N],sum;
int  n; 
int  main()
{
	scanf("%d",&n);
	int  ed=n*2;
	for(int  i=1;i<=ed;i++)scanf("%d",&a[i]);
	fc[0]=1;for(int  i=1;i<=ed;i++)fc[i]=(LL)(fc[i-1]*i)%mod;
	sort(a+1,a+ed+1);
	for(int  i=1;i<=n;i++)sum+=a[n+i]-a[i];
	sum%=mod;
	LL  shit=ksm(fc[n],mod-2);
	printf("%lld\n",sum*shit%mod*shit%mod*fc[ed]%mod);
	return  0;
}

E

题意: 给你\(n\)个点,\(m\)条边,然后每个点都属于一个学术团队,有\(k\)个学术团队,求满足要求的二元组\((i,j)\),要求为:\(i<j\),且第\(i\)个团队和第\(j\)个团队形成的诱导子图为二分图。

做法:
做法1: 先默认所有二元组都可以,找不可以的,只要用并查集先处理出每个团队自己的,然后再针对每个团队和其他团队的即可。

然后再两个团队判断完之后记得还原并查集,当然,需要注意的时,并查集不能路径压缩,不然还原并查集的时间复杂度就不是\(O(m)\)的了,只能按秩合并。

时间复杂度:\(O(mlogn)\)

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define  N  510000
using  namespace  std;
typedef  long  long  LL;
int  fa[N],val[N],siz[N],zanval;
int  findfa(int  x)
{
	zanval=0;
	int  y=x;
	while(fa[y]!=y)zanval^=val[y],y=fa[y];
	return  y;
}
inline  bool  mer(int  x,int  y,int  type)//合并
{
	int  tx=findfa(x);type^=zanval;
	int  ty=findfa(y);type^=zanval;
	if(tx==ty)
	{
		if(type==1)return  0;
	}
	else
	{
		if(siz[tx]>siz[ty])fa[ty]=tx,siz[tx]+=siz[ty],val[ty]=type;
		else  fa[tx]=ty,siz[ty]+=siz[tx],val[tx]=type;
	}
	return  1;
}
struct  node
{
	int  id;
	int  x,y;
	node(int  idx=0,int  xx=0,int  yx=0){id=idx;x=xx;y=yx;}
};
vector<node> fuck[N];//每个团队向其余团队的边
node  sta[N];int  top;
int  n,m,k,be[N];
bool  shit[N]/*判断一个联通块本身自己是不是二分图*/;int  shitcnt;//合法的团队
bool  tis[N];
int  pre[N],pre_top;
inline  void  check(int  x)//判断这个点的祖先完事之后是否需要还原
{
	x=findfa(x);
	if(!tis[x])
	{
		tis[x]=1;
		pre[++pre_top]=x;
	}
}
inline  bool  solve(int  l,int  r)//判断两个团队是否是二分图
{
	pre_top=0;
	for(int  i=l;i<=r;i++)
	{
		int  x=sta[i].x,y=sta[i].y;
		check(x);check(y);
		if(!mer(x,y,1))return  1;
	}
	return  0;
}
inline  void  put_hui()//恢复并查集
{
	for(int  i=1;i<=pre_top;i++)fa[pre[i]]=pre[i],val[pre[i]]=0,tis[pre[i]]=0;
}
inline  bool  cmp(node  x,node  y){return  x.id<y.id;}
int  main()
{
	scanf("%d%d%d",&n,&m,&k);
	shitcnt=k;
	for(int  i=1;i<=n;i++){scanf("%d",&be[i]);fa[i]=i;siz[i]=1;}
	for(int  i=1;i<=m;i++)
	{
		int  x,y;scanf("%d%d",&x,&y);
		if(be[x]==be[y])
		{
			if(!shit[be[x]])
			{
				if(!mer(x,y,1))
				{
					shit[be[x]]=1;
					shitcnt--;
				}
			}
		}
		else
		{
			fuck[be[x]].push_back(node(be[y],x,y));
			fuck[be[y]].push_back(node(be[x],y,x));
		}
	}
	LL  ans=(LL)shitcnt*(shitcnt-1)/2;
	LL  fei=0;
	for(int  i=1;i<=k;i++)
	{
		if(shit[i])continue;
		top=fuck[i].size();
		for(int  j=0;j<top;j++)sta[j+1]=fuck[i][j];
		sort(sta+1,sta+top+1,cmp);//使得id非严格单调递增
		int  tmp=sta[1].id,tmppre=1;
		for(int  j=1;j<=top;j++)
		{
			if(sta[j].id!=tmp)
			{
				if(!shit[tmp])
				{
					fei+=solve(tmppre,j-1);
					put_hui();
				}
				tmppre=j;
				tmp=sta[j].id;
			}
		}
		if(top  &&  !shit[sta[top].id])
		{
			fei+=solve(tmppre,top);
			put_hui();
		}
	}
	printf("%lld\n",ans-fei/2/*别忘了除2*/);
	return  0;
}

做法2:先Orz 一波ZWQ,其会\(O(n+m)\)的做法,首先,对每个团队跑一遍二分图染色,处理出这个团队必须对立的两个部分,例如\(1,3\)\(2,4\)必须对立,那么\(1,3\)缩成一个点\(a\)\(2,4\)缩成一个点\(b\)\(a,b\)连边(当然,为了保证时间复杂度是正确的,建议用\(match\)数组记录这条边,防止用边目录),需要注意的是,团队里可能不止一个必须对立的两个部分。然后如果\(1,3\)缩成了\(a\)点,那么其连边都变成\(a\)点连的边,最后只需要判断两个集合之间是否存在奇数环即可(二分图染色),当然,稍微注意一些细节:两个团队之间的边用\(vector\)存,只对两个团队之间边的端点跑二分图等等。

这样,就能保证时间复杂度:\(O(n+m)\)

当然,我是听\(ZWQ\)讲完口胡的。。。

posted @ 2020-11-02 17:02  敌敌畏58  阅读(174)  评论(0编辑  收藏  举报