KEYENCE Programming Contest 2021
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;
}

浙公网安备 33010602011771号