AtCoder Grand Contest 012

AtCoder Grand Contest 012

A - AtCoder Group Contest

翻译

\(3n\)个人,每一个人有一个强大值(看我的假翻译),每三个人可以分成一组,一组的强大值定义为三个人中第二强的人的强大值。求\(n\)组最大的强大值之和。

题解

这。。。不是倒着选两个人,正着选一个人构成一组就好了嘛。。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,a[300100];ll ans;
int main()
{
	n=read();
	for(int i=1;i<=n*3;++i)a[i]=read();
	sort(&a[1],&a[n+n+n+1]);
	for(int i=n+n+n-1,j=1;j<=n;++j,i-=2)ans+=a[i];
	cout<<ans<<endl;
	return 0;
}

B - Splatter Painting

翻译

给你一张无向图,每次给定一个点\(u_i\),让你把距离这个点不超过\(d_i\)的所有点都染成颜色\(c_i\)。输出最终每个点的颜色。

题解

这种染色问题倒着做。对于每个点记录一个它染出去的最远距离,因为\(d\)很小,所以这个值最多被改变\(d\)次。那么暴力即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define MAX 100100
#define ll long long
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,m,col[MAX],dis[MAX];
int Q,U[MAX],D[MAX],C[MAX];
void BFS(int S,int d,int c)
{
	if(dis[S]>=d)return;dis[S]=d;
	queue<int> Q;Q.push(S);
	while(!Q.empty())
	{
		int u=Q.front();Q.pop();
		if(!col[u])col[u]=c;
		if(!dis[u])continue;
		for(int i=h[u];i;i=e[i].next)
			if(dis[e[i].v]<dis[u]-1)
			{
				dis[e[i].v]=dis[u]-1;
				Q.push(e[i].v);
			}
	}
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=m;++i)
	{
		int u=read(),v=read();
		Add(u,v);Add(v,u);
	}
	Q=read();memset(dis,-1,sizeof(dis));
	for(int i=1;i<=Q;++i)U[i]=read(),D[i]=read(),C[i]=read();
	for(int i=Q;i;--i)
		BFS(U[i],D[i],C[i]);
	for(int i=1;i<=n;++i)printf("%d\n",col[i]);
	return 0;
}

C - Tautonym Puzzle

翻译

构造一个长度不超过\(200\),字符集为\([1,100]\)的串。使得其中能够从中间分成左右两个完全一样的子序列的个数恰好为\(n\)

题解

一个很\(naive\)的想法就是把\(n\)二进制分解,然后对于每一个二的若干次幂找一段连续的构建一下,长成\(123...123...\)这个样子。然而发现这样子的数字以及长度似乎都爆炸了。

换个方式,我们来倍增,假设\(XY\)这个串有\(ans\)对,那么\(aXaY\)就有\(2ans\)对,\(aXYa\)就有\(ans+1\)对。那么就很资磁了,倍增一下就好了。

至于为什么代码里\(n\)要加一,因为如果只按代码的方式做的话你会发现少了一对。

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
#define ll long long
ll n;int cnt;
deque<int> Q1,Q2;
void Work(ll n)
{
	if(n==1)return;
	Work(n>>1);++cnt;
	Q1.push_front(cnt);Q2.push_front(cnt);
	if(n&1)++cnt,Q1.push_front(cnt),Q2.push_back(cnt); 
}
int main()
{
	cin>>n;Work(n+1);
	printf("%d\n",(int)(Q1.size()+Q2.size()));
	while(!Q1.empty())printf("%d ",Q1.front()),Q1.pop_front();
	while(!Q2.empty())printf("%d ",Q2.front()),Q2.pop_front();
	puts("");return 0;
}

D - Colorful Balls

翻译

\(n\)个球排成一行,每一个球都有颜色和重量,如果两个球颜色相同并且重量和小于\(X\)那么他们可以交换位置,如果两个球颜色不同并且重量和小于\(Y\)那么他们可以交换位置。求有多少种不同的颜色序列。

题解

不难发现如果\(u\)可以和\(a,b\)交换,那么\(u,a,b\)就可以任意交换位置。那么如果我们把可以交换位置的球用边给连起来,那么一个联通块中的球可以任意交换位置。因此,一个联通块的贡献就是球的个数的阶乘除掉每种颜色球的个数的阶乘。而最终的答案显然就是所有联通块的乘积。

先考虑同色的连边。显然如果一个球能够和同色的连边,那么它必定可以和同色的重量最小的球连边,而连边方式有两种,第一种是和同色最小值的和小于\(X\),这样子可以直接连边。第二种连边方式则是这个球和异色最小值的和小于\(Y\),那么同色的最小值也必定可以和这个异色最小值连边,也就是这个球可以和同色最小值连边。如果我们把同色球按照重量排序,显然一个前缀之间都可以互换。而不能互换的显然是一段后缀,并且显然它们也不能和任何异色球连边,因为它们不能和异色最小值连边,也必定不能和其他异色的球连边。那么我们处理完所有同色的球之后,显然同色球可以合在一起考虑,只需要考虑重量最小的球对外的连边。那么再按照重量排一遍序,显然可以链接的一定是一段前缀,那么直接考虑这一段前缀的贡献就好了。

我竟然做出来了?这题真简单?

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define MAX 200200
#define MOD 1000000007
using namespace std;
#define ll long long
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,X,Y,ans;
struct Ball{int c,w;}B[MAX];
bool operator<(Ball a,Ball b){if(a.c!=b.c)return a.c<b.c;return a.w<b.w;}
int jc[MAX],jv[MAX],inv[MAX];
int pmn[MAX],smn[MAX],sz[MAX],W[MAX],p[MAX];
bool cmp(int i,int j){return W[i]<W[j];}
int main()
{
	n=read();X=read();Y=read();
	for(int i=1;i<=n;++i)B[i].c=read(),B[i].w=read();
	jc[0]=jv[0]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%MOD,jv[i]=1ll*jv[i-1]*inv[i]%MOD;
	sort(&B[1],&B[n+1]);
	memset(pmn,63,sizeof(pmn));
	memset(smn,63,sizeof(smn));
	memset(W,63,sizeof(W));
	for(int i=1,j;i<=n;i=j+1)
	{
		j=i;while(j<n&&B[i].c==B[j+1].c)++j;
		pmn[B[i].c]=smn[B[i].c]=B[i].w;
	}
	for(int i=1;i<=n;++i)pmn[i]=min(pmn[i],pmn[i-1]);
	for(int i=n;i>=1;--i)smn[i]=min(smn[i],smn[i+1]);
	for(int i=1,j;i<=n;i=j+1)
	{
		j=i;while(j<n&&B[i].c==B[j+1].c)++j;
		int d=min(pmn[B[i].c-1],smn[B[i].c+1]);
		int cnt=1;
		for(int k=i+1;k<=j;++k)
			if(B[i].w+B[k].w<=X||B[k].w+d<=Y)++cnt;
			else break;
		sz[B[i].c]=cnt;W[B[i].c]=B[i].w;
	}
	for(int i=1;i<=n;++i)p[i]=i;
	sort(&p[1],&p[n+1],cmp);
	for(int i=1;i<=n;++i)
		if(i==1||W[p[i]]+W[p[1]]<=Y)
			ans+=sz[p[i]];
	ans=jc[ans];
	for(int i=1;i<=n;++i)
		if(i==1||W[p[i]]+W[p[1]]<=Y)
			ans=1ll*ans*jv[sz[p[i]]]%MOD;
	printf("%d\n",ans);
	return 0;
}

E - Camel and Oases

翻译

\(n\)个绿洲排成一条直线,有一只骆驼,可以带体积为\(V\)的水,在绿洲可以补满水。现在骆驼要访问所有的绿洲,它有两种运动方式,第一种直接走过去,要求两个绿洲直接的距离不能超过\(V\)。另外一种是跳过去,\(V\)变成\([\frac{V}{2}]\),然后直接到达,当\(V=0\)时就不能跳了。

现在对于每一个绿洲作为起点,回答能否遍历所有的绿洲。

题解

发现\(V/2^k\)一共就\(log\)种取值,显然可以预处理出对于每一个起点,\(V=V/2^k\)时能够到达的区间。这样子我们就得到了一条条线段。

那么问题等价于回答,对于每一个\(V/2^k\),从中分别选出一条线段,然后每次钦定\(k=0\)的线段,问能否覆盖整个区间。

然后就状压了!设\(sl[S]\)表示用了\(S\)集合中的这些长度的线段(\(V/2^k\)),能够覆盖\([1,x]\)的最大\(x\)值,同理预处理\(sr[S]\),这样子我们就可以知道左右侧能够拼出来的位置。那么每个询问只需要考虑中间一段加入进来之后能够把整个线段联通即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 200200
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
void cmax(int &x,int y){if(x<y)x=y;}
void cmin(int &x,int y){if(x>y)x=y;}
int n,V[25],x[MAX],t;
int L[20][MAX],R[20][MAX],mr[MAX];
int sl[1<<19],sr[1<<19];
int main()
{
	n=read();V[0]=read();
	for(int i=1;i<=n;++i)x[i]=read();
	while(V[t])V[t+1]=V[t]>>1,++t;int S=1<<t;
	for(int i=0;i<=t;++i)
	{
		L[i][1]=1;R[i][n]=n;
		for(int j=2;j<=n;++j)
			if(x[j]-x[j-1]<=V[i])L[i][j]=L[i][j-1];
			else L[i][j]=j;
		for(int j=n-1;~j;--j)
			if(x[j+1]-x[j]<=V[i])R[i][j]=R[i][j+1];
			else R[i][j]=j;
	}
	for(int i=1;i<S;++i)sl[i]=1,sr[i]=n;
	sl[0]=0;sr[0]=n+1;
	for(int i=0;i<S;++i)
		for(int j=1;j<=t;++j)
			if(!(i&(1<<(j-1))))
			{
				cmax(sl[i|(1<<(j-1))],R[j][sl[i]+1]);
				cmin(sr[i|(1<<(j-1))],L[j][sr[i]-1]);
			}
	memset(mr,63,sizeof(mr));
	for(int j=0;j<S;++j)cmin(mr[sl[j]],sr[(S-1)^j]);
	for(int i=n;~i;--i)mr[i]=min(mr[i],mr[i+1]);
	for(int i=1;i<=n;++i)puts(mr[L[0][i]-1]<=R[0][i]+1?"Possible":"Impossible");
	return 0;
}

F - Prefix Median

翻译

给定一个长度为\(2n-1\)的数列\(a_i\),定义数列\(b_i\)\((a_1,a_2,...,a_{2i-1})\)的中位数。

现在对于\(a\)的任意一种排列,有多少种本质不同的\(b\)

对于\(10^9+7\)取模。

\(n\le 50\)

题解

一个\(2^{2n-1}\)的想法是状压已经用过了哪些\(a_i\),每次选出两个进行更新就可以了。

考虑每次加入两个数,中位数会怎么变化。讨论一下,要么加入一个比中位数大的和一个比中位数小的,然后中位数不变,要么加入两个比中位数小或者大的,改变成前驱或者后继。

因此,我们得到两个结论:1.\(b_i\)的取值范围一定在排序之后的\(a[i]\)\(a[2n-i]\)之间。2.不存在\(i,j,i<j\),满足\(b_j<b_i<b_{j+1}\)或者\(b_j>b_i>b_{j+1}\)

我们唯一知道的状态就是\(b_n\)一定是唯一确定的,那么我们考虑反过来做,设\(f[i][l][r]\)表示当前考虑到\(b_i\),还有\(l\)个数\(\le b_{i+1}\),有\(r\)个数\(\gt b_{i+1}\),并且这些数都可以作为当前\(b_i\)的可能取值的方案数。

那么我们的初值就是\(f[n][1][0]=1\),确定一下两侧范围自然扩大,能否把左右选择的范围变大,增量记为\(pl,pr\)

每次转移的时候枚举一下选择范围内的哪一个数作为接下来的\(b_i\),更新一下\(l,r\)就好了。

这里有一个问题,就是我们是否能否在前面已经确定\(b_{i+1..n}\)的情况下,可以任意选择\(b_i\),答案是能。 我们这样子思考,如果接下来我们要往左挪动,那么我们删去右侧最小的两个数,那么就可以取到接下来可选数中的最大值;如果接下来我们向右挪动,那么我们删去左侧最大的两个数,这样子就可以取到接下来的最小值;如果我们当前位置不变,左侧删去最右的一行、右侧删去最左的一行就可以取到接下来可能的最大最小值。因此我们接下来可以选择任意在\(l,r\)限制内的\(b\)

那么我们现在只需要计算一下方案数就行了。

考虑\(l,r\)怎么更新,假设我们向左选择了第\(L\)个数,那么显然这之间的都要删去,否则就无法直接跳到这一行了(注意这个操作是变为前驱,即中间的所有值都在某一步中被删去了),而右侧的范围自然拓展,再加上原本的位置变为合法位置,所以转移是\(f[i+1][l][r]\rightarrow f[i][l-L+pl][r+pr+(L>1)]\)

如果我们向右选择了第\(R\)个数,那么类似的得到转移\(f[i+1][l][r]\rightarrow f[i][l+pl+1][r+pr-R]\)

两个转移的微小区别在于状态中\(l\)是小于等于,而\(r\)是大于。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MOD 1000000007
#define MAX 101
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,a[MAX],f[MAX][MAX][MAX],ans;
int main()
{
	n=read();for(int i=1;i<n+n;++i)a[i]=read();
	sort(&a[1],&a[n+n]);
	f[n][1][0]=1;
	for(int i=n-1;i;--i)
	{
		int pl=(a[i]!=a[i+1]),pr=(a[n+n-i]!=a[n+n-i-1]);
		for(int l=0;l<=n+n-1;++l)
			for(int r=0;l+r<=n+n-1;++r)
				if(f[i+1][l][r])
				{
					for(int L=1;L<=l+pl;++L)add(f[i][l+pl-L+1][r+pr+(L>1)],f[i+1][l][r]);
					for(int R=1;R<=r+pr;++R)add(f[i][l+pl+1][r+pr-R],f[i+1][l][r]);
				}
	}
	for(int i=0;i<n+n;++i)
		for(int j=0;j<n+n;++j)
			add(ans,f[1][i][j]);
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-04-15 10:48  小蒟蒻yyb  阅读(393)  评论(0编辑  收藏  举报