动态规划报告(树形DP+概率DP

这篇具有很好参考价值的文章主要介绍了动态规划报告(树形DP+概率DP。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态规划报告

树形dp

树形 DP,即在树上进行的 DP。由于树固有的递归性质,树形 DP 一般都是递归进行的。一般需要在遍历树的同时维护所需的信息

以一道题目为例

2022CCPC桂林站G Group Homework

No, we don’t want group homework. It’s the place where 1+1<1 can be true. However, you are currently the leader of a group with three members. Luckily, your group members will write everything down for you since you are the prestigious leader. Unluckily, you have to assign the new algorithm homework to your two team members, Mr. Dian and Mr. Beng, who can’t understand the ideas of each other correctly and mess up all the details in the cooperation.

The new homework is about a tree: there are n vertices on the tree with n−1 bidirectional edges connecting them. Each vertex i is a problem with a score of ai. You can assign a tree path to each member, then Mr. Dian and Mr. Beng will solve the problems on their path independently. The final score of the homework is decided by the sum of the scores of the vertices visited by exactly one member — as we mentioned before, if both of them solved one problem independently, they will argue a lot about who is right, and all the effort will be in vain.

Now, you — Mr. Ji — want to maximize the final score (as well as the chance not to drop out of school due to too low GPA). Make a wise choice!

题意为求树上两条链的不相交部分的最大权值和

题解

考虑两条链的关系,我们可以证明至多交于一点。若交于两点以上,我们可以修改为更大的答案。

树形概率dp,动态规划,算法

所以可以确定要么是两条不相交的链,要么交于一点

  • 对于两条不相交链,可以直接分类讨论
  • 对于交于一点,可以枚举交点,用换根DP维护四条该点为根时的权值最大链

m x [ x ] [ i ] mx[x][i] mx[x][i]:x为起点的权值最大的四条链

d p [ x ] [ 0 ] dp[x][0] dp[x][0]:x为根节点子树中两条不相交链的最大和
d p [ x ] [ 1 ] dp[x][1] dp[x][1]:x为根节点子树中最大的一条链 (不一定要经过x)
d p [ x ] [ 2 ] dp[x][2] dp[x][2]:x为根节点子树中一条由x到叶子节点的链 加上一条与之不相交的链的最大和
d p [ x ] [ 3 ] dp[x][3] dp[x][3]:x为根节点子树中的一条 不经过x 的最大链
d p [ x ] [ 4 ] dp[x][4] dp[x][4]:x为根节点子树中一条由x到叶子节点的最大链

树形概率dp,动态规划,算法
在具体实现时有很多细节,需要结合代码来理解

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e5+3;

int n;
int a[maxn];
vector<int> t[maxn];
ll dp[maxn][5],mx[maxn][5];
ll mx4;//最长的四条链之和 
/*
mx[x][i]:x为起点的权值最大的四条链
dp[x][0]:x为根节点子树中两条不相交链的最大和
dp[x][1]:x为根节点子树中最大的一条链 __(不一定要经过x)__
dp[x][2]:x为根节点子树中一条由x到叶子节点的链 加上一条与之不相交的链的最大和
dp[x][3]:x为根节点子树中的一条 __不经过x__ 的最大链
dp[x][4]:x为根节点子树中一条由x到叶子节点的最大链
*/ 

void dfs(int x,int fa){
    dp[x][0]=dp[x][1]=dp[x][2]=dp[x][4]=a[x];
    dp[x][3]=0;
    for(auto y:t[x]){
        if(y==fa)continue;
        dfs(y,x);
		//由于有些状态转移不能在自己的一些状态转移之后转移 故需要注意转移状态的顺序
        dp[x][0]=max(dp[x][0],dp[y][0]);
        dp[x][0]=max(dp[x][0],dp[x][4]+dp[y][2]);//连x-y
        dp[x][0]=max(dp[x][0],dp[x][2]+dp[y][4]);//连x-y
        dp[x][0]=max(dp[x][0],dp[x][1]+dp[y][1]);//不连x-y
        
        dp[x][1]=max(dp[x][1],dp[y][1]);
        dp[x][1]=max(dp[x][1],dp[y][4]+dp[x][4]);

        dp[x][2]=max(dp[x][2],dp[y][2]+a[x]);
		dp[x][2]=max(dp[x][2],dp[x][4]+dp[y][1]);
		dp[x][2]=max(dp[x][2],dp[x][3]+dp[y][4]+a[x]);
		
        dp[x][3]=max(dp[x][3],dp[y][1]);

        dp[x][4]=max(dp[x][4],dp[y][4]+a[x]);
    }
}
void dfs4(int x,int fa,ll fmx){//fmx维护父节点除x节点外最长链加上a[fa]的值
    mx[x][1]=mx[x][2]=mx[x][3]=mx[x][4]=0;
    for(auto y:t[x]){
        if(y==fa)continue;
        if(dp[y][4]>=mx[x][1]){
            mx[x][4]=mx[x][3];
            mx[x][3]=mx[x][2];
            mx[x][2]=mx[x][1];
            mx[x][1]=dp[y][4];
        }
        else if(dp[y][4]>=mx[x][2]){
            mx[x][4]=mx[x][3];
            mx[x][3]=mx[x][2];
            mx[x][2]=dp[y][4];
        }
        else if(dp[y][4]>=mx[x][3]){
            mx[x][4]=mx[x][3];
            mx[x][3]=dp[y][4];
        }
        else if(dp[y][4]>=mx[x][4]){
            mx[x][4]=dp[y][4];
        }
    }
    if(fmx>=mx[x][1]){
        mx[x][4]=mx[x][3];
        mx[x][3]=mx[x][2];
        mx[x][2]=mx[x][1];
        mx[x][1]=fmx;
    }
    else if(fmx>=mx[x][2]){
        mx[x][4]=mx[x][3];
        mx[x][3]=mx[x][2];
        mx[x][2]=fmx;
    }
    else if(fmx>=mx[x][3]){
        mx[x][4]=mx[x][3];
        mx[x][3]=fmx;
    }
    else if(fmx>=mx[x][4]){
        mx[x][4]=fmx;
    }

    mx4=max(mx4,mx[x][1]+mx[x][2]+mx[x][3]+mx[x][4]);

    for(auto y:t[x]){
        if(y==fa) continue;
        if(dp[y][4]==mx[x][1]) dfs4(y,x,max(mx[x][2],fmx)+a[x]);
        else dfs4(y,x,max(mx[x][1],fmx)+a[x]);
    }
}

void solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
    if(n==1){
        cout<<0;
        return ;
    }//特判1个节点
    for(int i=1;i<n;++i){
    	int u,v;
		cin>>u>>v;
		t[u].push_back(v);
		t[v].push_back(u); 
    }
    dfs(1,0);
    dfs4(1,0,0);
    cout<<max(dp[1][0],mx4);
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T=1;
//	cin>>T; 
	while(T--){
		solve();
	}
    return 0;
}

在桂林G题目中使用到了换根DP的方法,接下来介绍一下换根DP的一些用法

换根DP

树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。

通常需要两次 DFS,第一次 DFS 预处理诸如深度,点权和之类的信息,在第二次 DFS 开始运行换根动态规划。

还是以一道题目为例 [POI2008] STA-Station

题目描述

给定一个 n n n 个点的树,请求出一个结点,使得以这个结点为根时,所有结点的深度之和最大。
一个结点的深度之定义为该节点到根的简单路径上边的数量。
如果有多个结点符合要求,输出任意一个即可。
对于全部的测试点,保证 1 ≤ n ≤ 1 0 6 , 1 ≤ u , v ≤ n 1 \leq n \leq 10^6,1\leq u, v\leq n 1n106,1u,vn,给出的是一棵树。

题解

树形概率dp,动态规划,算法

s i z [ x ] siz[x] siz[x] 记录以1为根时x子树大小,先通过一次dfs预处理出 s i z siz siz 数组
d p [ x ] dp[x] dp[x] 记录以 x为根 时所有节点的总深度

d p [ x ] → d p [ y ] dp[x]\rightarrow dp[y] dp[x]dp[y] 的转移即为换根的过程。显然在换根的转移过程中,以 y y y 为根或以 x x x 为根会导致其子树中的结点的深度产生改变。
其中y的子树结点深度都减少一,其余节点深度增加一,即转移方程为
d p [ y ] = d p [ x ] − s i z [ y ] + n − s i z [ y ] = d p [ x ] + n − 2 × s i z [ y ] dp[y]=dp[x]-siz[y]+n-siz[y]=dp[x]+n-2 \times siz[y] dp[y]=dp[x]siz[y]+nsiz[y]=dp[x]+n2×siz[y]

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e6+3;

int n;
ll siz[maxn],dp[maxn],dep[maxn],ans=0;
vector<int> t[maxn];

void pre(int x,int fa){
	dep[x]=dep[fa]+1;
	siz[x]=1;
	for(auto y:t[x]){
		if(y==fa) continue ;
		pre(y,x);
		siz[x]+=siz[y];
	}
}

void dfs(int x,int fa){
	ans=max(ans,dp[x]);
	for(auto y:t[x]){
		if(y==fa) continue ;
		dp[y]=dp[x]+n-2*siz[y];
		dfs(y,x);
	}
}

void solve(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		t[u].push_back(v);
		t[v].push_back(u);
	}
	dep[0]=0;
	pre(1,0);
	for(int i=1;i<=n;i++) dp[1]+=dep[i]-1;
	dfs(1,0);
	for(int i=1;i<=n;i++) if(dp[i]==ans){cout<<i;return ;}
	return ;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T=1;
//	cin>>T; 
	while(T--){
		solve();
	}
    return 0;
}

树上背包

除了换根DP,树上背包也是树形DP中的一种常见问题
树上的背包问题,简单来说就是背包问题与树形 DP 的结合。
洛谷 P2014 CTSC1997 选课 为例

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N N N 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a a a 是课程 b b b 的先修课即只有学完了课程 a a a,才能学习课程 b b b)。一个学生要从这些课程里选择 M M M 门课程学习,问他能获得的最大学分是多少?

输入格式

第一行有两个整数 N , M N , M N,M 用空格隔开。 ( 1 ≤ N ≤ 300 , 1 ≤ M ≤ 300 ) ( 1 \leq N \leq 300, 1 \leq M \leq 300) (1N300,1M300)

接下来的 N N N 行,第 I + 1 I+1 I+1 行包含两个整数 k i k_i ki s i s_i si, k i k_i ki 表示第 I I I门课的直接先修课, s i s_i si 表示第 I I I门课的学分。若 k i = 0 k_i=0 ki=0 表示没有直接先修课( 1 ≤ k i ≤ N , 1 ≤ s i ≤ 20 1\leq k_i \leq N, 1 \leq s_i \leq 20 1kiN,1si20)。

题解

根据题意每门课至多只有一门先修课,那么不妨添加第 0 0 0 门课程作为没有先修课的先修课,这样就可以以先修关系为边,建立一棵0为根的树。需要注意最后统计结果时要统计 m + 1 m+1 m+1 个节点(包含 0 0 0 号根节点)

d p [ x ] [ i ] [ j ] dp[x][i][j] dp[x][i][j] 表示 x x x 号点的子树中前 i i i 棵子树选择 j j j 门课程的最大学分

转移的过程就是在遍历树的过程中进行背包DP的转移。枚举 x x x 的每个子节点 y y y ,同时枚举以 y y y 为根的子树选择了几门课程,将子树的结果合并到 x x x

s i z [ x ] siz[x] siz[x] 表示 x x x 的子树大小,可以写出转移方程

d p [ x ] [ i ] [ j ] = max ⁡ k < j , k < s i z [ y ] d p [ x ] [ i − 1 ] [ j − k ] + d p [ y ] [ s i z [ y ] ] [ k ] dp[x][i][j]=\max _{k<j,k<siz[y]} dp[x][i-1][j-k] + dp[y][siz_{[y]}][k] dp[x][i][j]=maxk<j,k<siz[y]dp[x][i1][jk]+dp[y][siz[y]][k]

其中表示选择前 i i i 棵子树的这一维可以用01背包相同的滚动数组方式省略掉

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e3+10;

int n,m;
int s[maxn],f[maxn];
int dp[maxn][maxn],siz[maxn];
vector<int> t[maxn];

void dfs(int x){
	dp[x][1]=s[x];
	siz[x]=1;
	for(auto y:t[x]){
		dfs(y);
		siz[x]+=siz[y];
		for(int i=m+1;i;i--){
			for(int j=1;j<=siz[y];j++){
				dp[x][i+j]=max(dp[x][i+j],dp[x][i]+dp[y][j]);
			}
		}
	}
}

void solve(){ 
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>f[i]>>s[i];
		t[f[i]].push_back(i);
	}
	dfs(0);
	cout<<dp[0][m+1];
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T=1;
//	cin>>T; 
	while(T--){
		solve();
	}
    return 0;
}

再来看一道树上背包的题目

2022CCPC广州站I. Infection

A highly propagating bacterium infects a tree of n nodes (with n − 1 n−1 n1 edges, no cycles). These nodes are indexed from 1 1 1 to n n n.

Exactly one node will be infected at the beginning. Each node on the tree has an initial susceptibility weight ai, which represents that node i has a probability of a i Σ i = 1 n a i \frac{ a_i }{\Sigma ^n_{i=1}ai} Σi=1naiai to become the initial infected node of the tree.

In addition, each node has an infection probability p i p_i pi, which represents its probability of being infected by adjacent nodes.

Specifically, starting from the initial infected node, if a node is infected, the uninfected node x x x that is adjacent to it will have a probability of p x p_x px to become a new infected node, and then x x x can continue to infect its adjacent nodes.

Now, your task is to calculate the probability that exactly k nodes are eventually infected. You need to output an answer for each k = 1 , … , n . k=1,…,n. k=1,,n.

You need to output the answer modulo 1 0 9 + 7 , 10^9+7, 109+7, which means if your answer is a b ( g c d ( a , b ) = 1 ) \frac a b (gcd(a,b)=1) ba(gcd(a,b)=1) , you need to output a ⋅ b − 1 m o d 1 0 9 + 7 a⋅b^{−1}mod10^9+7 ab1mod109+7, where b ⋅ b − 1 ≡ 1 ( m o d 1 0 9 + 7 ) b⋅b^{-1}\equiv 1 (mod 10^9+7) bb11(mod109+7).

Input

The first line contains an integer n ( 2 ≤ n ≤ 2000 ) n (2\leq n\leq 2000) n(2n2000), denoting the number of nodes of the tree.

The next n − 1 n−1 n1 lines, each line contains two positive integers u u u and v ( 1 ≤ u , v ≤ n ) v (1\leq u,v\leq n) v(1u,vn), denoting that there is an edge ( u , v ) (u,v) (u,v) on the tree.

Next n lines, the i-th line contains three positive integers a i , b i , c i a_i,b_i,c_i ai,bi,ci , where ai means as above and p i = b i c i p_i=\frac{b_i}{c_i} pi=cibi. It is guaranteed that 1 ≤ a i , b i , c i ≤ 1 0 9 , ∑ i = 1 n a i ≤ 1 0 9 , b i ≤ c i , g c d ( b i , c i ) = 1 1\leq a_i,b_i,c_i\leq 10^9,\sum _{i=1}^n a_i\leq 10^9,b_i\leq c_i,gcd(b_i,c_i)=1 1ai,bi,ci109,i=1nai109,bici,gcd(bi,ci)=1.

Output

Output n n n lines, each line contains single integer. The integer on the i i i-th line should be the answer modulo 1 0 9 + 7 10^9+7 109+7 when k = i k=i k=i.

题解

d p [ x ] [ i ] [ 1 / 0 ] dp[x][i][1/0] dp[x][i][1/0] 表示 x x x 的子树感染了 i i i 个点,是否包含初始感染点的概率之和, s i z [ x ] siz[x] siz[x] 表示 x x x 子树大小
由于感染是相邻的,所以 j > 0 j>0 j>0 x x x 点一定是被感染了的点

a [ x ] ∑ a \frac{a[x]}{\sum a} aa[x] x x x 点初始感染概率, p [ x ] = b [ x ] c [ x ] p[x]=\frac{b[x]}{c[x]} p[x]=c[x]b[x] 为被传染概率

所以在初始化背包时 d p [ x ] [ 1 ] [ 0 ] = a [ x ] ∑ a , d p [ x ] [ 1 ] [ 1 ] = p [ x ] dp[x][1][0]=\frac{a[x]}{\sum a},dp[x][1][1]=p[x] dp[x][1][0]=aa[x],dp[x][1][1]=p[x]

d p [ x ] [ 0 ] [ 1 ] dp[x][0][1] dp[x][0][1] 是不合法状态,概率为 0 0 0

d p [ x ] [ 0 ] [ 0 ] = 1 − p [ x ] dp[x][0][0]=1-p[x] dp[x][0][0]=1p[x] ,该子树不被感染只需要该子树根节点 x x x 不被传染

d p [ y ] → d p [ x ] dp[y]\rightarrow dp[x] dp[y]dp[x] 转移时根据初始感染点的位置分三种情况

  1. 初始感染点不在 x x x 子树时
    d p [ x ] [ i + j ] [ 0 ] = ∑ i ≤ s i z [ x ] , j ≤ s i z [ y ] d p [ x ] [ i ] [ 0 ] × d p [ y ] [ j ] [ 0 ] dp[x][i+j][0]=\sum _{i\leq siz[x],j\leq siz[y]}dp[x][i][0]\times dp[y][j][0] dp[x][i+j][0]=isiz[x],jsiz[y]dp[x][i][0]×dp[y][j][0]
  2. 初始感染点在 x x x 子树但不在 y y y 子树时
    d p [ x ] [ i + j ] [ 1 ] = ∑ i ≤ s i z [ x ] , j ≤ s i z [ y ] d p [ x ] [ i ] [ 1 ] × d p [ y ] [ j ] [ 0 ] dp[x][i+j][1]=\sum _{i\leq siz[x],j\leq siz[y]}dp[x][i][1]\times dp[y][j][0] dp[x][i+j][1]=isiz[x],jsiz[y]dp[x][i][1]×dp[y][j][0]
  3. 初始感染点在 y y y 子树时
    d p [ x ] [ i + j ] [ 1 ] = ∑ i ≤ s i z [ x ] , j ≤ s i z [ y ] d p [ x ] [ i ] [ 0 ] × d p [ y ] [ j ] [ 0 ] dp[x][i+j][1]=\sum _{i\leq siz[x],j\leq siz[y]}dp[x][i][0]\times dp[y][j][0] dp[x][i+j][1]=isiz[x],jsiz[y]dp[x][i][0]×dp[y][j][0]

在统计答案时分别统计只感染 x x x 子树的贡献,即 d p [ x ] × ( 1 − p [ f a ] ) dp[x]\times (1-p[fa]) dp[x]×(1p[fa])

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e3+10;
const ll mod=1e9+7;

int n;
int siz[maxn];
ll a[maxn],b[maxn],c[maxn],p[maxn],suma;
ll dp[maxn][maxn][2],ans[maxn];
vector<int> t[maxn];

ll inv(ll x){
	ll k=mod-2,base=x;
	ll ans=1;
	while(k){
		if(k&1) (ans*=base)%=mod;
		(base*=base)%=mod;
		k>>=1;
	}
	return ans;
}
ll f[maxn][2];
void dfs(int x,int fa){
	dp[x][1][0]=p[x];
	dp[x][1][1]=a[x]*suma%mod;
	siz[x]=1;
	for(auto y:t[x]){
		if(y==fa) continue ;
		dfs(y,x);
		for(int i=1;i<=siz[x]+siz[y];i++) f[i][1]=f[i][0]=0;
		for(int i=1;i<=siz[x];i++){
			for(int j=0;j<=siz[y];j++){
				(f[i+j][0]+=dp[x][i][0]*dp[y][j][0])%=mod;
				(f[i+j][1]+=dp[x][i][1]*dp[y][j][0])%=mod;
				if(j) (f[i+j][1]+=dp[x][i][0]*dp[y][j][1])%=mod;
			}
		}
		siz[x]+=siz[y];
		for(int i=1;i<=siz[x];i++){
			dp[x][i][0]=f[i][0];
			dp[x][i][1]=f[i][1];
		}
	}
	for(int i=1;i<=siz[x];i++){
		(ans[i]+=(1-p[fa]+mod)%mod*dp[x][i][1])%=mod;
	}
	dp[x][0][0]=(1-p[x]+mod)%mod;
}

void solve(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		t[u].push_back(v);
		t[v].push_back(u);
	}
	suma=0;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i]>>c[i];
		suma+=a[i];
		p[i]=b[i]*inv(c[i])%mod;
	}
	suma=inv(suma%mod);
	dfs(1,0);
	for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T=1;
//	cin>>T; 
	while(T--){
		solve();
	}
    return 0;
}

概率DP

DP求概率

这类题目采用顺推,也就是从初始状态推向结果。同一般的 DP 类似的,难点依然是对状态转移方程的刻画,只是这类题目经过了概率论知识的包装。

Codeforces 148 D Bag of mice 为例

The dragon and the princess are arguing about what to do on the New Year’s Eve. The dragon suggests flying to the mountains to watch fairies dancing in the moonlight, while the princess thinks they should just go to bed early. They are desperate to come to an amicable agreement, so they decide to leave this up to chance.

They take turns drawing a mouse from a bag which initially contains w white and b black mice. The person who is the first to draw a white mouse wins. After each mouse drawn by the dragon the rest of mice in the bag panic, and one of them jumps out of the bag itself (the princess draws her mice carefully and doesn’t scare other mice). Princess draws first. What is the probability of the princess winning?

If there are no more mice in the bag and nobody has drawn a white mouse, the dragon wins. Mice which jump out of the bag themselves are not considered to be drawn (do not define the winner). Once a mouse has left the bag, it never returns to it. Every mouse is drawn from the bag with the same probability as every other one, and every mouse jumps out of the bag with the same probability as every other one.

Input

The only line of input data contains two integers w w w and b ( 0   ≤   w ,   b   ≤   1000 ) b (0 \leq w, b \leq 1000) b(0 w,b 1000).

Output

Output the probability of the princess winning. The answer is considered to be correct if its absolute or relative error does not exceed 1 0 − 9 10^{-9} 109.

题目大意:袋子里有 w w w 只白鼠和 b b b 只黑鼠,公主和龙轮流从袋子里抓老鼠。谁先抓到白色老鼠谁就赢,如果袋子里没有老鼠了并且没有谁抓到白色老鼠,那么算龙赢。公主每次抓一只老鼠,龙每次抓完一只老鼠之后会有一只老鼠跑出来。每次抓的老鼠和跑出来的老鼠都是随机的。公主先抓。问公主赢的概率。

题解

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 轮到公主 时袋子里有 i i i 只白鼠, j j j 只黑鼠,公主赢的概率。对抓到的老鼠进行分类讨论,考虑转移方程。

  • 公主抓到白鼠,公主获胜。 d p [ i ] [ j ] + = i i + j dp[i][j]+=\frac{i}{i+j} dp[i][j]+=i+ji
  • 公主抓到黑鼠,龙抓到白鼠,龙赢了。无贡献
  • 都抓到黑鼠,跑了一只黑鼠, d p [ i ] [ j ] ← d p [ i ] [ j − 3 ] , d p [ i ] [ j ] + = j i + j × j − 1 i + j − 1 × j − 2 i + j − 2 × d p [ i ] [ j − 3 ] dp[i][j] \leftarrow dp[i][j-3] , dp[i][j]+=\frac{j}{i+j}\times \frac{j-1}{i+j-1}\times \frac{j-2}{i+j-2}\times dp[i][j-3] dp[i][j]dp[i][j3],dp[i][j]+=i+jj×i+j1j1×i+j2j2×dp[i][j3]
  • 都抓到黑鼠,跑了一只白鼠, d p [ i ] [ j ] ← d p [ i − 1 ] [ j − 2 ] , d p [ i ] [ j ] + = j i + j × j − 1 i + j − 1 × i i + j − 2 × d p [ i − 1 ] [ j − 2 ] dp[i][j] \leftarrow dp[i-1][j-2] , dp[i][j]+=\frac{j}{i+j}\times \frac{j-1}{i+j-1}\times \frac{i}{i+j-2}\times dp[i-1][j-2] dp[i][j]dp[i1][j2],dp[i][j]+=i+jj×i+j1j1×i+j2i×dp[i1][j2]
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e3+10;

double dp[maxn][maxn];
void solve(){
	int w,b;
	cin>>w>>b;
	for(int i=1;i<=w;i++) dp[i][0]=1;
	for(int i=1;i<=b;i++) dp[0][i]=0;
	for(int i=1;i<=w;i++){
		for(int j=1;j<=b;j++){
			dp[i][j]+=(double)i/(i+j);
			if(j>=3) dp[i][j]+=(double)j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2)*dp[i][j-3];
			if(i>=1&&j>=2) dp[i][j]+=(double)j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2)*dp[i-1][j-2];
		}
	}
	cout<<fixed<<setprecision(10)<<dp[w][b];
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T=1;
//	cin>>T; 
	while(T--){
		solve();
	} 
	return 0;
}

DP求期望

POJ2096 Collecting Bugs

Description

Ivan is fond of collecting. Unlike other people who collect post stamps, coins or other material stuff, he collects software bugs. When Ivan gets a new program, he classifies all possible bugs into n categories. Each day he discovers exactly one bug in the program and adds information about it and its category into a spreadsheet. When he finds bugs in all bug categories, he calls the program disgusting, publishes this spreadsheet on his home page, and forgets completely about the program.

Two companies, Macrosoft and Microhard are in tight competition. Microhard wants to decrease sales of one Macrosoft program. They hire Ivan to prove that the program in question is disgusting. However, Ivan has a complicated problem. This new program has s subcomponents, and finding bugs of all types in each subcomponent would take too long before the target could be reached. So Ivan and Microhard agreed to use a simpler criteria — Ivan should find at least one bug in each subsystem and at least one bug of each category.

Macrosoft knows about these plans and it wants to estimate the time that is required for Ivan to call its program disgusting. It’s important because the company releases a new version soon, so it can correct its plans and release it quicker. Nobody would be interested in Ivan’s opinion about the reliability of the obsolete version.

A bug found in the program can be of any category with equal probability. Similarly, the bug can be found in any given subsystem with equal probability. Any particular bug cannot belong to two different categories or happen simultaneously in two different subsystems. The number of bugs in the program is almost infinite, so the probability of finding a new bug of some category in some subsystem does not reduce after finding any number of bugs of that category in that subsystem.

Find an average time (in days of Ivan’s work) required to name the program disgusting.

Input

Input file contains two integer numbers, n n n and s ( 0 < n , s ≤ 1000 ) s (0 < n, s \leq 1 000) s(0<n,s1000).

Output

Output the expectation of the Ivan’s working days needed to call the program disgusting, accurate to 4 digits after the decimal point.

题目大意:一个软件有 s s s 个子系统,会产生 n n n 种 bug。某人一天发现一个 bug,这个 bug 属于某种 bug 分类,也属于某个子系统。每个 bug 属于某个子系统的概率 1 s \frac{1}{s} s1,属于某种 bug 分类的概率是 1 n \frac{1}{n} n1。求发现 n n n 种 bug,且 s s s 个子系统都找到 bug 的期望天数。

题解

d p [ i ] [ j ] dp[i][j] dp[i][j] 为已经找到了 i i i 种 bug,属于 j j j 种分类时的期望天数。显然 d p [ n ] [ s ] = 0 dp[n][s]=0 dp[n][s]=0,题目所求答案为 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]

对新发现的 bug 进行分类讨论,考虑状态转移

  • 若该 bug 种类已知,分类已知, d p [ i ] [ j ] → d p [ i ] [ j ] dp[i][j]\rightarrow dp[i][j] dp[i][j]dp[i][j]:概率为 i n × j s \frac{i}{n}\times\frac{j}{s} ni×sj
  • 若该 bug 种类已知,分类未知, d p [ i ] [ j ] → d p [ i ] [ j + 1 ] dp[i][j]\rightarrow dp[i][j+1] dp[i][j]dp[i][j+1]:概率为 i n × s − j s \frac{i}{n}\times\frac{s-j}{s} ni×ssj
  • 若该 bug 种类未知,分类已知, d p [ i ] [ j ] → d p [ i + 1 ] [ j ] dp[i][j]\rightarrow dp[i+1][j] dp[i][j]dp[i+1][j]:概率为 n − i n × j s \frac{n-i}{n}\times\frac{j}{s} nni×sj
  • 若该 bug 种类未知,分类未知, d p [ i ] [ j ] → d p [ i + 1 ] [ j + 1 ] dp[i][j]\rightarrow dp[i+1][j+1] dp[i][j]dp[i+1][j+1]:概率为 n − i n × s − j s \frac{n-i}{n}\times\frac{s-j}{s} nni×ssj

由期望的线性性质,可以得到方程

d p [ i ] [ j ] = i n × j s × ( d p [ i ] [ j ] + 1 ) + i n × s − j s × ( d p [ i ] [ j + 1 ] + 1 ) + n − i n × j s × ( d p [ i + 1 ] [ j ] + 1 ) + n − i n × s − j s × ( d p [ i + 1 ] [ j + 1 ] + 1 ) dp[i][j]=\frac{i}{n}\times\frac{j}{s}\times (dp[i][j]+1)+\frac{i}{n}\times\frac{s-j}{s}\times (dp[i][j+1]+1)+\frac{n-i}{n}\times\frac{j}{s}\times (dp[i+1][j]+1)+\frac{n-i}{n}\times\frac{s-j}{s}\times (dp[i+1][j+1]+1) dp[i][j]=ni×sj×(dp[i][j]+1)+ni×ssj×(dp[i][j+1]+1)+nni×sj×(dp[i+1][j]+1)+nni×ssj×(dp[i+1][j+1]+1)

化简后可得 d p [ i ] [ j ] = i n × s − j s × d p [ i ] [ j + 1 ] + n − i n × j s × d p [ i + 1 ] [ j ] + n − i n × s − j s × d p [ i + 1 ] [ j + 1 ] + 1 1 − i n × j s dp[i][j]=\frac{\frac{i}{n}\times\frac{s-j}{s}\times dp[i][j+1]+\frac{n-i}{n}\times\frac{j}{s}\times dp[i+1][j]+\frac{n-i}{n}\times\frac{s-j}{s}\times dp[i+1][j+1]+1}{1-\frac{i}{n}\times\frac{j}{s}} dp[i][j]=1ni×sjni×ssj×dp[i][j+1]+nni×sj×dp[i+1][j]+nni×ssj×dp[i+1][j+1]+1
= i × ( s − j ) × d p [ i ] [ j + 1 ] + ( n − i ) × i × d p [ i + 1 ] [ j ] + ( n − i ) × ( s − j ) × d p [ i + 1 ] [ j + 1 ] + n ∗ s n × s − i × j =\frac{i\times(s-j)\times dp[i][j+1]+(n-i)\times i\times dp[i+1][j]+(n-i)\times(s-j)\times dp[i+1][j+1] + n*s}{n\times s-i\times j} =n×si×ji×(sj)×dp[i][j+1]+(ni)×i×dp[i+1][j]+(ni)×(sj)×dp[i+1][j+1]+ns

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e3+10;

double dp[maxn][maxn];
void solve(){
	int n,s;
	cin>>n>>s;
	for(int i=n;i>=0;i--){
		for(int j=s;j>=0;j--){
			if(i==n&&j==s) continue ;
			dp[i][j]=(double)(i*(s-j)*dp[i][j+1]+(n-i)*j*dp[i+1][j]+(n-i)*(s-j)*dp[i+1][j+1]+n*s)/(n*s-i*j);
		}
	}
	cout<<fixed<<setprecision(10)<<dp[0][0];
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T=1;
//	cin>>T; 
	while(T--){
		solve();
	} 
	return 0;
}

可以看出,与DP概率相比,DP求期望在得到期望的方程后往往还需要进行一些变化才能变成最终的转移方程

Codeforces Round #848 (Div. 2) D. Flexible String Revisit

You are given two binary strings a a a and b b b of length n n n. In each move, the string a a a is modified in the following way.

An index i ( 1 ≤ i ≤ n ) i (1\leq i\leq n) i(1in) is chosen uniformly at random. The character ai will be flipped. That is, if a i a_i ai is 0 0 0, it becomes 1 1 1, and if a i a_i ai is 1 1 1, it becomes 0 0 0.
What is the expected number of moves required to make both strings equal for the first time?

A binary string is a string, in which the character is either 0 0 0 or 1 1 1.

Input

The first line contains a single integer t ( 1 ≤ t ≤ 1 0 5 ) t (1\leq t\leq 10^5) t(1t105) — the number of test cases. The description of the test cases follows.

The first line of each test case contains a single integer n ( 1 ≤ n ≤ 1 0 6 ) n (1\leq n\leq 10^6) n(1n106) — the length of the strings.

The second line of each test case contains the binary string a a a of length n n n.

The third line of each test case contains the binary string b b b of length n n n.

It is guaranteed that the sum of n over all test cases does not exceed 1 0 6 10^6 106.

Output

For each test case, output a single line containing the expected number of moves modulo 998244353 998244353 998244353.

Formally, let M = 998244353 M=998244353 M=998244353. It can be shown that the answer can be expressed as an irreducible fraction p q \frac{p}{q} qp, where p p p and q q q are integers and q ≡ 0 ( m o d M ) q \equiv 0(modM) q0(modM). Output the integer equal to p ⋅ q − 1 m o d M p⋅q^{−1}modM pq1modM. In other words, output such an integer x x x that 0 ≤ x ⪇ M 0\leq x\lneq M 0xM and x ⋅ q ≡ p ( m o d M ) x⋅q≡p(modM) xqp(modM).

题解

d p [ i ] dp[i] dp[i] 表示有 i i i 个位置不同时的期望
显然 d p [ 0 ] = 0 dp[0]=0 dp[0]=0 已经到达目标状态, d p [ n ] = 1 + d p [ n − 1 ] dp[n]=1+dp[n-1] dp[n]=1+dp[n1] 所有位置都不同

考虑对于 d p [ i ] dp[i] dp[i]

  • 若选择不同的位置, d p [ i ] → d p [ i − 1 ] dp[i]\rightarrow dp[i-1] dp[i]dp[i1] ,概率为 i n \frac{i}{n} ni
  • 若选择相同的位置, d p [ i ] → d p [ i + 1 ] dp[i]\rightarrow dp[i+1] dp[i]dp[i+1] ,概率为 n − i n \frac{n-i}{n} nni

可以得到 d p [ i ] = i n × ( d p [ i − 1 ] + 1 ) + n − i n × ( d p [ i + 1 ] + 1 ) dp[i]=\frac{i}{n}\times (dp[i-1]+1)+\frac{n-i}{n}\times (dp[i+1]+1) dp[i]=ni×(dp[i1]+1)+nni×(dp[i+1]+1)

x = i − 1 x=i-1 x=i1 化简可得转移方程 d p [ x ] = n × d p [ x − 1 ] − x × d p [ x − 2 ] − n n − x − 1 dp[x]=\frac{n\times dp[x-1]-x\times dp[x-2]-n}{n-x-1} dp[x]=nx1n×dp[x1]x×dp[x2]n
但是这个方程得转移需要两个初始状态 d p [ 0 ] dp[0] dp[0] d p [ 1 ] dp[1] dp[1],而计算出 d p [ 1 ] = 2 n − 1 dp[1]=2^n-1 dp[1]=2n1 并不容易

CF官方题解给出的办法是设 d p [ i ] = a [ i ] + b [ i ] × d p [ i + 1 ] dp[i]=a[i]+b[i]\times dp[i+1] dp[i]=a[i]+b[i]×dp[i+1]

d p [ i ] = i n × ( d p [ i − 1 ] + 1 ) + n − i n × ( d p [ i + 1 ] + 1 ) dp[i]=\frac{i}{n}\times (dp[i-1]+1)+\frac{n-i}{n}\times (dp[i+1]+1) dp[i]=ni×(dp[i1]+1)+nni×(dp[i+1]+1) 式中的 d p [ i − 1 ] dp[i-1] dp[i1] 替换为 a [ i − 1 ] + b [ i − 1 ] × d p [ i ] a[i-1]+b[i-1]\times dp[i] a[i1]+b[i1]×dp[i] ,从而得到 d p [ i ] → d p [ i + 1 ] dp[i]\rightarrow dp[i+1] dp[i]dp[i+1] 的方程
d p [ i ] = i n × ( a [ i − 1 ] + b [ i − 1 ] × d p [ i ] ) + n − i n × d p [ i + 1 ] + 1 dp[i]=\frac{i}{n}\times (a[i-1]+b[i-1]\times dp[i])+\frac{n-i}{n}\times dp[i+1]+1 dp[i]=ni×(a[i1]+b[i1]×dp[i])+nni×dp[i+1]+1

化简后得到 d p [ i ] = a [ i − 1 ] × i + n n − i × b [ i − 1 ] + n − i n − i × b [ i − 1 ] × d p [ i + 1 ] dp[i]=\frac{a[i-1]\times i+n }{n-i\times b[i-1]}+\frac{n-i}{n-i\times b[i-1]}\times dp[i+1] dp[i]=ni×b[i1]a[i1]×i+n+ni×b[i1]ni×dp[i+1]

已知 d p [ 0 ] = 0 dp[0]=0 dp[0]=0 所以有 d p [ 1 ] = 1 + n − 1 n × d p [ 2 ] dp[1]=1+\frac{n-1}{n}\times dp[2] dp[1]=1+nn1×dp[2]
可以得到 a [ 1 ] = 1 , b [ 1 ] = n − 1 n a[1]=1,b[1]=\frac{n-1}{n} a[1]=1,b[1]=nn1

再通过 a [ i ] = a [ i − 1 ] × i + n n − i × b [ i − 1 ] , b [ i ] = n − i n − i × b [ i − 1 ] a[i]=\frac{a[i-1]\times i+n }{n-i\times b[i-1]},b[i]=\frac{n-i}{n-i\times b[i-1]} a[i]=ni×b[i1]a[i1]×i+n,b[i]=ni×b[i1]ni,就可以求得 a [ i ] , b [ i ] ( 2 ≤ i ≤ n ) a[i],b[i](2\leq i\leq n) a[i],b[i](2in)

由于无法求得 a [ 0 ] a[0] a[0] b [ 0 ] b[0] b[0],就仍然无法得到 d p [ 0 ] → d p [ 1 ] dp[0]\rightarrow dp[1] dp[0]dp[1]

观察 d p [ n ] = 1 + d p [ n − 1 ] dp[n]=1+dp[n-1] dp[n]=1+dp[n1],与之前类似地可以设 d p [ i ] = c [ i ] + d [ i ] × d p [ i − 1 ] dp[i]=c[i]+d[i]\times dp[i-1] dp[i]=c[i]+d[i]×dp[i1]
d p [ i ] = i n × ( d p [ i − 1 ] + 1 ) + n − i n × ( d p [ i + 1 ] + 1 ) dp[i]=\frac{i}{n}\times (dp[i-1]+1)+\frac{n-i}{n}\times (dp[i+1]+1) dp[i]=ni×(dp[i1]+1)+nni×(dp[i+1]+1) 式中的 d p [ i + 1 ] dp[i+1] dp[i+1] 替换为 c [ i + 1 ] + d [ i + 1 ] × d p [ i ] c[i+1]+d[i+1]\times dp[i] c[i+1]+d[i+1]×dp[i],从而得到 d p [ i ] → d p [ i − 1 ] dp[i]\rightarrow dp[i-1] dp[i]dp[i1] 的方程
d p [ i ] = i n × d p [ i − 1 ] + n − i n × ( c [ i + 1 ] + d [ i + 1 ] × d p [ i ] ) + 1 dp[i]=\frac{i}{n}\times dp[i-1]+\frac{n-i}{n}\times(c[i+1]+d[i+1]\times dp[i])+1 dp[i]=ni×dp[i1]+nni×(c[i+1]+d[i+1]×dp[i])+1

化简得 d p [ i ] = n + ( n − i ) × c [ i + 1 ] n − ( n − i ) × d [ i + 1 ] + i n − ( n − i ) × d [ i + 1 ] × d p [ i − 1 ] dp[i]=\frac{n+(n-i)\times c[i+1]}{n-(n-i)\times d[i+1]}+\frac{i}{n-(n-i)\times d[i+1]}\times dp[i-1] dp[i]=n(ni)×d[i+1]n+(ni)×c[i+1]+n(ni)×d[i+1]i×dp[i1]

类似得,通过 c [ n ] = 1 , d [ n ] = 1 c[n]=1,d[n]=1 c[n]=1,d[n]=1 c [ i ] = n + ( n − i ) × c [ i + 1 ] n − ( n − i ) × d [ i + 1 ] , d [ i ] = i n − ( n − i ) × d [ i + 1 ] c[i]=\frac{n+(n-i)\times c[i+1]}{n-(n-i)\times d[i+1]},d[i]=\frac{i}{n-(n-i)\times d[i+1]} c[i]=n(ni)×d[i+1]n+(ni)×c[i+1],d[i]=n(ni)×d[i+1]i,可以求得 c [ i ] , d [ i ] c[i],d[i] c[i],d[i]

{ d p [ i ] = a [ i − 1 ] × i + n n − i × b [ i − 1 ] + n − i n − i × b [ i − 1 ] × d p [ i + 1 ] d p [ i ] = n + ( n − i ) × c [ i + 1 ] n − ( n − i ) × d [ i + 1 ] + i n − ( n − i ) × d [ i + 1 ] × d p [ i − 1 ] \begin{cases} dp[i]=\frac{a[i-1]\times i+n }{n-i\times b[i-1]}+\frac{n-i}{n-i\times b[i-1]}\times dp[i+1]\\ dp[i]=\frac{n+(n-i)\times c[i+1]}{n-(n-i)\times d[i+1]}+\frac{i}{n-(n-i)\times d[i+1]}\times dp[i-1] \end{cases} {dp[i]=ni×b[i1]a[i1]×i+n+ni×b[i1]ni×dp[i+1]dp[i]=n(ni)×d[i+1]n+(ni)×c[i+1]+n(ni)×d[i+1]i×dp[i1]

解方程组可得 d p [ i ] = c [ i ] + d [ i ] × a [ i − 1 ] 1 − d [ i ] × b [ i − 1 ] dp[i]=\frac{c[i]+d[i]\times a[i-1]}{1-d[i]\times b[i-1]} dp[i]=1d[i]×b[i1]c[i]+d[i]×a[i1]

由于 d p [ 1 ] = c [ 1 ] + d [ 1 ] × d p [ 0 ] = c [ 1 ] dp[1]=c[1]+d[1]\times dp[0]=c[1] dp[1]=c[1]+d[1]×dp[0]=c[1],代入 d p [ i ] = c [ i ] + d [ i ] × a [ i − 1 ] 1 − d [ i ] × b [ i − 1 ] dp[i]=\frac{c[i]+d[i]\times a[i-1]}{1-d[i]\times b[i-1]} dp[i]=1d[i]×b[i1]c[i]+d[i]×a[i1] 可以得到 a [ 0 ] + b [ 0 ] = 0 a[0]+b[0]=0 a[0]+b[0]=0
又因为 d p [ 0 ] = a [ 0 ] + b [ 0 ] × d p [ 1 ] dp[0]=a[0]+b[0]\times dp[1] dp[0]=a[0]+b[0]×dp[1],即 a [ 0 ] + b [ 0 ] ∗ c [ 1 ] = 0 a[0]+b[0]*c[1]=0 a[0]+b[0]c[1]=0

联立得
{ 当 n = 1 时, c [ 1 ] = 1 , a [ 0 ] , b [ 0 ] 为任意实数 当 n ≠ 1 时, a [ 0 ] = 0 , b [ 0 ] = 0 \begin{cases} 当 n=1 时,c[1]=1 , a[0],b[0]为任意实数\\ 当 n\neq 1 时,a[0]=0,b[0]=0 \end{cases} {n=1时,c[1]=1,a[0],b[0]为任意实数n=1时,a[0]=0,b[0]=0

所以可以把 a [ 0 ] = 0 , b [ 0 ] = 0 a[0]=0,b[0]=0 a[0]=0,b[0]=0作为初始化

code
#include <bits/stdc++.h>
using namespace std;

#define mod 998244353 
#define N 1000005
 
template<int MOD>
struct ModInt {
  unsigned x;
  ModInt() : x(0) { }
  ModInt(signed sig) : x(sig) {  }
  ModInt(signed long long sig) : x(sig%MOD) { }
  int get() const { return (int)x; }
  ModInt pow(ll p) { ModInt res = 1, a = *this; while (p) { if (p & 1) res *= a; a *= a; p >>= 1; } return res; }
 
  ModInt &operator+=(ModInt that) { if ((x += that.x) >= MOD) x -= MOD; return *this; }
  ModInt &operator-=(ModInt that) { if ((x += MOD - that.x) >= MOD) x -= MOD; return *this; }
  ModInt &operator*=(ModInt that) { x = (unsigned long long)x * that.x % MOD; return *this; }
  ModInt &operator/=(ModInt that) { return (*this) *= that.pow(MOD - 2); }
 
  ModInt operator+(ModInt that) const { return ModInt(*this) += that; }
  ModInt operator-(ModInt that) const { return ModInt(*this) -= that; }
  ModInt operator*(ModInt that) const { return ModInt(*this) *= that; }
  ModInt operator/(ModInt that) const { return ModInt(*this) /= that; }
  bool operator<(ModInt that) const { return x < that.x; }
  friend ostream& operator<<(ostream &os, ModInt a) { os << a.x; return os; }
};
typedef ModInt<998244353> mint;
 
mint a[N], b[N], c[N], d[N];
 
 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
 
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
 
        string s1, s2;
        cin >> s1 >> s2;
 
        int diff = 0;
        for(int i = 0; i < n; i++){
            diff += s1[i]!=s2[i];
        }
 
        c[n] = 1, d[n] = 1, a[1] = 1, b[1] = ((mint) n-1)/n;
 
        for(int i = 2; i <= n; i++){
            a[i] = ((mint) n + a[i-1]*i)/((mint) n - b[i-1]*i);
            b[i] = ((mint) n-i)/((mint) n - b[i-1]*i);
        }
 
        for(int i = n-1; i >= 1; i--){
            c[i] = ((mint) n + c[i+1]*(n-i))/((mint) n - d[i+1]*(n-i));
            d[i] = (mint) i / ((mint) n - d[i+1]*(n-i));
        }
 
        mint ans = (c[diff]+d[diff]*a[diff-1])/((mint) 1 - d[diff]*b[diff-1]);
 
        cout << ans << endl;
    }
 
    return 0;
}

换一种倒推的DP方式将更加简单
d p [ i ] dp[i] dp[i] 为从 i i i 个不同位置到 i − 1 i-1 i1 个不同位置的期望

  • 若选择不同位置,期望为 i n \frac{i}{n} ni
  • 若选择相同位置,期望为 n − i n × ( 1 + d p [ i + 1 ] + d p [ i ] ) \frac{n-i}{n}\times(1+dp[i+1]+dp[i]) nni×(1+dp[i+1]+dp[i])
    意为这次选择到达了 d p [ i + 1 ] dp[i+1] dp[i+1] 的状态,需要再选 d p [ i + 1 ] dp[i+1] dp[i+1] 次回到 d p [ i ] dp[i] dp[i]

可以直接得到转移方程
d p [ i ] = i n + n − i n × ( d p [ i ] + d p [ i + 1 ] + 1 ) dp[i]=\frac{i}{n}+\frac{n-i}{n}\times(dp[i]+dp[i+1]+1) dp[i]=ni+nni×(dp[i]+dp[i+1]+1)
d p [ i ] = ( n − i ) × d p [ i + 1 ] + n i dp[i]=\frac{(n-i)\times dp[i+1]+n}{i} dp[i]=i(ni)×dp[i+1]+n
初始状态 d p [ n ] = 1 dp[n]=1 dp[n]=1 ,因为所有位置都不同
最后的结果即为 ∑ 1 k d p [ i ] \sum^k_1 dp[i] 1kdp[i] k k k 为不同位置数文章来源地址https://www.toymoban.com/news/detail-530064.html

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e6+10;
const ll mod=998244353;

ll dp[maxn];

ll getinv(ll x){
	ll k=mod-2;
	ll inv=1;
	while(k){
		if(k&1) (inv*=x)%=mod;
		(x*=x)%=mod;
		k>>=1;
	}
	return inv;
}

void solve(){
	int n;
	string a,b;
	cin>>n>>a>>b;
	int dif=0;
	for(int i=0;i<n;i++) if(a[i]!=b[i]) dif++;
	dp[n]=1;
	for(int i=n-1;i>=0;i--){
		dp[i]=(n+(n-i)*dp[i+1])%mod*getinv(i)%mod;
	}
	ll ans=0;
	for(int i=1;i<=dif;i++) (ans+=dp[i])%=mod;
	cout<<ans<<endl;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T=1;
	cin>>T; 
	while(T--){
		solve();
	} 
	return 0;
}

到了这里,关于动态规划报告(树形DP+概率DP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 动态规划-树形DP

    链接:https://www.acwing.com/problem/content/848/ 给定一颗树,树中包含 n n n 个结点(编号 1 ∼ n 1 sim n 1 ∼ n )和 n − 1 n-1 n − 1 条无向边。 请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 重心定义:重心是指树中的一个结点,如果将这个点删除后,剩

    2024年02月08日
    浏览(37)
  • 动态规划——树形DP 学习笔记

    前置知识:树基础。 树形 DP,即在树上进行的 DP,最常见的状态表示为 (f_{u,cdots}) ,表示以 (u) 为根的子树的某个东东。 本文将讲解一些经典题目(树的子树个数、树的最大独立集、树的最小点覆盖、树的最小支配集、树的直径、树的重心、树的中心),以及一些常见形

    2024年02月08日
    浏览(37)
  • 动态规划树形DP课后习题蓝桥舞会

      蓝桥舞会 题目描述 蓝桥公司一共有n名员工,编号分别为1~n。 他们之间的关系就像一棵以董事长为根的树,父节点就是子节点的直接上司。每个员工有一个快乐指数aj。 现蓝桥董事会决定举办一场蓝桥舞会来让员工们在工作之余享受美好时光,不过对于每个员工,他们都

    2024年02月21日
    浏览(43)
  • 动态规划-概率DP

    https://www.luogu.com.cn/problem/CF148D 袋子里有 w w w 只白鼠和 b b b 只黑鼠 ,A和B轮流从袋子里抓,谁先抓到白色谁就赢。A每次随机抓一只,B每次随机抓完一只之后会有另一只随机老鼠跑出来。如果两个人都没有抓到白色则B赢。A先抓,问A赢的概率。 输入 一行两个数 w , b w,b w , b 。

    2024年02月08日
    浏览(25)
  • 动态规划day09(打家劫舍,树形dp)

    目录 198.打家劫舍 看到题目的第一想法 看到代码随想录之后的想法 自己实现过程中遇到的困难 213.打家劫舍II 看到题目的第一想法 看到代码随想录之后的想法 自己实现过程中遇到的困难 337.打家劫舍 III(树形dp) 看到题目的第一想法 看到代码随想录之后的想法 自己实现过程中

    2024年01月23日
    浏览(91)
  • 11.动态规划:树形DP问题、树上最大独立集、树上最小支配集、换根DP、树上倍增(LCA)【灵神基础精讲】

    回溯和树形DP的区别(什么时候需要return结果?):对于回溯,通常是在「递」的过程中增量地构建答案,并在失败时能够回退,例如八皇后。对于递归,是把原问题分解为若干个相似的子问题,通常会在「归」的过程中有一些计算。如果一个递归能考虑用记忆化来优化,就

    2024年02月04日
    浏览(40)
  • 【LeetCode动态规划#11】打家劫舍系列题(涉及环结构和树形DP的讨论)

    力扣题目链接(opens new window) 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非

    2023年04月21日
    浏览(36)
  • DP算法:动态规划算法

    (1)确定初始状态 (2)确定转移矩阵,得到每个阶段的状态,由上一阶段推到出来 (3)确定边界条件。 蓝桥杯——印章(python实现) 使用dp记录状态,dp[i][j]表示买i张印章,凑齐j种印章的概率 i表示买的印章数,j表示凑齐的印章种数 情况一:如果ij,不可能凑齐印章,概

    2024年02月07日
    浏览(49)
  • 算法——动态规划(DP)——递推

    动态规划常用于解决优化问题。 动态规划通常以自底向上或自顶向下的方式进行求解。 自底向上的动态规划从最简单的子问题开始,逐步解决更复杂的问题,直到达到原始问题。 自顶向下的动态规划则从原始问题出发,分解成子问题,并逐步求解这些子问题。 动态规划算法

    2024年01月20日
    浏览(55)
  • ★动态规划(DP算法)详解

    什么是动态规划:动态规划_百度百科 内容太多了不作介绍,重点部分是无后效性,重叠子问题,最优子结构。 问S-P1和S-P2有多少种路径数,毫无疑问可以先从S开始深搜两次,S-P1和S-P2找出所有路径数,但是当这个图足够大,那就会超时。 动态规划旨在用 空间换时间 ,我们

    2024年02月04日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包