DP
数位 DP
P13085
介绍一下数位 dp 的模板:
click here
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=something;
int dp[N][][][][][][],digit[N];
int dfs(int pos,int limit,int lead0,something){
if(!pos)
return something;
if(!limit&&!lead0&&dp[pos][last]!=-1)
return dp[pos][last];
int res=0;
for(int i=0;i<=(limit?digit[pos]:9);i++){
if(lead0&&i==0)
res+=dfs(pos-1,limit&&i==(limit?digit[pos]:9),1,11);
do something
}
if(!limit&&!lead0)
dp[pos][last]=res;
return res;
}
int sol(int x){
int cnt=0;
while(x){
digit[++cnt]=x%10,x/=10;
}
return dfs(cnt,1,1,something);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
memset(dp,-1,sizeof dp);
do something
return 0;
}
然后这题照着写即可。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
int l,r;
int dp[N][N],digit[N];
int dfs(int pos,int limit,int lead0,int last){
if(!pos)
return 1;
if(!limit&&!lead0&&dp[pos][last]!=-1)
return dp[pos][last];
int res=0;
for(int i=0;i<=(limit?digit[pos]:9);i++){
if(lead0&&i==0)
res+=dfs(pos-1,limit&&i==(limit?digit[pos]:9),1,11);
else if(abs(last-i)>=2)
res+=dfs(pos-1,limit&&i==(limit?digit[pos]:9),0,i);
}
if(!limit&&!lead0)
dp[pos][last]=res;
return res;
}
int sol(int x){
int cnt=0;
while(x){
digit[++cnt]=x%10,x/=10;
}
return dfs(cnt,1,1,11);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
memset(dp,-1,sizeof dp);
cin>>l>>r;
cout<<sol(r)-sol(l-1);
return 0;
}
注意,数位 dp 有个很好的性质,就是可以方便地枚举比某个数小的数。
树形 DP
P12382
tj.
P12238
tj.
状压 DP
P5911
只有区区 \(16\) 人,很明显的状压。
令 \(dp_i\) 表示过桥状态为 \(i\) 时的最少总时间。
初始 \(dp_0=0\),其余极大;答案就是 \(dp_{2^n-1}\)。
然后考虑转移。因为我并不知道上最后一组的状态是什么,但一定是目前状态的一个子集。所以这里需要枚举一下子集,并且预处理出每组状态的总重量和总时间才方便转移。具体见代码。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int maxw,n;
int w[N],W[N],t[N],T[N];
int dp[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>maxw>>n;
for(int i=0;i<n;i++)
cin>>t[i]>>w[i];
for(int i=0;i<(1ll<<n);i++){
for(int j=0;j<n;j++){
if((i>>j)&1){
W[i]+=w[j],T[i]=max(T[i],t[j]);
}
}
}
memset(dp,0x3f,sizeof dp);
dp[0]=0;
for(int i=0;i<(1<<n);i++){
for(int j=i;j!=0;j=i&(j-1)){
if(W[j]<=maxw){
dp[i]=min(dp[i],dp[i^j]+T[j]);
}
}
}
cout<<dp[(1<<n)-1];
return 0;
}
总结:枚举子集的技巧。
P2396
又是一道明显的状压。
令 \(dp_i\) 表示选卡片的状态为 \(i\) 时的方案数,答案 \(dp_{2^n-1}\),初始 \(dp_0=1\),其余为 \(0\)。
考虑转移。这时,我们发现一个问题。因为厄运数字的位置不能走,所以我必须知道当前的位置才能转移。于是我们需要一个辅助状态 \(pos\) 记录,但如何快速得到 \(pos\)?显然,\(pos\) 需要从当前状态中的一个 \(1\) 中转移而来,于是想到从 lowbit 转移过来即可。同理,dp 转移的时候,也要从上个状态转移而来,而如何快速枚举上一个状态?显然,上一个状态只会比当前状态少走一步,即少一个 \(1\),于是也可以使用 lowbit 优化。
也许你会问,为什么不枚举子集呢?很简单,因为会 T 飞。
实现
#include<bits/stdc++.h>
//#define int long long
using namespace std;
const int N=24;
const int MOD=1e9+7;
int n,m;
int pos[1<<N],dp[1<<N],b[3];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=0;i<n;i++)
cin>>pos[1<<i];
cin>>m;
for(int i=1;i<=m;i++)
cin>>b[i];
dp[0]=1;
for(int i=1;i<(1<<n);i++){
int j=i&-i;
pos[i]=pos[i^j]+pos[j];
bool f=1;
for(int p=1;p<=m;p++){
if(b[p]==pos[i]){
f=0; break;
}
}
if(f){
int t=i;
while(t){
int cur=t&-t;
t^=cur;
dp[i]=(dp[i]+dp[i^cur])%MOD;
}
}
else{
dp[i]=0;
}
}
cout<<dp[(1<<n)-1];
return 0;
}
总结:lowbit 优化状压 dp 的技巧。
P2167
仍然考虑状压 dp,令 \(dp_{i,j}\) 表示第 \(i\) 位 \(n\) 个字符串的匹配情况为 \(j\) 时的方案数。
容易发现越到后面能匹配上 \(T\) 的串会越来越少,所以我写的是扩散形转移,初始 \(dp_{0,2^n-1}=1\),答案 \(dp_{len,j}\),其中 \(j\) 满足二进制下恰有 \(k\) 个 \(1\)。
转移的话,第一条要求不用管,重点考虑第二条。我如何知道前一个状态满足的字符串到当前状态还是否满足呢?这启发我们设计辅助状态 \(sta_{i,j}\) 表示 \(n\) 个字符串第 \(i\) 位上有 \(j\) 字符的状态,这个是很容易预处理出来的。有了这个辅助状态,我们仅需和前一个状态与一下就能得到当前状态了。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=16,M=2e2+5,K=55;
const int MOD=1000003;
int _,n,m,k;
string s[N];
int dp[K][1<<N],sta[K][M];
void sol(){
memset(dp,0,sizeof dp);
memset(sta,0,sizeof sta);
cin>>n>>k;
for(int i=0;i<n;i++)
cin>>s[i];
m=s[1].size();
for(int j=0;j<m;j++){
for(char c='a';c<='z';c++){
for(int i=0;i<n;i++){
if(s[i][j]=='?'||s[i][j]==c)
sta[j][c]|=(1<<i);
}
}
}
dp[0][(1<<n)-1]=1;
for(int i=0;i<m;i++){
for(int j=0;j<(1<<n);j++){
for(char c='a';c<='z';c++)
dp[i+1][sta[i][c]&j]=(dp[i+1][sta[i][c]&j]+dp[i][j])%MOD;
}
}
int ans=0;
for(int i=0;i<(1<<n);i++)
if(__builtin_popcount(i)==k)
ans=(ans+dp[m][i])%MOD;
cout<<ans<<'\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>_;
while(_--)
sol();
return 0;
}
总结:设计辅助状态的思想。

浙公网安备 33010602011771号