• 【AtCoder】AtCoder Grand Contest 035 解题报告


    点此进入比赛

    (A):XOR Circle(点此看题面

    大致题意: 给你(n)个数,问是否能将它们摆成一个环,使得环上每个位置都是其相邻两个位置上值的异或值。

    不考虑(0),我们假设环上第一个数是(a_1),第二个数是(a_2),则第三个数(a_3=a_1 xor a_2),第四个数(a_4=a_2 xor a_3=a_1)。。。

    以此类推,也就是说,一个合法的环上必然是(a_1,a_2,a_1 xor a_2)三者重复。

    整理一下,就是要满足:

    • 总共只有三种数,记其为(x,y,z)
    • 每种数出现次数一样。
    • (x xor y=z)

    然后我们要特殊考虑(0)

    首先,(n)(0)必然合法。

    其次,若只有(0)和另外一种数(x),且(x)出现次数是(0)出现次数两倍,也合法(其实这一情况可以归到上面)。

    应该就是这样吧。。。(AtCoder)上数据挺水的,如果有(Hack)数据可以在下方评论区留言。

    #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];
    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,t=0;for(F.read(n),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(!a[1]&&t==1) return puts("Yes"),0;if(n%3||t>3) return puts("No"),0;//分别特判有n个0、n非3的倍数、数的种类超过3种的情况
    	for(i=1;i<n/3;++i) if(a[i]^a[i+1]) return puts("No"),0;//判断第一种数是否占所有数的1/3
    	for(i=n/3+1;i<n/3*2;++i) if(a[i]^a[i+1]) return puts("No"),0;//判断第二种数是否占所有数的1/3
    	for(i=n/3*2+1;i<n;++i) if(a[i]^a[i+1]) return puts("No"),0;//判断第三种数是否占所有数的1/3
    	return puts((a[1]^a[n/3+1])==a[n]?"Yes":"No"),0;//判断是否符合异或条件
    }
    

    (B):Even Degrees(点此看题面

    大致题意: 给你一张图,让你给每条边确定方向,使所有点出度为偶数。

    首先考虑判无解,不难发现边数为奇数时肯定无解,为偶数时一定有解。

    有解时,我们先找出原图中的一棵生成树,然后非树边随便定方向。

    接下来我们遍历这棵树。

    对于每个点,我们先处理完所有子节点,然后只考虑这个点与父节点之间边的方向。

    如果当前节点出度为奇数,边的方向就是向父亲,否则,边的方向就是向当前节点。

    这样我们可以保证除根节点外所有节点出度都是偶数。而总边数是偶数,所以根节点出度也是偶数。

    #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 add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,m,ee,d[N+5],lnk[N+5];struct edge {int to,nxt;}e[N<<1];
    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 writeE(Con Ty& x,Con Ty& y) {write(x),pc(' '),write(y),pc('
    ');}
    		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
    }F;
    template<int SZ> class UnionFindSet//并查集用于找生成树时判连通性
    {
    	private:
    		int f[SZ+5];
    		I int getfa(CI x) {return f[x]?f[x]=getfa(f[x]):x;}
    	public:
    		I void Union(CI x,CI y) {f[getfa(x)]=getfa(y);}
    		I bool Identify(CI x,CI y) {return getfa(x)==getfa(y);}
    };UnionFindSet<N> U;
    I void dfs(CI x,CI lst=0)//遍历树
    {
    	RI i;for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfs(e[i].to,x),0);//先处理子节点
    	lst&&(d[x]?F.writeE(x,lst):(d[lst]^=1,F.writeE(lst,x)),0);//根据当前节点度数奇偶性确定到父节点边的方向
    }
    int main()
    {
    	RI i,x,y;if(F.read(n,m),m&1) return puts("-1"),0;
    	for(i=1;i<=m;++i) F.read(x,y),U.Identify(x,y)?
    		(F.writeE(x,y),d[x]^=1):(U.Union(x,y),add(x,y),add(y,x));//生成树上边在树上连边,非树边随便定方向
    	return dfs(1),F.clear(),0;
    }
    

    (C):Skolem XOR Tree(点此看题面

    大致题意:(2n)个点,其中(i)(n+i)的点权都是(i)。要你构造一棵生成树,使得对于所有(i)(n+i)的树上路径异或值为(i)

    首先考虑,对于任意(2x)(2x+1),满足((2x) xor (2x+1)=1)

    所以我们可以对于所有(xlelfloorfrac n2 floor),连((2x)->(2x+1)->(1)->(n+2x)->(n+2x+1))

    注意对于节点(1)(n+1),我们连((2)->(n+1))即可。

    根据上面的步骤,对于(n)为奇数的情况,我们就已经处理完了。

    但如果(n)为偶数,那么对于(n)(2n)两个节点,我们就还没有建完,且它们不能按照之前的套路建了。

    考虑现在的问题,就是要在这棵生成树上找到两个点,使得它们树上路径异或值为(n),然后我们就可以让(n)(2n)分别作为这两个点的儿子。

    重要的是,这里的生成树是十分特殊的!树上路径异或值就是两个点到根路径上节点的异或值再异或(1)

    只要开个桶就能很方便地查找了。

    #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 add(x,y) (a[++cnt]=x,b[cnt]=y,addE(x,y),addE(y,x))
    #define addE(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,cnt,ee,s[2*N+5],p[N<<1],lnk[2*N+5],a[N<<1],b[N<<1];struct edge {int to,nxt;}e[N<<2];
    I bool dfs(CI x,CI lst=0)//找树上路径异或为n的两个点
    {
    	if(p[n^s[x]^1]) return add(n,p[n^s[x]^1]),add(x,n<<1),true;p[s[x]]=x;//先查询是否存在对应点,若有则建边返回true,没有则刷新桶
    	for(RI i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst&&(s[e[i].to]=s[x]^(e[i].to%n),dfs(e[i].to,x))) return true;//处理子节点
    	return false;//找不到返回false
    }
    int main()
    {
    	RI i,j,t;if(scanf("%d",&n),n==1) return puts("No"),0;//特判n=1的情况输出无解
    	for(i=2;i<n;i+=2) add(i,i+1),add(i+1,1),add(1,n+i),add(n+i,n+i+1);add(2,n+1);//对于每一对相邻的数,按套路建图
    	if(!(n&1)&&!dfs(s[1]=1)) return puts("No"),0;//如果第n个点和第2n个点找不到位置安放,输出无解
    	for(puts("Yes"),i=1;i<=cnt;++i) printf("%d %d
    ",a[i],b[i]);//输出
    	return 0;
    }
    

    (D):Add and Remove(点此看题面

    大致题意: 给你一个序列,你每次可以删除一个数,使其两边的数加上这个数的值,求最后剩下的两个数之和的最小值。

    我们可以考虑(dfs)

    设当前求解区间为([l,r]),这个区间左端点会被计算(tl)次,右端点会被计算(tr)次。

    则我们可以枚举在这个区间内最后被删除的数(i),则它被计算的次数就是(tl+tr)

    然后我们再递归处理子区间([l,i])([i,r]),统计最小值即可。

    #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 18
    #define LL long long
    #define RL Reg LL
    #define CL Con LL&
    #define INF 1e18
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    using namespace std;
    int n,a[N+5];
    I LL dfs(CI l,CI r,CL tl,CL tr)//dfs
    {
    	if(r-l==1) return 0;RL t,res=INF;//若只剩左右端点,返回0
    	for(RI i=l+1;i^r;++i) t=dfs(l,i,tl,tl+tr)+dfs(i,r,tl+tr,tr)+a[i]*(tl+tr),Gmin(res,t);//枚举最后被删除的数,递归子区间
    	return res;//返回答案
    }
    int main()
    {
    	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);//读入
    	return printf("%lld",dfs(1,n,1,1)+a[1]+a[n]),0;//求解
    }
    

    (E):Develop(点此看题面

    这题被(bzt)神仙搬去当了模拟赛题。

    题解可以详见这篇博客:【2019.8.11上午 慈溪模拟赛 T2】十七公斤重的文明(seventeen)

    (F):Two Histograms(点此看题面

    大致题意: 给定一个(n*m)矩阵,对每行确定一个(k_i∈[0,m]),对每列确定一个(l_i∈[0,n]),矩阵上((i,j))位置的值是([jle k_i]+[ile l_j]),问有多少种不同的矩阵。

    我们考虑对于一组序列(k,l),找到一个统一的方式去表示它们,使得表达结果相同的构造出的矩阵相同,不同的构造出的矩阵不同。

    然后画图可以发现,如果存在(x)(y)满足(k_x+1=y and l_y=x),那么我们就可以将(k_x)(1)(l_y)(1)

    那么我们就是要找出有多少组(k,l),使得不存在(x,y)满足上面条件。

    这可以容斥。

    就是用存在(x,y)对数(ge0)的方案数,减去对数(ge1)的方案数,加上对数(ge2)的方案数,以此类推。

    设需要求出对于(ge x)的方案数,我们先在(k)中选(x)个位置,然后在(l)中取(x)个位置,剩下的随便填,也就是:

    [C_n^x*A_m^x*(n+1)^{m-x}*(m+1)^{n-x} ]

    #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 500000
    #define X 998244353
    #define swap(x,y) (x^=y^=x^=y)
    #define Qinv(x) Qpow(x,X-2)
    #define A(x,y) (1LL*Fac[x]*IFac[(x)-(y)]%X)
    #define C(x,y) (1LL*A(x,y)*IFac[y]%X)
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    using namespace std;
    int n,m,Fac[N+5],IFac[N+5],Pn[N+5],Pm[N+5];
    I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    int main()
    {
    	RI i,t,ans=0;scanf("%d%d",&n,&m),n>m&&swap(n,m);
    	for(Fac[0]=Pn[0]=Pm[0]=i=1;i<=m;++i)//预处理
    		Fac[i]=1LL*Fac[i-1]*i%X,Pn[i]=1LL*Pn[i-1]*(n+1)%X,Pm[i]=1LL*Pm[i-1]*(m+1)%X;
    	for(IFac[m]=Qinv(Fac[m]),i=m-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;
    	for(i=0;i<=n;++i) t=1LL*C(n,i)*A(m,i)%X*Pn[m-i]%X*Pm[n-i]%X,Inc(ans,i&1?X-t:t);//容斥
    	return printf("%d",ans),0;//输出答案
    }
    
  • 相关阅读:
    Java Swing TextArea 滚动条和获得焦点
    Windows下一个AndroidStudio 正在使用Git(AndroidStudio工程GitHub关联)
    我们将与操作系统工作谈一场无私的爱──《云情人》思考
    CSDN markdown 编辑 三 基本语法
    Android项目包装apk和apk反编译,xml反编译
    char (*(*p[3])( int ))[5] 等等一系列 左右法则
    typedef 优于 #define
    int *(*a[5])(int, char*)
    C++宏定义详解
    STL 案例分析
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC035.html
Copyright © 2020-2023  润新知