前言
最近找不到想做的题,在闪指导的建议找了场以前的(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_i imes p_{xoplus i})显然是一个异或卷积的形式。
按照套路,我们把它们的生成函数写成卷积,得到一个等量关系:(其中(*)表示异或卷积)
考虑(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)。
即:(其实这个式子放在这里也没啥意义)
回到原式,由于卷积是满足结合律的,因此我们可以把(F(x))移到左边得到:
单独保留(F(x)),得到:
于是就做完了?
等等,异或卷积求逆是什么鬼东西啊?
我也不知道是什么东西,想了半天也没想明白。
根据网上的题解,似乎就是先分别做(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)是前一半)。
可惜,这次的正确率很低,一道题往往要挂好多次,不过这可能也和这次基本上没看过题解有关吧,正因此很多细节一开始都没有想清楚。