「JSOI2018」机器人(数数)(DP)

L O J LOJ LOJ 传送门

  • 题解:神仙数数题,从性质入手

  • 结论 1:一条副对角线上的方向相同,因为如果不相同的话就会有格子没有走到或是从两个方向走过来
    结论 2:第 i i i 条对角线与第 i + g c d ( n , m ) i+gcd(n,m) i+gcd(n,m) 条对角线相同,证明咕
    结论 3:有了第 2 个结论,我们可以发现机器人走的路径是循环的,且循环节长度为 g c d ( n , m ) gcd(n,m) gcd(n,m)

  • 我们不妨设循环节长度为 d d d,其中向下走了 i i i 步向右走了 j j j 步,那么纵坐标回到 1 需要 l c m ( i , n ) lcm(i,n) lcm(i,n)个循环节,横坐标会到原点需要 l c m ( j , m ) lcm(j,m) lcm(j,m) 个循环节,所以有等式
    n m g c d ( n , m ) = l c m ( i , n ) ∗ l c m ( j , m ) g c d ( l c m ( i , n ) , l c m ( j , m ) ) \frac{nm}{gcd(n,m)}=\frac{lcm(i,n)*lcm(j,m)}{gcd(lcm(i,n),lcm(j,m))} gcd(n,m)nm=gcd(lcm(i,n),lcm(j,m))lcm(i,n)lcm(j,m)
    显然成立时当且仅当 l c m ( i , n ) = n , l c m ( j , m ) = m lcm(i,n)=n,lcm(j,m)=m lcm(i,n)=n,lcm(j,m)=m
    那么当除 ( 1 , 1 ) (1,1) (1,1) 外的格子全部是 1 时,方案数为
    w a y s = ∑ g c d ( i , d ) = 1 , g c d ( i , n ) = 1 , g c d ( j , m ) = 1 ( d i ) ways=\sum_{gcd(i,d)=1,gcd(i,n)=1,gcd(j,m)=1}\binom{d}{i} ways=gcd(i,d)=1,gcd(i,n)=1,gcd(j,m)=1(id)
    我们考虑 d p dp dp 出有障碍的情况,枚举向下的步数 i i i,那么我们只需要对 ( i + 1 ) ∗ ( j + 1 ) (i+1)*(j+1) (i+1)(j+1) 的矩阵讨论每一种走法经过多少步遇到障碍物,注意到遇到一个障碍物时走到步数可以提前预处理,我们把所有障碍物缩到这一个 ( i + 1 ) ∗ ( j + 1 ) (i+1)*(j+1) (i+1)(j+1) 的矩阵中,那么问题就是对每一条路径,贡献是路径上的最小值
    那么我们统计每一个最小值的出现次数, d p i , j , k dp_{i,j,k} dpi,j,k 表示到 i , j i,j i,j 最小值为 k k k 的出现次数
    复杂度 O ( T n 5 ) O(Tn^5) O(Tn5) 但常数很小

#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
cs int Mod = 998244353;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
void Add(int &a, int b){ a = add(a, b); }
void Mul(int &a, int b){ a = mul(a, b); }
cs int N = 60;
int gcd(int a, int b){ return !b ? a : gcd(b, a%b); }
int T, n, m, mn[N][N]; char mp[N][N];
int work(int dn, int dm){
	static int dp[N][N][N*N];
	dp[1][1][mn[1][1]]=1;
	for(int i=1; i<=dn; i++)
	for(int j=1; j<=dm; j++)
	for(int k=1; k<=n*m; k++) if(dp[i][j][k]){
		if(i+1<=dn) Add(dp[i+1][j][min(k,mn[i+1][j])],dp[i][j][k]);
		if(j+1<=dm) Add(dp[i][j+1][min(k,mn[i][j+1])],dp[i][j][k]);
	}
	int as = 0;
	for(int i=1; i<=n*m; i++) Add(as, mul(i,dp[dn][dm][i]));
	for(int i=1; i<=dn; i++) for(int j=1; j<=dm; j++)
	for(int k=1; k<=n*m; k++) dp[i][j][k]=0;
	return as;
}
void Solve(){
	n = read(), m = read();
	for(int i=1; i<=n; i++) scanf("%s",mp[i]+1);
	int d = gcd(n, m), as = 0;
	for(int i = 1, j = d-1; i < d; i++, j--){
		if(gcd(i,d) == 1 && gcd(i,n) == 1 && gcd(j,m) == 1){
			for(int l = 1; l <= i+1; l++)
			for(int r = 1; r <= j+1; r++){
				int u=l, v=r, stp = l+r-2; mn[l][r] = n*m;
				while((u^l)||(v^r)||(stp==l+r-2)){
					if(mp[u][v]=='1'){ mn[l][r]=stp; break; }
					u+=i; v+=j; stp+=d; if(u>n) u-=n; if(v>m) v-=m;
				}
			}
			Add(as, work(i+1, j+1));
		}
	} cout << as << '\n';
}
int main(){
	T = read();
	while(T--) Solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值