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

【AtCoder】AtCoder Grand Contest 050 解题报告(A~D)

点此进入比赛

A:AtCoder Jumper(点此看题面

  • \(n\)个点,要求构造一张有向图。
  • 你可以从每个点连出两条边,要求任意两点之间的最短路不超过\(10\)
  • \(n\le10^3\)

签到题

发现\(2^{10}=1024\),所以我们只要对于每个点\(i\)连出\(2i\)\(2i+1\)(模\(n\)意义下)两条边,就符合题意了。

代码:\(O(n)\)

#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
using namespace std;
int n;
int main()
{
	RI i;for(scanf("%d",&n),i=1;i<=n;++i) printf("%d %d\n",(2*i-1)%n+1,(2*i)%n+1);return 0;//连出2i和2i+1两条边
}

B:Three Coins(点此看题面

  • 有一个长度为\(n\)的序列,每个位置有一个权值。
  • 每次你可以选择连续三个空位置各放一个硬币,或者选择连续三个有硬币的位置移除它们的硬币。
  • 你可以进行任意次操作,要求最大化最终有硬币的位置的权值和。
  • \(n\le500\)

发掘性质

首先,我们发现如果一个位置\(i\)右边三个位置都为空,我们可以先在\(i+1\sim i+3\)放上硬币,然后移除\(i\sim i+2\)的硬币,就实现了把一个位置上的硬币向右移动三格的操作(同理我们也可以实现把一个位置上的硬币向左移动三格的操作)。

由于这个移动过程不能碰到其他硬币,所以如果我们初始选择了三个位置放上硬币并通过一波操作将它们移到三个合适的位置上,那么这个序列就被划分成了四个区间,由于硬币不能越过边界,因此每个区间都相当于是一个子问题。

所以设\(f_{i,j}\)表示区间\([i,j]\)的答案,于是有两种转移:

  • 枚举一个位置把区间分成两部分,即\(f_{i,j}=f_{i,k}+f_{k+1,j}\)
  • 满足\(j-i\equiv2(mod\ 3)\),则对于所有满足\(k-i\equiv1(mod\ 3)\)\(k\),可以把\(i,j,k\)视为一次操作选择的三个点,得到\(f_{i,j}=a_i+f_{i+1,k-1}+a_k+f_{k+1,j-1}+a_j\)

代码:\(O(n^3)\)

#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 500
using namespace std;
int n,a[N+5],f[N+5][N+5];
int main()
{
	RI i,j,k,l;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);
	for(l=1;l<=n;++l) for(i=1,j=l;j<=n;++i,++j)//区间DP
	{
		for(k=i;k^j;++k) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);//枚举一个位置把区间分成两部分
		if((j-i)%3==2) for(k=i+1;k<=j;k+=3)//枚举k,满足i,k,j模3依次加1
			f[i][j]=max(f[i][j],a[i]+f[i+1][k-1]+a[k]+f[k+1][j-1]+a[j]);//把i,j,k视为一次操作选择的三个点
	}return printf("%d\n",f[1][n]),0;
}

C:Block Game(点此看题面

  • 一根数轴,有一个人初始在原点处。
  • 你每次操作可以任选一个位置放一个障碍物,对手每次操作可以把人移动到相邻的一个空格子。
  • 如果某次你操作完之后人左右格子都是障碍物时你获胜。
  • 给出操作序列,有一些位置空缺,问有多少种填法满足你能获胜。
  • \(n\le10^6\)

最优策略的思考

考虑两个人的最优策略。

显然,对于你,一定会选择人左右两边的某一个格子,最小化人还能行走的空间。

相对地,对手一定会尽可能走到序列的中间,来最大化你操作后他还能行走的区间。

于是我们发现,假设人上一次能够连着走有效的\(t\)步,那么他就能获得一个大小为\(t+1\)的活动空间,则这一次他的有效步数最多只能是\(\lfloor\frac t2\rfloor\)步。

逆向思维

发现直接做我们还需要记录上一次走的有效步数,复杂度爆炸。

但考虑最终有多少有效步数实际上都是没意义的,只要能有\(1\)步你就输了。

所以我们从后往前枚举,假设在这次你操作之后你还会操作\(j\)次,那么在你的上次操作和这次操作之间对手就至少要有\(2^j\)个有效步数才能让你输。

由于\(j\)的大小是\(O(logn)\)的,所以我们可以设立状态\(f_{i,j}\)进行\(DP\)

如果你操作了就需要转移到\(f_{i-2^j-1,j+1}\)(转移条件是\([i-2^j-1,i-1]\)都可以由对手操作),如果对手操作相对于是无效步数直接转移到\(f_{i-1,j}\)

代码:\(O(nlogn)\)

#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 1000000
#define LN 20
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
#define X 998244353
using namespace std;
int n,g[N+5],f[N+5][LN+5];char s[N+5];
int main()
{
	RI i,j,x;for(scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i) g[i]=g[i-1]+(s[i]=='B');//统计B的前缀和
	for(f[n][0]=1,i=n;i;--i)
	{
		if(s[i]^'S') for(j=0;j^LN;++j) g[x=max(0,i-(1<<j)-1)]==g[i-1]&&Inc(f[x][j+1],f[i][j]);//要求前2^j个位置全填S
		if(s[i]^'B') for(j=0;j<=LN;++j) Inc(f[i-1][j],f[i][j]);//多余的步数没用,直接转移
	}
	RI t=1;for(i=1;i<=n;++i) s[i]=='?'&&(t=2LL*t%X);//统计总方案数
	RI v=0;for(j=0;j<=LN;++j) Inc(v,f[0][j]);return printf("%d\n",(t-v+X)%X),0;//总方案数-输的方案数
}

D:Shopping(点此看题面

  • \(n\)个人和\(k\)个物品,每个还没有获胜的人轮流操作。
  • 每个人操作时会选择一个他没有选过的物品,如果这个物品还存在,他就会拿走这个物品并获胜。
  • 求每个人获胜的概率。
  • \(n,k\le40\)

概率\(DP\)

感觉作为\(D\)题来说有些偏水了吧?

考虑有用的信息就是这个人已经尝试选过的物品个数\(l\)已经被拿走的物品个数\(x\)之后还没有获胜的人数\(p\),于是设\(f_{l,x,p}\)表示在这种情况下这个人最后输掉的概率。

通过这些信息我们实际上还可以推出之前还没有获胜的人数\(n-p-x-1\)

转移的时候我们枚举在这个人下次选择之前新增的获胜人数\(i\),其中在他之后的获胜人数\(j\)

首先,这个人这次一定不能选中,这个概率是\(\frac{x-l}{k-l}\)(注意,尝试选过的物品个数\(l\)一定被包含在已经被拿走的物品个数\(x\)中)。

然后,如果我们设\(g_{l,m,x,y}\)表示对于\(m\)个已经尝试选过\(l\)个物品的人,经过他们之后获胜人数从\(x\)变成\(y\)的概率,则达成\(i,j\)的概率就是\(g_{l,p,x,x+j}\times g_{l+1,n-p-x-1,x+j,x+i}\)

\(g_{l,m,x,y}\)的转移应该是非常简单的,无非考虑当前人是否获胜,分别以\(\frac{k-y+1}{k-l}\)的概率从\(g_{l,m-1,x,y-1}\)转移,以\(\frac{y-l}{k-l}\)的概率从\(g_{l,m-1,x,y}\)转移。

最后对于每个位置\(i\),我们枚举在他之前已经获胜的人数\(j\),那么概率就是\(\sum g_{0,i-1,0,j}\times f_{0,j,n-i}\)

代码:\(O(n^5)\)

#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 40
#define X 998244353
using namespace std;
int n,k,a[N+5],Inv[N+5],f[N+5][N+5][N+5],g[N+5][N+5][N+5][N+5];
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;}
int main()
{
	RI i,j,l,x,p;for(scanf("%d%d",&n,&k),i=1;i<=k;++i) Inv[i]=QP(i,X-2);
	for(l=0;l<=k;++l) for(x=l;x<=k;++x) for(g[l][0][x][x]=1,i=1;i<=n;++i) for(j=x;j<=k;++j)//动态规划预处理出g
		g[l][i][x][j]=((j?1LL*(k-j+1)*g[l][i-1][x][j-1]:0)+1LL*(j-l)*g[l][i-1][x][j])%X*Inv[k-l]%X;
	for(l=k;~l;--l) for(x=l;x<=k;++x) for(p=0;x+p+1<=n;++p)//动态规划求出f
	{
		if(x==k) {f[l][x][p]=1;continue;}//特判x=k
		for(i=0;x+i<=k;++i) for(j=max(i-(n-p-x-1),0);j<=min(i,p);++j) f[l][x][p]=//枚举i,j
			(1LL*(x-l)*Inv[k-l]%X*g[l][p][x][x+j]%X*g[l+1][n-p-x-1][x+j][x+i]%X*f[l+1][x+i][p-j]+f[l][x][p])%X;//转移
	}
	RI t;for(i=1;i<=n;printf("%d\n",(1-t+X)%X),++i)//枚举每个位置
		for(t=j=0;j<=k;++j) t=(1LL*g[0][i-1][0][j]*f[0][j][n-i]+t)%X;return 0;//计算答案
}
posted @ 2021-04-01 10:41  TheLostWeak  阅读(127)  评论(0编辑  收藏  举报