Live2D

Solution -「USACO 2020.12 P」Spaceship

Description

  Link.

  Bessie 在一张含 n 个结点的有向图上遍历,站在某个结点上时,她必须按下自己手中 m 个按钮中处于激活状态的一个才能走向其他结点或终止遍历(不能原地等待)。初始时,所有按钮都处于激活状态,按下 i 号按钮时,i 号按钮变为非激活状态,所有编号 <i 的按钮被激活。

  给定 q 组形如 (bs,s,bt,t) 的询问,求 Bessie 从 s 出发,第一步按 bs 按钮,到 t 终止遍历,且最后一步按 bt 按钮的遍历方案数(遍历顺序或按键不同,方案则不同)。

  n,k,q60

Solution

  大概是图上的高维大力 DP 题叭。

  初步理解按键规则:若把 m 个按键视为一个二进制数,那么在行动过程中这一数的数值是单增的——因为若按键最高非激活位被重新激活,则一定被更高位激活。

  进一步,我们尝试以“非激活按键的最高位”为切入点设计 DP 状态。令 f(h,i,j) 表示从 i 出发(不钦定第一步)走到 j(不钦定最后一步),且非激活按键最高位不超过 h 的方案数。转移:

  • 当前方案根本没有取到过 hf(h,i,j)f(h1,i,j)

  • 否则,枚举取到 h 的唯一一点 k,显然有

    f(h,i,j)(u,k),(k,v)Ef(h1,i,u)f(h1,v,j)

    注意到 hk 正在枚举,视为常数,乘法中的两个状态分别只和 ij 有关,所以只需要定义辅助状态

    g(i)=(u,k)Ef(h1,i,u)h(j)=(k,v)Ef(h1,v,j)

    则有 f(h,i,j)g(i)h(j)


  最后一个问题,求出这个 f 有什么用呢?

  f 的定义与询问的差别仅有是否限制第一步和最后一步,所以可以直接把 q 个限制当做虚拟点丢到状态里,让 f 成为 m×(n+q)×(n+q) 的状态,f(h,i,j) 的含义变为:

  • i,jn:含义不变;
  • inj>n:从 i 出发(不钦定第一步),走到第 jn 个询问的 t 且最后一步为 bt 的方案数;
  • i>njn:从第 i1 个询问的 s 出发,且第一步为 bs,走到 j(不钦定最后一步)的方案数;
  • i>nj>n:同理。

  可见,第 i 个询问的答案即为 f(m,n+i,n+i)。转移过程需要变化的地方仅是当枚举的 (h,k) 恰好为某个询问的某个端点时才给 gh 添加方案。

  综上,复杂度 O(mn(n+q)2),代码极度舒适。

Code

/* Clearink */

#include <cstdio>

#define rep( i, l, r ) for ( int i = l, rpbound##i = r; i <= rpbound##i; ++i )
#define per( i, r, l ) for ( int i = r, rpbound##i = l; i >= rpbound##i; --i )

const int MAXN = 60, MOD = 1e9 + 7;
int n, m, q, f[MAXN + 5][MAXN * 2 + 5][MAXN * 2 + 5];
int lef[MAXN * 2 + 5], rig[MAXN * 2 + 5];
char adj[MAXN + 5][MAXN + 5];
struct Query { int bs, s, bt, t; } qry[MAXN + 5];

inline int mul( const long long a, const int b ) { return a * b % MOD; }
inline int add( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
inline void addeq( int& a, const int b ) { ( a += b ) >= MOD && ( a -= MOD ); }

int main() {
	scanf( "%d %d %d", &n, &m, &q );
	rep ( i, 1, n ) {
		scanf( "%s", adj[i] + 1 );
		rep ( j, 1, n ) adj[i][j] ^= '0';
	}
	rep ( i, 1, q ) {
		scanf( "%d %d %d %d", &qry[i].bs, &qry[i].s, &qry[i].bt, &qry[i].t );
	}
	rep ( h, 1, m ) {
		int ( *fcur )[MAXN * 2 + 5]( f[h] );
		int ( *flas )[MAXN * 2 + 5]( f[h - 1] );
		rep ( i, 1, n + q ) rep ( j, 1, n + q ) fcur[i][j] = flas[i][j];
		rep ( k, 1, n ) {
			rep ( i, 1, n ) lef[i] = rig[i] = 0;
			lef[k] = rig[k] = 1;
			rep ( i, 1, q ) {
				lef[n + i] = qry[i].bs == h && qry[i].s == k;
				rig[n + i] = qry[i].bt == h && qry[i].t == k;
			}
			rep ( i, 1, n + q ) rep ( j, 1, n ) if ( adj[j][k] ) {
				addeq( lef[i], flas[i][j] );
			}
			rep ( i, 1, n ) rep ( j, 1, n + q ) if ( adj[k][i] ) {
				addeq( rig[j], flas[i][j] );
			}
			rep ( i, 1, n + q ) rep ( j, 1, n + q ) {
				addeq( fcur[i][j], mul( lef[i], rig[j] ) );
			}
		}
	}
	rep ( i, 1, q ) printf( "%d\n", f[m][n + i][n + i] );
	return 0;
}

posted @   Rainybunny  阅读(209)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示