【CodeForces】CodeForcesRound576 Div1 解题报告
\(A\):MP3(点此看题面)
大致题意: 让你选择一个值域区间\([L,R]\),使得序列中满足\(L\le a_i\le R\)的数的种类数不超过\(2^{\lfloor\frac {8I}n\rfloor}\),输出剩余数的数量的最小值。
对着英文题面懵了半天。。。
理解题意之后这题就很水了。
考虑我们先排序+离散化,然后选择的数必然是离散化序列上的一段长度为\(2^{\lfloor\frac {8I}n\rfloor}\)的区间。
那么我们枚举区间左端点,双指针维护区间右端点,就可以求出答案了。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 400000
#define INF 1e9
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m,t,a[N+5],s[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
int main()
{
RI i,p1,p2,ans=INF;for(F.read(n,m),i=1;i<=n;++i) F.read(a[i]);//读入
for(sort(a+1,a+n+1),i=1;i<=n;++i) s[i]=a[i];t=unique(s+1,s+n+1)-s-1;//排序+离散化
if(m=8*m/n,m>=30) return puts("0"),0;if(m=(1LL<<m),m>t) return puts("0"),0;//若能选择的区间长度超过总长度,直接输出0
for(p1=p2=i=1;i+m-1<=t;++i)//枚举左端点
{
W(p1^n&&a[p1]<s[i]) ++p1;W(p2^n&&a[p2+1]<=s[i+m-1]) ++p2;//双指针维护原序列上的区间
Gmin(ans,(p1-1)+(n-p2));//更新答案
}return printf("%d",ans),0;//输出答案
}
\(B\):Welfare State(点此看题面)
大致题意: 支持两种操作,单点修改,全序列对一个数取\(Max\),求最后的序列。
我写了一个线段树。
我们用\(Mx_{rt}\)表明\(rt\)子树内所有数都要向它取\(Max\),然后当要对\(rt\)子树内的值进行单点修改时,就把\(Mx_{rt}\)下传给左右儿子。
最后每个位置的答案就是其在线段树中对应的叶节点的值与其到根路径上所有\(Mx_{rt}\)的最大值。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
using namespace std;
int n,a[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
template<int SZ> class SegmentTree//线段树
{
private:
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
#define PD(x) (U(x<<1,Mx[x]),U(x<<1|1,Mx[x]),Mx[x]=0)
#define U(x,v) (Mx[x]<(v)&&(Mx[x]=v))
int n,V[SZ+5],Mx[SZ<<2];
I void Upt(CI x,CI y,CI l,CI r,CI rt)//单点修改
{
if(l==r) return (void)(Mx[rt]=0);RI mid=l+r>>1;PD(rt),
x<=mid?Upt(x,y,LT):Upt(x,y,RT);
}
I int Qry(CI x,CI l,CI r,CI rt)//单点查询
{
if(l==r) return max(V[l],Mx[rt]);RI mid=l+r>>1,t;
return t=x<=mid?Qry(x,LT):Qry(x,RT),max(t,Mx[rt]);
}
public:
I void Build(CI _n,int *a) {n=_n;for(RI i=1;i<=n;++i) V[i]=a[i];}
I void Upt(CI x,CI y) {V[x]=y,Upt(x,y,1,n,1);}I void Max(CI x) {U(1,x);}
I int Qry(CI x) {return Qry(x,1,n,1);}
};SegmentTree<N> S;
int main()
{
RI Qt,i,op,x,y;for(F.read(n),i=1;i<=n;++i) F.read(a[i]);S.Build(n,a);//初始化
F.read(Qt);W(Qt--) F.read(op,x),op==1?(F.read(y),S.Upt(x,y)):S.Max(x);//处理操作
for(i=1;i<=n;++i) F.write(S.Qry(i)," \n"[i==n]);return F.clear(),0;//输出序列
}
\(C\):Matching vs Independent Set(点此看题面)
大致题意: 给你一张\(3n\)个点和\(m\)条边的图,求一个\(n\)条边的匹配或\(n\)个点的独立集。
这题的做法很巧妙啊。
考虑我们先对每条边扫一遍,只要它的两个端点都未曾作为某个匹配的端点,就把这条边作为一个匹配。
然后,如果作为匹配的边数大于等于\(n\),就直接输出其中的\(n\)条边。
否则,作为匹配的点数显然小于\(2n\),也就是说,剩下的点数大于\(n\)。而剩下的点两两之间,显然都没有边相连,否则就会被抓去作为匹配。所以输出其中\(n\)个点即可。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 500000
using namespace std;
int n,m,s[3*N+5],ans[M+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
I void writes(Con string& s) {for(RI i=0,l=s.length();i^l;++i) pc(s[i]);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
int main()
{
RI Tt,i,t,x,y;F.read(Tt);W(Tt--)
{
for(F.read(n,m),i=1;i<=3*n;++i) s[i]=0;//清空
for(t=0,i=1;i<=m;++i) F.read(x,y),!s[x]&&!s[y]&&(s[x]=s[y]=1,ans[++t]=i);//若能作为匹配就作为匹配
if(t>=n) for(F.writes("Matching\n"),i=1;i<=n;++i) F.write(ans[i]," \n"[i==n]);//若存在匹配
else for(F.writes("IndSet\n"),t=0,i=1;i<=3*n&&t^n;++i) !s[i]&&(F.write(i," \n"[++t==n]),0);//否则存在独立集
}return F.clear(),0;
}
\(D\):Rectangle Painting 1(点此看题面)
大致题意: 给你一个方阵,其中有一些格子被染黑,每次可以选择一个\(w*h\)的区间以\(max(w,h)\)的代价将其染黑,求最小代价将整个方阵染白。
\(n\)很小,想到暴力\(DP\)。
设\(f_{xx,yx,xy,yy}\)表示将以\((xx,yx)\)为左上角、\((xy,yy)\)为右下角的区间全染白的最小代价。
可以以\(dfs\)形式转移,每次枚举一行或一列,将这个区间分裂转移。
由于有记忆化,跑满也只是\(O(n^5)\)的。而且其中有些区间是不合法的,并且当一个区间全白或全黑的时候我们可以直接返回答案,所以跑不满,是能过的。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50
using namespace std;
int n,a[N+5][N+5],s[N+5][N+5],f[N+5][N+5][N+5][N+5];
I int DP(CI xx,CI yx,CI xy,CI yy)//求将当前区间染白的最小代价
{
if(f[xx][yx][xy][yy]) return f[xx][yx][xy][yy];RI i,t,res=max(xy-xx+1,yy-yx+1);//如果访问过
if(t=s[xy][yy]-s[xx-1][yy]-s[xy][yx-1]+s[xx-1][yx-1],!t) return 0;if(t==(xy-xx+1)*(yy-yx+1)) return res;//如果全白或全黑
for(i=xx;i^xy;++i) t=DP(xx,yx,i,yy)+DP(i+1,yx,xy,yy),res>t&&(res=t);//枚举一行分裂
for(i=yx;i^yy;++i) t=DP(xx,yx,xy,i)+DP(xx,i+1,xy,yy),res>t&&(res=t);return f[xx][yx][xy][yy]=res;//枚举一列分裂
}
int main()
{
RI i,j;string st;for(scanf("%d",&n),i=1;i<=n;++i)
for(cin>>st,j=1;j<=n;++j) a[i][j]=st[j-1]=='#',
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];//预处理二维前缀和
return printf("%d",DP(1,1,n,n)),0;
}
\(E\):Rectangle Painting 2(点此看题面)
大致题意: 给你一个方阵,其中有一些区间被染黑,每次可以选择一个\(w*h\)的区间以\(min(w,h)\)的代价将其染黑,求最小代价将整个方阵染白。
由于是\(min(w,h)\),所以贪心考虑,我们每次必然染白一行或者一列。
这就变成了一个经典的二分图匹配问题。
我们对于黑色格子\((i,j)\),由第\(i\)行向第\(j\)列连一条容量为\(INF\)的边。
由超级源向每行连边,由每列向超级汇连边,然后跑最大流即可。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define M 50
#define INF 2e9
using namespace std;
int n,m,xx[M+5],xy[M+5],yx[M+5],yy[M+5],v[2*M+5][2*M+5];
class Discretization//离散化
{
private:
int s[2*M+5];
public:
int n;I void Ins(CI x) {s[++n]=x;}I void Init() {sort(s+1,s+n+1),n=unique(s+1,s+n+1)-s-1;}
I int GV(CI x) {return lower_bound(s+1,s+n+1,x)-s;}I int GF(CI x) {return s[x];}
}Dx,Dy;
template<int PS,int ES> class Dinic//最大流
{
private:
#define Else(x) ((((x)-1)^1)+1)
#define add(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
int ee,lnk[PS+5],cur[PS+5],q[PS+5],dep[PS+5];struct edge {int to,nxt,F;}e[2*ES+5];
I bool BFS(CI s,CI t)//BFS找增广路
{
RI i,k,H=1,T=0;for(i=1;i<=Dx.n+Dy.n+2;++i) dep[i]=0;dep[q[++T]=s]=1;
W(H<=T&&!dep[t]) for(i=lnk[k=q[H++]];i;i=e[i].nxt)
e[i].F&&!dep[e[i].to]&&(dep[q[++T]=e[i].to]=dep[k]+1);
return dep[t];
}
I int DFS(CI x,RI f)//DFS求最大流
{
if(x==T||!f) return f;RI& i=cur[x];RI t,res=0;for(;i;i=e[i].nxt)
{
if((dep[x]+1)^dep[e[i].to]||!e[i].F||!(t=DFS(e[i].to,min(f,e[i].F)))) continue;
if(e[i].F-=t,e[Else(i)].F+=t,res+=t,!(f-=t)) return res;
}return dep[x]=-1,res;
}
public:
int S,T;I Dinic() {S=1,T=2;}I int P1(CI x) {return x+2;}I int P2(CI x) {return x+Dx.n+2;}
I void Add(CI x,CI y,CI f) {add(x,y,f),add(y,x,0);}//连边
I void Solve()
{
RI i,f=0;W(BFS(S,T)) {for(i=1;i<=Dx.n+Dy.n+2;++i) cur[i]=lnk[i];f+=DFS(S,INF);}//最大流
printf("%d",f);//输出答案
}
};Dinic<4*M+2,4*M+4*M*M> D;
int main()
{
RI i,j,k;scanf("%d%d",&n,&m);
for(i=1;i<=m;++i) scanf("%d%d%d%d",xx+i,yx+i,xy+i,yy+i),//读入
Dx.Ins(xx[i]),Dx.Ins(++xy[i]),Dy.Ins(yx[i]),Dy.Ins(++yy[i]);
for(Dx.Ins(n+1),Dy.Ins(n+1),Dx.Init(),Dy.Init(),i=1;i<=m;++i)//对于每个黑色区间
{
xx[i]=Dx.GV(xx[i]),yx[i]=Dy.GV(yx[i]),xy[i]=Dx.GV(xy[i])-1,yy[i]=Dy.GV(yy[i])-1;//求出离散化后的值
for(j=xx[i];j<=xy[i];++j) for(k=yx[i];k<=yy[i];++k) ++v[j][k];//每行向每列连边
}
for(i=1;i^Dx.n;++i) for(j=1;j^Dy.n;++j) v[i][j]&&(D.Add(D.P1(i),D.P2(j),INF),0);//连边
for(i=1;i^Dx.n;++i) D.Add(D.S,D.P1(i),Dx.GF(i+1)-Dx.GF(i));
for(i=1;i^Dy.n;++i) D.Add(D.P2(i),D.T,Dy.GF(i+1)-Dy.GF(i));return D.Solve(),0;
}
\(F\):GCD Groups 2(点此看题面)
大致题意: 让你把一组数分成两组,使每组\(gcd\)都为\(1\)。
随机+卡时大法好。
我们每次随机一个排列,然后枚举每一个数。贪心地想,若这个数加到第一组内,能使第一组的\(gcd\)减少,我们就把这个数加到第一组中,否则加到第二组中。
最后看下是否两组\(gcd\)都为\(1\),若为\(1\),就可以直接输出当前方案了。
如果直到\(0.49\)秒都没有得出答案,就可以输出无解了。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,a[N+5],s[N+5],p[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
I int gcd(CI x,CI y) {return y?gcd(y,x%y):x;}
int main()
{
RI i,t,g1,g2;for(F.read(n),i=1;i<=n;++i) F.read(a[i]),s[i]=i;
W(1.0*clock()/CLOCKS_PER_SEC<=0.49)//卡时
{
random_shuffle(s+1,s+n+1),g1=a[s[1]],p[s[1]]=1,g2=a[s[2]],p[s[2]]=0;//随机排列
for(i=3;i<=n;++i) (t=gcd(g1,a[s[i]]))<g1?(g1=t,p[s[i]]=1):(g2=gcd(g2,a[s[i]]),p[s[i]]=0);//贪心分组
if(g1==1&&g2==1) {for(puts("YES"),i=1;i<=n;++i) F.write(p[i]+1,' ');return F.clear(),0;}//若合法直接输出
}return puts("NO"),0;//输出无解
}