zoukankan      html  css  js  c++  java
  • 【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
    ",ans),0;//输出答案
    }
    

    (C):Tests(点此看题面

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

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

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

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

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

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

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

    • (1sim x)中某一场加(y)分,把(1sim x+1)的其他考试加满。
    • (x+1sim m)的某一场加(y)分,把(1sim 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
    ",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
    ",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
    ",ans),0;//枚举最终点统计答案
    }
    

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

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

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

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

    [f_x=egin{cases}(sum_{i=0}^{2^n-1}f_i imes p_{xoplus i})+1&x ot=0,\0&x=0end{cases} ]

    其中(f_i imes p_{xoplus i})显然是一个异或卷积的形式。

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

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

    考虑(E(x))这个多项式的系数具体是什么,显然其中(1sim 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," 
    "[i==P-1]);return 0;//将每一项都减去f[0]
    }
    

    后记

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

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

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

  • 相关阅读:
    SciTE 快捷键
    MySQL数据库性能优化
    常用的正则表达式全面总结
    PHP中的Memcache的应用
    经典数学题:态度决定一切
    PHP Socket基础
    由浅入深探究mysql索引结构原理、性能分析与优化
    深入理解HTTP协议
    PHP会话控制之Session介绍原理
    PHP会话控制之Cookie使用例子
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC034.html
Copyright © 2011-2022 走看看