前言
哭了啊,又被同机房那几个奆佬摁在地上摩擦,平均比每人少做一道题目。
比赛链接: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
");
else printf("No
");
}
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
",ans/shit);
}
return 0;
}
D
题意: 给你长度为(2n)的数组,将其分成两个数组(q,p),然后(q)从大到小排序,(p)从小到大排序,这次拆分的价值为:(sumlimits_{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
",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
",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)讲完口胡的。。。