把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AtCoder】AtCoder Grand Contest 034 解题报告

点此进入比赛

前言

最近找不到想做的题,在闪指导的建议找了场以前的\(AGC\)题目。

同时又由于闪指导这几天比较忙(忙着指导),这一场我是自己做的,没法接受闪指导的教诲了。

\(A\):Kenken Race(点此看题面

大致题意:\(n\)个位置,其中有一些有障碍,每次行走可以从第\(i\)个格子走到第\(i+1\)\(i+2\)个格子(要求格子为空)。现有两人分别在\(A,B\),问是否能分别走到\(C,D\)(满足\(A<B,A<C,B<D\))。

仅仅考虑从一个位置是否能走到另一个位置,则只要不存在连续两个障碍格即可。

但由于现在有两个人,我们还要考虑当\(C>D\)时,\(A\)是否能走到\(B\)的右边,其实就是需要连续三个空格为\(A\)提供一个超车的机会,只要判断\(B-1\)\(D+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 200000
using namespace std;
int n,A,B,C,D;char s[N+5];
int main()
{
	RI i;scanf("%d%d%d%d%d%s",&n,&A,&B,&C,&D,s+1);
	for(i=A;i^C;++i) if(s[i]=='#'&&s[i+1]=='#') return puts("No"),0;//A能否到C
	for(i=B;i^D;++i) if(s[i]=='#'&&s[i+1]=='#') return puts("No"),0;//B能否到D
	if(C>D)//是否需要超车
	{
		RI f=0;for(i=B;i<=D&&!f;++i) s[i-1]=='.'&&s[i]=='.'&&s[i+1]=='.'&&(f=1);//是否存在连续三个空格
		if(!f) return puts("No"),0;
	}return puts("Yes"),0;
}

\(B\):ABC(点此看题面

大致题意: 给定一个由A B C组成的字符串,每次操作将一个ABC变成BCA,问最多能操作几次。

考虑每次操作都相当于是将A不断向右移。

因此,我们记录一个\(t\)表示当前持有的A的个数,无非有以下几种情况:

  • 当前位是A,说明又可以多持有一个A,将\(t\)\(1\)
  • 当前位是B,且下一位是C,说明可以进行\(t\)次操作,将\(ans\)\(t\)
  • 否则,当前持有的A都失效了,\(t=0\)
#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
#define LL long long
using namespace std;
int n;char s[N+5];
int main()
{
	RI i,t=0;LL ans=0;scanf("%s",s+1),n=strlen(s+1);
	for(i=1;i<n;++i) s[i]^'A'?(s[i]=='B'&&s[i+1]=='C'?(++i,ans+=t):(t=0)):++t;//分三种情况
	return printf("%lld\n",ans),0;//输出答案
}

\(C\):Tests(点此看题面

大致题意:\(n\)场考试,已知\(B\)同学第\(i\)场考试分数为\(b_i\)\(A\)同学可以给第\(i\)场考试一个\(l_i\sim u_i\)间的重要度\(c_i\),且可以花\(1\)单位时间为自己任意一场考试分数加\(1\)(单场考试分数不能超过\(X\)),求至少花费多少时间能够使\(\sum_{i=1}^n(a_i-b_i)\times c_i\ge 0\)

显然的一个贪心,当\(a_i\le b_i\)时,\(c_i=l_i\),否则\(c_i=u_i\)

于是我们考虑给第\(i\)次考试加分的贡献,当\(a_i\le b_i\)时贡献为\(l_i\),否则贡献为\(u_i\)

由于\(l_i\le u_i\),因此对于同一个\(i\),给它加分的贡献是不降的。

也就是说,在能加的分数一定时,把一场考试加满肯定不会使答案变劣

因此,先把考试按加满能得到的总贡献排序,然后二分答案\(mid\),令\(x=mid\ div\ X,y=mid\ mod\ X\)

则最优方案只有两种情况:

  • \(1\sim x\)中某一场加\(y\)分,把\(1\sim x+1\)的其他考试加满。
  • \(x+1\sim m\)的某一场加\(y\)分,把\(1\sim x\)的考试加满。

(其实也就是把某一场考试加\(y\)分,然后把其余考试中的前\(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 100000
#define LL long long
using namespace std;
int n,m;struct Data
{
	int a,b,p;I bool operator < (Con Data& o) Con {return 1LL*p*a+1LL*(m-p)*b>1LL*o.p*o.a+1LL*(m-o.p)*o.b;}//按加满所得贡献排序
}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 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=(x<<3)+(x<<1)+(c&15),D);}
}F;
#define V(i,v) (v<=s[i].p?1LL*v*s[i].a:1LL*s[i].p*s[i].a+1LL*(v-s[i].p)*s[i].b)//给第i场考试v分的贡献
I bool Check(Con LL& w)//验证
{
	RI i,x=w/m,y=w%m;LL t=0;for(i=1;i<=x;++i) t+=1LL*(m-s[i].p)*s[i].b;for(;i<=n;++i) t-=1LL*s[i].p*s[i].a;//初始化t
	for(i=x+1;i<=n;++i) if(t+V(i,y)>=0) return 1;for(i=1;i<=x;++i) if(t+V(x+1,m)-V(i,m)+V(i,y)>=0) return 1;return 0;//枚举y分的考试
}
int main()
{
	RI i;for(F.read(n),F.read(m),i=1;i<=n;++i) F.read(s[i].p),F.read(s[i].a),F.read(s[i].b);
	sort(s+1,s+n+1);LL l=0,r=1LL*n*m,mid;W(l<r) Check(mid=l+r-1>>1)?r=mid:l=mid+1;return printf("%lld\n",r),0;//排序后二分答案
}

\(D\):Manhattan Max Matching(点此看题面

大致题意: 平面直角坐标系上有\(n\)个红点和\(n\)个蓝点,每个点上有一定数量的球(保证两种颜色球的总数相等)。求最优的红蓝球匹配方式,使得匹配两球曼哈顿距离之和最大。

做题做太少了,尤其是太久没写网络流,根本就没有往网络流的方向上去靠。

实际上只要想到了网络流,作为\(D\)题,此题真的偏水了。

网络流求二分图匹配相信大家都会,但这里如果直接暴力建边显然不太行,因此要优化。

考虑曼哈顿距离\(|x_1-x_2|+|y_1-y_2|\),其中\(|a|=\max\{a,-a\}\),而此题中求的又恰好是最大值。

因此我们建四个辅助节点,每个红球向它们分别连代价为\(x+y,x-y,-x+y,-x-y\)的边,每个蓝球分别由它们连代价为\(-x-y,-x+y,x-y,x+y\)的边。

然后跑一遍最大费用最大流即可。

#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 1000
#define LL long long
using namespace std;
int n;class NetFlow//网络流
{
	private:
		#define E(x) ((((x)-1)^1)+1)
		#define add(x,y,f,c) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c)
		int ee,lnk[2*N+10];struct edge {int to,nxt,F,C;}e[20*N+5];
		int IQ[2*N+10],p[2*N+10],F[2*N+10];LL C[2*N+10];queue<int> q;I bool SPFA(CI s,CI t)//SPFA找增广路
		{
			RI i,y,k;for(i=1;i<=2*n+6;++i) C[i]=-1e18;F[s]=1e9,C[s]=0,q.push(s);W(!q.empty())
				for(i=lnk[k=q.front()],q.pop(),IQ[k]=0;i;i=e[i].nxt) C[y=e[i].to]<C[k]+e[i].C&&
					e[i].F&&(C[y]=C[k]+e[p[y]=i].C,F[y]=min(F[k],e[i].F),!IQ[y]&&(q.push(y),IQ[y]=1));
			return C[t]!=-1e18;
		}
	public:
		I void Add(CI x,CI y,CI f,CI c) {add(x,y,f,c),add(y,x,0,-c);}//连边
		I void MCMF()//最大费用最大流
		{
			RI x,s=2*n+1,t=2*n+2;LL y=0;W(SPFA(s,t))
			{
				y+=C[x=t]*F[t];W(x^s) e[p[x]].F-=F[t],e[E(p[x])].F+=F[t],x=e[E(p[x])].to;
			}printf("%lld\n",y);
		}
}F;
int main()
{
	RI i,x,y,z;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d%d",&x,&y,&z),F.Add(2*n+1,i,z,0),//从超级源连边
		F.Add(i,2*n+3,z,x+y),F.Add(i,2*n+4,z,x-y),F.Add(i,2*n+5,z,-x+y),F.Add(i,2*n+6,z,-x-y);//向四个虚拟点连边
	for(i=1;i<=n;++i) scanf("%d%d%d",&x,&y,&z),F.Add(n+i,2*n+2,z,0),//向超级汇连边
		F.Add(2*n+3,n+i,z,-x-y),F.Add(2*n+4,n+i,z,-x+y),F.Add(2*n+5,n+i,z,x-y),F.Add(2*n+6,n+i,z,x+y);//从四个虚拟点连边
	return F.MCMF(),0;
}

\(E\):Complete Compress(点此看题面

大致题意: 给定一棵树,有些节点上有球。每次你可以把距离大于\(1\)的两个小球向着对方的方向各自移动一步,问最少需要几步能够把所有的球移到同一个点。

显然可以枚举最终点\(i\),然后只要求出每个\(i\)的答案并取最小值即可。

考虑我们直接以\(i\)为根暴力\(dfs\)一遍,对于每个点\(x\)求出它子树内所有球到\(x\)的距离之和\(f_x\)、子树内球数\(sz_x\)

但这样是不够的,我么还需要求出一个\(g_x\),表示\(x\)子树内所有无法移到\(x\)的球\(x\)的距离之和。

什么叫无法移到\(x\)

我们发现,只有对分属于\(x\)不同子树内的球操作才能使两个球同时向上移动。(同一子树内的必然已在处理该子树时考虑过了)

设一个子树内需要移动\(u\)\(=g_{son}+sz_{son}\))次,其余子树内最多移动\(v\)\(=f_x-(f_{son}+sz_{son})\),注意是\(f\))次。

如果\(u>v\),则这个子树中最多被移动\(v\)次,无法把所有点移到根,因此\(g_x=u-v\)

否则,若不存在这样的子树,由于每次必须要移动两个点,因此令\(g_x=(\sum g_{son})\mod2\)

搜完之后发现,\(i\)节点能作为最终点当且仅当\(g_i=0\),此时答案为\(\frac{f_i}2\)

#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 2000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,ans=N*N,ee,lnk[N+5],f[N+5],g[N+5],sz[N+5];char s[N+5];struct edge {int to,nxt;}e[N<<1];
I void dfs(CI x,CI lst=0)//暴搜一遍
{
	RI i;for(f[x]=g[x]=0,sz[x]=s[x]&1,i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
		(dfs(e[i].to,x),f[x]+=f[e[i].to]+sz[e[i].to],sz[x]+=sz[e[i].to],g[x]+=g[e[i].to]+sz[e[i].to]);//上传信息
	RI u,v,t=0;for(i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst)//寻找是否存在一个移不完的子树
		if((u=g[e[i].to]+sz[e[i].to])>(v=f[x]-f[e[i].to]-sz[e[i].to])) return (void)(g[x]=u-v);g[x]&=1;//得出g值
}
int main()
{
	RI i,x,y;for(scanf("%d%s",&n,s+1),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
	for(i=1;i<=n;++i) dfs(i),!g[i]&&ans>f[i]/2&&(ans=f[i]/2);return ans==N*N?puts("-1"):printf("%d\n",ans),0;//枚举最终点统计答案
}

\(F\):RNG and XOR(点此看题面

大致题意: 有一个\(n\)位二进制数的随机数生成器,其中生成\(x\)的概率为\(p_x\)。初始\(S=0\),每次将\(S\)异或上随机产生的数。对于\(i=0\sim 2^{n-1}\),问期望多少回合后第一次\(S=i\)

一道比较套路的多项式题,前面的都能自己推出来,然而死在了最后奇怪的异或卷积求逆。

考虑我们设\(f_x\)表示生成\(x\)的期望步数,显然有转移:

\[f_x=\begin{cases}(\sum_{i=0}^{2^n-1}f_i\times p_{x\oplus i})+1&x\not=0,\\0&x=0\end{cases} \]

其中\(f_i\times p_{x\oplus i}\)显然是一个异或卷积的形式。

按照套路,我们把它们的生成函数写成卷积,得到一个等量关系:(其中\(*\)表示异或卷积)

\[F(x)*P(x)=F(x)+E(x) \]

考虑\(E(x)\)这个多项式的系数具体是什么,显然其中\(1\sim 2^n-1\)项的系数都是\(-1\),而第\(0\)项的系数却不好搞。

但是,我们知道\(\sum_{i=0}^{2^n-1}p_i=1\),因此\(F(x)*P(x)\)所得多项式的系数总和应该与\(F(x)\)的系数总和一致,则第\(0\)项的系数自然就是\(2^n-1\)

即:(其实这个式子放在这里也没啥意义)

\[E(x)=(2^n-1)-\sum_{i=1}^{2^n-1}x^i \]

回到原式,由于卷积是满足结合律的,因此我们可以把\(F(x)\)移到左边得到:

\[F(x)*(P(x)-1)=E(x) \]

单独保留\(F(x)\),得到:

\[F(x)=E(x)*(P(x)-1)^{-1} \]

于是就做完了?

等等,异或卷积求逆是什么鬼东西啊?

我也不知道是什么东西,想了半天也没想明白。

根据网上的题解,似乎就是先分别做\(FWT\),然后把左项乘上右项的逆元,再做\(IFWT\)

由于这道题很特殊,\(f_0=0\),因此我们只要最终把每个\(f_i\)都减去\(f_0\)就可以了。

说实话最后几步还是不怎么懂。

#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 X 998244353
#define I2 499122177LL
using namespace std;
int n,P,p[1<<N],_[1<<N];
I int QP(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;}
I void FWT(int *s,CI op)//异或卷积
{
	for(RI i=1,j,k,x,y;i^P;i<<=1) for(j=0;j^P;j+=i<<1) for(k=0;k^i;++k) x=s[j+k],y=s[i+j+k],
		s[j+k]=(x+y)%X,s[i+j+k]=(x-y+X)%X,!~op&&(s[j+k]=I2*s[j+k]%X,s[i+j+k]=I2*s[i+j+k]%X);
}
int main()
{
	RI i,s=0;for(scanf("%d",&n),P=1<<n,i=0;i^P;++i) scanf("%d",p+i),s+=p[i];
	for(s=QP(s,X-2),i=0;i^P;++i) p[i]=1LL*p[i]*s%X;p[0]?--p[0]:(p[0]=X-1);//先把p转化为分数,然后将P(x)减1
	for(_[0]=P-1,i=1;i^P;++i) _[i]=X-1;//求出E(x)
	FWT(p,1),FWT(_,1);for(RI i=0;i^P;++i) _[i]=1LL*_[i]*QP(p[i],X-2)%X;FWT(_,-1);//卷积
	for(i=0;i^P;++i) printf("%d%c",(_[i]-_[0]+X)%X," \n"[i==P-1]);return 0;//将每一项都减去f[0]
}

后记

感觉这一场\(AGC\)似乎特别水?

我自己能做出\(A,B,C,E\),而\(D,F\)也都能推出一半(\(D\)是后一半,\(F\)是前一半)。

可惜,这次的正确率很低,一道题往往要挂好多次,不过这可能也和这次基本上没看过题解有关吧,正因此很多细节一开始都没有想清楚。

posted @ 2020-07-26 16:33  TheLostWeak  阅读(299)  评论(0编辑  收藏  举报