回退背包

引入

对于一些转移方程类似的dp,我们可以先将整体的值算出来,再一个个反推改变后的值

讲解

P4141 消失之物

如果我们一个个讨论不选的物品,那么就是朴素的01背包

但这样的复杂度是 \(O(n^2m)\) 的,不能通过此题

正如引言所说,一个物品不选时的转移方程是类似的,因此我们可以先算出所有都可以选的 \(f\),再去推有一个不能选的 \(g\)

那么 \(g\) 怎么得到呢,我们考虑从转移方程下手:

对于一个选定不要的 \(k\),我们令 \(g_{k,i}\) 为体积为 \(i\) 的方案数,那么有 \(f_{i}=g_{k,i} +g_{k,i-w_k}\rarr g_{k,i}=f_i-g_{k,i-w_k}\),那么我们就可以 \(O(m)\) 地求出 \(g\),总复杂度为 \(O(nm)\)

#include<bits/stdc++.h>
using namespace std;
const int N=2005;
int f[N],n,m,w[N],ans[N];
int main(){
    cin>>n>>m;f[0]=1;
    for(int i=1;i<=n;i++) cin>>w[i];
    for(int i=1;i<=n;i++)
        for(int j=m;j>=w[i];j--) f[j]+=f[j-w[i]],f[j]%=10;
    for(int i=1;i<=n;i++){
        for(int j=0;j<w[i];j++) ans[j]=f[j];
        for(int j=w[i];j<=m;j++) 
            ans[j]=(f[j]-ans[j-w[i]]+10)%10;
        for(int j=1;j<=m;j++) cout<<ans[j];cout<<"\n";
    }
    return 0;
}

例一 P14805 [CCPC 2024 哈尔滨站] 弹珠赛跑

我们先考虑暴力的做法

我们枚举一个点作为中位数所在点并枚举出它的位置,那么我们就可以求出到原点的时间 \(t\),然后我们就可以预处理出每个点不能在 \(t\) 时到达原点的起点个数 \(a\),令 \(f_i\) 为 \(t\) 时间时有 \(i\) 个点在原点前的期望,那么显然有转移 \(f_i=f_{i-1}\cdot {a_j\over n}+f_i\cdot {n-a_j\over n}\),复杂度为 \(O(m^3n)\)

优化其实与上一问类似,选出一个点为中位点其实就是让这个点不参与转移,我们令 \(g_j\) 为不选 \(j\) 的期望,那么有 \(f_i=g_{j,i-1}\cdot {a_j\over n}+g_{j,i}\cdot {n-a_j\over n}\rarr g_{j,i}=(f_i- g_{j,i-1}\cdot {a_j\over n})\space* {n\over n-a_j }\)

代码中有有一些小优化,但思路是一致的

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=505,mod=1e9+7;
int x[N],v[N],f[N],n,m,inv[N],invv[N],ans;
struct node{int p,q;};
bool cmp(node a,node b){return x[a.p]*v[b.q]<x[b.p]*v[a.q];}
vector<node> t; 
int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod,b>>=1;
	}
	return res;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){cin>>x[i];x[i]=-x[i],inv[i]=qpow(i,mod-2);}
	sort(x+1,x+n+1);
	for(int i=1;i<=m;i++){cin>>v[i];invv[i]=qpow(v[i],mod-2);}
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) t.push_back(node{i,j});
	f[0]=1;
	sort(t.begin(),t.end(),cmp);
	for(auto [a,b]:t){
		f[0]=f[0]*n%mod*inv[n-a+1]%mod;
		for(int i=1;i<=m;i++) 
			f[i]=(f[i]+(mod-f[i-1]*(a-1)%mod*inv[n]%mod)%mod)%mod*n%mod*inv[n-a+1]%mod;
		ans+=f[m/2]*x[a]%mod*invv[b]%mod;ans%=mod;
		for(int i=m;i;i--) 
			f[i]=f[i]*(n-a)%mod*inv[n]%mod+f[i-1]*a%mod*inv[n]%mod,f[i]%=mod;
		f[0]=f[0]*(n-a)%mod*inv[n]%mod;
	}
	cout<<ans*inv[n]%mod;
	return 0;
}

例二 雀魂

题面

让 \(n\) 个人打一局麻将。

由于麻将规则是复杂的,繁琐的,玄学的,我们可以认为第 \(i\) 个人得点数目在

\(L_i \backsim R_i\) 之间均匀随机。(注意,得点数可以不为整数)

你需要求出:第 \(i\) 个人恰好按照得点数目从小到大排第 \(k\) 名的概率是多少呢?

这时你可能就要问了:如果平局怎么算呢?按首字母排序吗?

注意到随机连续分布,我们可以视为产生平局的概率无穷小,那么我们忽略这种情况。

解析

因为取值为实数,那么显然直接做是不可做的,考虑将区间离散化

那么我们先考虑只有一个区间的情况,此时对于一个点,它排 \(1\backsim n\) 名的概率应该都为 \(1\over n\),

再到多个区间上,对于一个区间,一个节点的贡献就是 \(1\over k\)(\(k\) 为区间中点的数量),对于每一种 \(k\) 的期望是可以dp得到的,然后求不同点的贡献用回退背包即可

具体的说,我们令 \(f_{i,j}\) 为区间左侧有 \(i\) 个点,区间中有 \(j\) 的点的期望,\(g_k\) 为不选 \(k\) 的期望

\(p_1,p_2,p_3\) \(k\) 点在区间左侧,区间中,区间右侧的期望,那么有 \(f_{i,j}=g_{k,i-1,j}*p_1+g_{k,i,j-1}*p_2+g_{k,i,j}*p_3 \rarr \begin{cases} g_{k,i,j}={1\over p_3}\cdot (f_{i,j}-g_{k,i-1,j}*p_1-g_{k,i,j-1}*p_2)\space(p_3\ne0) \\g_{k,i,j}={1\over p_2}\cdot (f_{i,j+1}-f_{i-1,j+1}*p_1) \end{cases}\)

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC opitimize(2)
#define int long long
const int N=205,mod=1e9+9;
int lsh[N<<1],f[N][N],g[N][N],dlt[N],ans[N][N],invl[N],n,m,invi[N];
struct node{int l,r;}a[N];
int read(){
	int k=0,f=1;
	int c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') k=k*10+c-'0',c=getchar();
	return k*f;
}
void out(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else out(x/10),putchar(x%10+'0');
}
int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod,b>>=1;
	}
	return res;
}
int add(int x,int y){x+=y;return x>=mod?x-mod:x;}
int mul(int x,int y){x*=y;return x-x/mod*mod;}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read();m=2*n;
	for(int i=1;i<=n;i++){
		a[i].l=read(),a[i].r=read();
		lsh[i*2-1]=a[i].l,lsh[i*2]=a[i].r;
		invl[i]=qpow(a[i].r-a[i].l,mod-2),invi[i]=qpow(i,mod-2);
	}
	sort(lsh+1,lsh+m+1);
	m=unique(lsh+1,lsh+m+1)-lsh-1;
	for(int t=1;t<m;t++){
		for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) f[i][j]=0;
		f[0][0]=1;
		for(int k=1;k<=n;k++){
			int p1=mul(max(0ll,(min(a[k].r,lsh[t])-a[k].l)),invl[k]),p2=0;
			if(a[k].l<=lsh[t]&&lsh[t]<a[k].r) p2=mul((lsh[t+1]-lsh[t]),invl[k]);
			int p3=add(1,add(mod-p1,mod-p2));
			for(int i=k;~i;i--) 
				for(int j=k;~j;j--){
					int sum=mul(f[i][j],p3);
					if(i) sum=add(sum,mul(f[i-1][j],p1));
					if(j) sum=add(sum,mul(f[i][j-1],p2));
					f[i][j]=sum;
				}
		}
		for(int k=1;k<=n;k++){
			if(!(a[k].l<=lsh[t]&&lsh[t]<a[k].r))continue;
			int p1=mul(max(0ll,(min(a[k].r,lsh[t])-a[k].l)),invl[k]),p2=0;
			if(a[k].l<=lsh[t]&&lsh[t]<a[k].r) p2=mul((lsh[t+1]-lsh[t]),invl[k]);
			int p3=add(1,add(mod-p1,mod-p2));
			for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) g[i][j]=0;
			if(p3){
				int inv=qpow(p3,mod-2);
				for(int i=0;i<=n;i++)
					for(int j=0;j<=n;j++){
						int sum=f[i][j];
						if(i) sum=add(sum,(mod-mul(g[i-1][j],p1)));
						if(j) sum=add(sum,(mod-mul(g[i][j-1],p2)));
						g[i][j]=mul(sum,inv);
					}
			}
			else{
				int inv=qpow(p2,mod-2);
				for(int i=0;i<=n;i++)
					for(int j=0;j<=n;j++){
						int sum=f[i][j+1];
						if(i) sum=add(sum,mod-mul(g[i-1][j+1],p1));
						g[i][j]=mul(sum,inv);
					}
			}
			for(int i=0;i<n;i++)
				for(int j=0;j<n;j++){
					if(!g[i][j]) continue;
					int res=mul(g[i][j],mul(invi[j+1],p2));
					dlt[i+1]=add(dlt[i+1],res),dlt[i+j+2]=add(dlt[i+j+2],(mod-res));
				}
			for(int i=1;i<=n;i++) dlt[i]=add(dlt[i],dlt[i-1]);	
			for(int i=0;i<=n;i++) ans[k][i]=add(ans[k][i],dlt[i]);
			for(int i=0;i<=n+1;i++) dlt[i]=0;
		}
	}
	for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){out(ans[i][j]);putchar(' ');}putchar('\n');}
	return 0;
}

​

posted @ 2026-10-30 14:49  Rainesxiao  阅读(3)  评论(0)    收藏  举报