KEYENCE Programming Contest 2021

Contest Link

C - Robot on Grid

给定一个 \(H\times W\) 的格子,其中 \(k\) 个填了 R , D , X 中的一个,R 只能往右走,D 只能往右走,X 两边都能走。剩下的空格能填入三个中的任意一个。对于每种填写方案,求出 \((1,1)\)\((H,W)\) 的路径数对 \(998244353\) 取模的结果之和。

Solution

一开始脑子抽了,一直在讨论一个格子从上面和左边继承的方式怎么算,并胡了个假的:格子方案数×走过来的方案数(0/1)×另一个继承格(如果存在)的填法(1/3) ,这个东西能过小样例但是显然在两个继承格都是待填写时会挂。

其实是想复杂了。直接考虑每个格子的可达性计算,如果是已经填了显然就是 \(0/1\) ,否则是 \(2/3\) 。然后最后再乘上所有待填格的填写数就好了。注意要预处理逆元……不然 \(\mathcal{O}(n^2\log)\) 拿头跑罢。

//Author: RingweEH
const int N=5e3+10;
const int Mod=998244353;
int mp[N][N],n,m,k;     //-1:填写 0:X 1:R 2:D
int f[N][N];
char s[5];

int power( int a,int b )
{
    int res=1;
    for ( ; b; b>>=1,a=1ll*a*a%Mod )
        if ( b&1 ) res=1ll*res*a%Mod;
    return res;
}

void Add( int &a,int b ) { a=(1ll*a+b>Mod) ? a+b-Mod : a+b; }
 
int main()
{
    n=read(); m=read(); k=read();
    int i,j,x,y;
    for ( i=1; i<=n; i++ )
        for ( j=1; j<=m; j++ )
            mp[i][j]=-1;
    for ( i=1; i<=k; i++ )
    {
        x=read(),y=read(); scanf( "%s",s+1 );
        if ( s[1]=='X' ) mp[x][y]=0;
        else if ( s[1]=='R' ) mp[x][y]=1;
        else mp[x][y]=2;
    }

    memset( f,0,sizeof(f) ); f[1][1]=1; int inv=1ll*power(3,Mod-2)*2ll%Mod;
    for ( i=1; i<=n; i++ )
        for ( j=1; j<=m; j++ )
        {
            if ( mp[i][j]==2 ) Add( f[i+1][j],f[i][j] );
            else if ( mp[i][j]==1 ) Add( f[i][j+1],f[i][j] );
            else if ( mp[i][j]==0 ) Add( f[i+1][j],f[i][j] ),Add( f[i][j+1],f[i][j] );
            else Add( f[i+1][j],1ll*f[i][j]*inv%Mod ),Add( f[i][j+1],1ll*f[i][j]*inv%Mod );
        }
    f[n][m]=1ll*f[n][m]*power(3,1*n*m-k)%Mod;
    printf( "%d\n",f[n][m] );

    return 0;
}

D - Choosing Up Sides

\(2^N\) 个玩家,用尽可能少的操作次数,每次操作将所有人分成两队,使得最后

  • 存在一个整数 \(n\) ,对于每一对 \(1\leq i<j\leq 2^N\)\(i,j\) 在同一队中恰好 \(n\) 次。
  • 存在一个整数 \(m\) ,对每一对 \(1\leq i<j\leq 2^N\)\(i,j\) 在不同的队中恰好 \(m\) 次。

\(1\leq N\leq 8\) ,给出方案。

Solution

结论 :答案为 \(2^N-1\) .

Proof

每进行一轮,属于不同组别的数对个数增加 \(4^{N-1}\) 个。一共进行了 \(n+m\) 轮,所以 \(\displaystyle (n+m)\cdot 4^{N-1}=m\binom{2^N}{2}\) .

所以有 \(n:m=2^{N-1}-1:2^{N-1}\) ,所以轮数一定是 \(2^N-1\) 的倍数。

构造方案

在第 \(i\) 轮的时候,对于 \(j\) ,如果 \(count(i\And j)\bmod 2=0\) ,那么就让 \(j\)\(A\) 队,否则去 \(B\) 队。其中 \(count\) 表示二进制下 \(1\) 的个数。

容易证明这样构造一定合法。 但是真的想不到啊/kel

//Author:RingweEH
int n,k;
int calc( int x )
{
	int res=0;
	for ( ; x; x-=((x)&(-x)) ) res++;
	return res;
}

int main()
{
	n=read(); int cnt=1<<n;
	printf( "%d\n",cnt-1 );
	for ( int i=1; i<cnt; i++ )
	{
		for ( int j=0; j<cnt; j++ )
			if ( calc(i&j)&1 ) printf( "B" );
			else printf( "A" );
		printf( "\n" );
	}

	return 0;
}

E - Greedy Ant

数轴上有 \(N\) 颗糖,第 \(i\) 颗在 \(2i\) ,有权值 \(a_i\) 。初始时 Ant\(1,3,\cdots ,2N+1\) 中选一个站好。

Snuke 先手,选择任意一颗糖拿走。Ant 每次在左右两颗最近的糖中选一颗权值更大的拿走。

对于 Ant 的每个位置,求出 Snuke 能得到的最大值。

Solution

转化规则:Snuke 每回合只拿走 \(a_l,a_r\) 的一个,或者把机会保留。

\(f[l][r][k]\) 表示 \((l,r)\) 已经拿完,Snuke 已经拿了 \(k\) 次的最大值。但是这样要枚举起点,复杂度 \(\mathcal{O}(n^4)\) 显然不行。

状态数似乎很难减少,那么着眼点就在 枚举起点 上。

考虑是不是能够不枚举一遍完成?

不妨设 \(f[l][r][k]\) 表示 \((l,r)\) 还没有拿,Snuke 还能拿 \(k\) 次的最大值。如果起点在 \(2i,2(i+1)\) 之间,那么答案就是 \(f[i][i+1][0]\) .

边界就是 \(f[0][n+1][(n+1)/2]=0\) .

//Author:RingweEH
const int N=410;
int n,a[N],f[N][N][N];

int main()
{
	n=read(); 
	for ( int i=1; i<=n; i++ )
		a[i]=read();

	memset( f,-1,sizeof(f) );
	f[0][n+1][(n+1)/2]=0;
	for ( int len=n+2; len>=3; len-- )
		for ( int l=0; l+len-1<=n+1; l++ )
		{
			int r=l+len-1;
			for ( int k=0; k<=(n+1)/2; k++ )
			{
				int nw=f[l][r][k];
				if ( nw<0 ) continue;
				if ( 2*(k-1)<len-1 ) 
					bmax( f[l+1][r][k-1],nw+a[l+1] ),bmax( f[l][r-1][k-1],nw+a[r-1] );
				if ( 2*k<len-1 )
				{
					if ( a[l+1]>a[r] ) bmax( f[l+1][r][k],nw );
					if ( a[r-1]>a[l] ) bmax( f[l][r-1][k],nw );
				}
			}
		}

	for ( int i=0; i<=n; i++ )
		printf( "%d\n",f[i][i+1][0] );

	return 0;
}
posted @ 2021-01-18 16:15  MontesquieuE  阅读(85)  评论(0)    收藏  举报