Atcoder AGC012 题解

A - AtCoder Group Contest

按照强度排序后,相邻的三个一组。

#include
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
const int N=300005;
int n,a[N];LL ans;
int main()
{
	n=read();
	for(RI i=1;i<=n*3;++i) a[i]=read();
	sort(a+1,a+1+n*3);
	for(RI i=n*3-1;i>n;i-=2) ans+=(LL)a[i];
	printf("%lld\n",ans);
	return 0;
}

B - Splatter Painting

读入所有询问后反向处理。染色的时候,在每个节点上记录它是被离它多远的点染色的,这样可以剪枝。

#include
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=100005;
int n,m,tot,js,Q;
int h[N],ne[N<<1],to[N<<1],wk[N],X[N],D[N],C[N],col[N],q[N],dis[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void work(int x,int d,int c) {
	if(d<=wk[x]) return;
	wk[x]=d;
	if(!col[x]) col[x]=c;
	if(!d) return;
	for(RI i=h[x];i;i=ne[i]) work(to[i],d-1,c);
}
int main()
{
	int x,y;
	n=read(),m=read();
	for(RI i=1;i<=m;++i) x=read(),y=read(),add(x,y),add(y,x);
	Q=read();
	for(RI i=1;i<=Q;++i) X[i]=read(),D[i]=read(),C[i]=read();
	for(RI i=1;i<=n;++i) wk[i]=-1;
	for(RI i=Q;i>=1;--i) work(X[i],D[i],C[i]);
	for(RI i=1;i<=n;++i) printf("%d\n",col[i]);
	return 0;
}

C - Tautonym Puzzle

假设已经构造出来的字符串,前半部分是X后半部分是Y,对于一个新字符c,将原字符串改成cXcY贡献增加一倍,改成cXYc增加一,则倍增即可。

#include
using namespace std;
#define RI register int
typedef long long LL;
LL bin[42],n;
int a[205],js,len,k;
int main()
{
	bin[0]=1;for(RI i=1;i<=40;++i) bin[i]=bin[i-1]<<1LL;
	scanf("%lld",&n);++n;
	for(RI i=40;i>=0;--i) if(n&bin[i]) {k=i;break;}
	for(RI i=k-1;i>=0;--i) {
		++js;
		for(RI j=len;j>=len/2+1;--j) a[j+2]=a[j];
		a[len/2+2]=js;
		for(RI j=len/2;j>=1;--j) a[j+1]=a[j];
		a[1]=js,len+=2;
		if(n&bin[i]) {
			++js;
			for(RI j=len;j>=1;--j) a[j+1]=a[j];
			a[1]=a[len+2]=js,len+=2;
		}
	}
	printf("%d\n",len);
	for(RI i=1;i<=len;++i) printf("%d ",a[i]);
	puts("");
    return 0;
}

D - Colorful Balls

发现最后一定是一些颜色中最轻的一部分球可以任意变动位置,这些球构成了一个“集合”,其他球位置保持不变。

所以检验一下每种颜色最轻的球可不可以与所有球中最轻的球(最轻的球的颜色则不用这个检验)交换位置,如果可以,哪些球又能和这种颜色的最轻球交换位置,最后得到每种颜色这个在这个集合里有多少个球,按照有重复元素的排列公式可以算出结果。

#include
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int mod=1e9+7,N=200005,inf=0x3f3f3f3f;
vector<int> orz[N];
int mi=inf,mic,mii,X,Y,ans,js,n,a[N],fac[N],ni[N];
int ksm(int x,int y) {
	int re=1;
	for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
	return re;
}
int main()
{
	int c,w;
	n=read(),X=read(),Y=read();
	for(RI i=1;i<=n;++i) {
		c=read(),w=read(),orz[c].push_back(w);
		if(w<=mi) mii=mi,mi=w,mic=c;
		else if(w<mii) mii=w;
	}
	for(RI i=1;i<=n;++i) sort(orz[i].begin(),orz[i].end());
	for(RI i=1;i<=n;++i) {
		if(!(int)(orz[i].size())) continue;
		if(i==mic) {
			a[i]=1,++js;
			for(RI j=1;j<orz[i].size();++j)
				if(orz[i][j]+mii<=Y||orz[i][j]+orz[i][0]<=X) ++a[i],++js;
		}
		else {
			if(orz[i][0]+mi>Y) continue;
			a[i]=1,++js;
			for(RI j=1;j<orz[i].size();++j)
				if(orz[i][j]+mi<=Y||orz[i][j]+orz[i][0]<=X) ++a[i],++js;
		}
	}
	fac[0]=1;for(RI i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
	ni[n]=ksm(fac[n],mod-2);
	for(RI i=n-1;i>=0;--i) ni[i]=1LL*ni[i+1]*(i+1)%mod;
	ans=fac[js];
	for(RI i=1;i<=n;++i) ans=1LL*ans*ni[a[i]]%mod;
	printf("%d\n",ans);
    return 0;
}

E - Camel and Oases

显然,最多进行 l o g log log次跳跃。假设确定了当前驼峰的储水量,会发现可以把绿洲划分为一条条的线段,每条线段内部的绿洲可以通过行走互相到达,否则就需要跳跃。若把储水量为 V V V时的线段看做第0层,为 V / 2 V/2 V/2时看做第1层,为 V / 4 V/4 V/4时看做第2层……可以发现遍历绿洲就是从每层取一条线段覆盖所有绿洲。

对于第0层的一条线段,如果默认第0层选它,能不能遍历绿洲呢?

接下来就是状压DP了。首先预处理存好每层的线段,以及每层覆盖每个绿洲的线段的左右端点。设 f [ z t ] f[zt] f[zt] z t zt zt是一个二进制状态,表示有哪些层被取了线段, f f f记录取了这些线段后覆盖 [ 1 , x ] [1,x] [1,x]这个区间,能覆盖到的最大 x x x。这个转移就通过枚举当前状态下下一次取哪一层的线段,然后取那一层包含 f [ z t ] + 1 f[zt]+1 f[zt]+1这个绿洲的线段即可。

也同样DP出一个 g [ z t ] g[zt] g[zt],覆盖 [ x , n ] [x,n] [x,n]能覆盖到的最小 x x x

有了f和g,我们就可以知道 m i [ i ] mi[i] mi[i]:当 [ 1 , i ] [1,i] [1,i]被覆盖时, [ x , n ] [x,n] [x,n]能被覆盖到的最小 n n n

有了mi后,我们就可以判断第0层强制选某条线段后,其他绿洲能不能被覆盖了。

#include
using namespace std;
#define RI register int
int read() {
	int q=0,w=1;char ch=' ';
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q*w;
}
const int N=200005,LIM=524290,inf=0x3f3f3f3f;
int n,V,m;
int bin[21],x[N],L[N],f[LIM],g[LIM],in[21][N],ans[N],mi[N];
vector<int> seg[21];

void prework() {
	seg[m].push_back(0);
	for(RI i=2;i<=n;++i)
		if(x[i]-x[i-1]>V) seg[m].push_back(i-1);
	seg[m].push_back(n);
	int now=1;
	for(RI i=1;i<seg[m].size();++i)
		while(now<=seg[m][i]) in[m][now]=i,++now;
}
void work1() {
	for(RI i=0;i<bin[m]-1;++i) {
		for(RI j=0;j<m;++j)
			if(!(i&bin[j]))
				f[i|bin[j]]=max(f[i|bin[j]],seg[j+1][in[j+1][f[i]+1]]);
	}
}
void work2() {
	for(RI i=0;i<bin[m];++i) g[i]=n+1;
	for(RI i=0;i<bin[m]-1;++i) {
		for(RI j=0;j<m;++j)
			if(!(i&bin[j]))
				g[i|bin[j]]=min(g[i|bin[j]],seg[j+1][in[j+1][g[i]-1]-1]+1);
	}
}
void work3() {
	for(RI i=0;i<=n;++i) mi[i]=inf;
	for(RI i=0;i<bin[m];++i)
		mi[f[i]]=min(mi[f[i]],g[(bin[m]-1)^i]);
	for(RI i=n-1;i>=0;--i) mi[i]=min(mi[i],mi[i+1]);
}

int main()
{
	n=read(),V=read();
	bin[0]=1;for(RI i=1;i<=20;++i) bin[i]=bin[i-1]<<1;
	for(RI i=1;i<=n;++i) x[i]=read();
	prework();while(V) V>>=1,++m,prework();
	work1(),work2(),work3();
	for(RI i=1;i<seg[0].size();++i) {
		int L=seg[0][i-1]+1,R=seg[0][i];
		if(mi[L-1]<=R+1) for(RI j=L;j<=R;++j) ans[j]=1;
	}
	for(RI i=1;i<=n;++i) puts(ans[i]?"Possible":"Impossible");
	return 0;
}

F - Prefix Median

当我已经确定好前 2 i − 1 2i-1 2i1 a a a后,若添加两个比中位数大的数,中位数变为被添加了的数中它的后继。若添加两个比中位数小的数,则变为前驱。若添加一大一小,则不变。

a a a从小到大排序,显然第 i i i个中位数的值在区间 [ a i , a 2 n − i ] [a_i,a_{2n-i}] [ai,a2ni]中。

先假设 a a a是个排列。

f ( i , j , k ) f(i,j,k) f(i,j,k)表示已经确定了第 i i i个及之后的 b b b,且确定 b i b_i bi前其可能能选的数值中,有 j j j个小于等于 b i + 1 b_{i+1} bi+1 k k k个大于 b i + 1 b_{i+1} bi+1的方案数。

初始值 f ( n , 1 , 0 ) = 1 f(n,1,0)=1 f(n,1,0)=1

当状态 f ( i , j , k ) f(i,j,k) f(i,j,k) f ( i − 1 , ? , ? ) f(i-1,?,?) f(i1,?,?)转移时, b i b_i bi的备选方案相比 b i + 1 b_{i+1} bi+1,增加了 a i a_i ai a 2 n − i a_{2n-i} a2ni两种,所以 j j j k k k分别加1。

每此从 f ( i , ? , ? ) f(i,?,?) f(i,?,?) f ( i − 1 , ? , ? ) f(i-1,?,?) f(i1,?,?)转移时,已经确定好的(然而没体现在DP状态里)的 a a a应该要被删除两个。

接下来,假设选择备选方案中, b i b_i bi往左数第 L L L个(因为 j j j的含义是小于等于,所以这个左数是从选 b i b_i bi开始算选第一个),那么由于 b i b_i bi应当是 b i + 1 b_{i+1} bi+1的前驱,所以往左数第 L − 1 L-1 L1到第 1 1 1个被纳入备选方案的数,都应该在之前的移动中,删除一定的 a a a时,被删除掉了,所以转移到 f ( i , j + 1 − L + 1 , k + 1 + ( L > 1 ) ) f(i,j+1-L+1,k+1+(L>1)) f(i,j+1L+1,k+1+(L>1))

b i b_i bi往右数第 R R R个,同理,转移到 f ( i , j + 1 + 1 , k + 1 − R ) f(i,j+1+1,k+1-R) f(i,j+1+1,k+1R)

那么问题来了,是不是所有备选方案里的数都能选呢?

其实是可以的,虽然我不会严谨证明,不过可以用CY证明法证一下:

从前往后推,每次删a时:若b值往右移,要删两个较小数,都删较小数中最大的;若b值不变,要删一个较小一个较大,较小的那个删较小数中最大的。那么每次取新的b时,都能取到备选方案中最小的数。
若改一下,删一次较小数中最小的,就能取到备选方案中第二小的数。
……

现在将排列扩展到一般序列,只需要改一个地方:若 a i = a i + 1 a_i=a_{i+1} ai=ai+1,则每次移动后, j j j的值并没有+1( b b b的可选值种类没有增加)。同样,若 a 2 n − i = a 2 n − i − 1 a_{2n-i}=a_{2n-i-1} a2ni=a2ni1 k k k的值也没有+1。

#include
using namespace std;
#define RI register int
const int mod=1e9+7;
int n,ans,f[102][102][102],a[102];
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
	scanf("%d",&n);
	for(RI i=1;i<=n+n-1;++i) scanf("%d",&a[i]);
	sort(a+1,a+n+n);
	f[n][1][0]=1;
	for(RI i=n-1;i>=1;--i) {
		int pl=(a[i]!=a[i+1]),pr=(a[n+n-i]!=a[n+n-i-1]);
		for(RI j=0;j<=n+n-1;++j)
			for(RI k=0;j+k<=n+n-1;++k) {
				if(!f[i+1][j][k]) continue;
				for(RI L=1;L<=j+pl;++L)
					f[i][j+pl-L+1][k+pr+(L>1)]=qm(f[i][j+pl-L+1][k+pr+(L>1)]+f[i+1][j][k]);
				for(RI R=1;R<=k+pr;++R)
					f[i][j+pl+1][k+pr-R]=qm(f[i][j+pl+1][k+pr-R]+f[i+1][j][k]);
			}
	}
	for(RI i=0;i<=n+n-1;++i)
		for(RI j=0;j<=n+n-1;++j) ans=qm(ans+f[1][i][j]);
	printf("%d\n",ans);
	return 0;
}

你可能感兴趣的:(杂项,Atcoder,AGC题解)