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\)
時間複雜度:\(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\)講完口胡的。。。