1. 程式人生 > 實用技巧 >CF#680 Div.2賽後總結

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\)講完口胡的。。。