回退背包
引入
对于一些转移方程类似的dp,我们可以先将整体的值算出来,再一个个反推改变后的值
讲解
如果我们一个个讨论不选的物品,那么就是朴素的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;
}

浙公网安备 33010602011771号