(ps:本集合為Star_F總結的dp進階知識,持續更新~。 轉載本文章需要聯絡我,否則視為侵權!!)
前置知識:線性dp,揹包,樹形dp,區間dp
內容預覽:
- 狀壓dp
- 數位dp
- dp最佳化(字首和,單調佇列,斜率最佳化)
1. 狀壓dp:
思路:如果題目中 \(n\) 的範圍特別小(\(<=20\)) 大機率可以狀壓dp
狀態:f[i][j]:考慮到前 \(i\) 個,且已考慮的集合為 \(j\) (j為二進位制數,1表示考慮,0表示不考慮)
的集合
(輔助知識:位運算)
例題:
- P1171 售貨員的難題
dp[i][j] 表示從起點到第j號點 且到達時狀態恰好為i的最短路
比較簡單的狀壓dp,之間看程式碼吧:
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
int f[1<<20][20],w[20][20],n;
int main(){
cin>>n;
memset(f,0x3f,sizeof f);
f[1][0]=0;
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
cin>>w[i][j];
for(int i=1;i<(1<<n);i+=2) //列舉狀態
for(int j=0;j<n;j++){ //列舉下一步到達的點
if(!((i >> j) & 1)) continue;
for(int k=0;k<n;k++){ 列舉中介點
if(j==k) continue;
if(!(i>>k &1)) continue;
f[i][j]=min(f[i][j],f[i^(1<<j)][k]+w[k][j]);
}
}
int minn=2e9;
for(int i=0;i<=n-1;++i) minn=min(minn,f[(1<<n)-1][i]+w[i][0]);
cout<<minn<<endl;
return 0;
}
- P1896 [SCOI2005] 互不侵犯
思路:f[i][j][s]就表示在只考慮前i行時,在前i行(包括第i行)有且僅有s個國王,且第i行國王的情況是編號為j的狀態時情況的總數。而k就代表第i-1行的國王情況的狀態編號。
點選檢視程式碼
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;
int sit[2000],gs[2000];
int cnt=0;
int n,yong;
long long f[10][2000][100]={0};
void dfs(int he,int sum,int node)//預處理出每一個狀態
{
if(node>=n)//如果已經處理完畢(注意是大於等於)
{
sit[++cnt]=he;
gs[cnt]=sum;
return;//新建一個狀態
}
dfs(he,sum,node+1);//不用第node個
dfs(he+(1<<node),sum+1,node+2);//用第node個,此時node要加2,及跳過下一個格子
}
int main()
{
scanf("%d%d",&n,&yong);
dfs(0,0,0);
for(int i=1;i<=cnt;i++)f[1][i][gs[i]]=1;//第一層的所有狀態均是有1種情況的
for(int i=2;i<=n;i++)
for(int j=1;j<=cnt;j++)
for(int k=1;k<=cnt;k++)//列舉i、j、k
{
if(sit[j]&sit[k])continue;
if((sit[j]<<1)&sit[k])continue;
if(sit[j]&(sit[k]<<1))continue;//排除不合法國王情況
for(int s=yong;s>=gs[j];s--)f[i][j][s]+=f[i-1][k][s-gs[j]];//列舉s,計算f[i][j][s]
}
long long ans=0;
for(int i=1;i<=cnt;i++)ans+=f[n][i][yong];//統計最終答案,記得用long long
printf("%lld",ans);
return 0;
}
2. 數位dp:
以一個數的數位進行dp,一般為求一個區間滿足某些條件的數
通常把區間改為\((1...r) - (1...l-1)\)
例題:
- P2657windy數
遞推做法:
點選檢視程式碼
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 11;
int f[N][10];
void init()
{
for (int i = 0; i <= 9; i ++ ) f[1][i] = 1;
for (int i = 2; i < N; i ++ )
for (int j = 0; j <= 9; j ++ )
for (int k = 0; k <= 9; k ++ )
if (abs(j - k) >= 2)
f[i][j] += f[i - 1][k];
}
int dp(int n)
{
if (!n) return 0;
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0;
int last = -2;
for (int i = nums.size() - 1; i >= 0; i -- )
{
int x = nums[i];
for (int j = i == nums.size() - 1; j < x; j ++ )
if (abs(j - last) >= 2)
res += f[i + 1][j];
if (abs(x - last) >= 2) last = x;
else break;
if (!i) res ++ ;
}
for (int i = 1; i < nums.size(); i ++ )
for (int j = 1; j <= 9; j ++ )
res += f[i][j];
return res;
}
int main()
{
init();
int l, r;
cin >> l >> r;
cout << dp(r) - dp(l - 1) << endl;
return 0;
}
- CF628D
遞迴做法,列舉到那一位,現在的屬性,有沒有什麼限制
點選檢視程式碼
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 1e9+7;
ll m,d,a[2005],len,f[2005][2005];
char l[2005],r[2005];
ll dfs(int u,int sum,int yaoqiu){
if(u>len) return sum==0?1:0;
if(!yaoqiu&&f[u][sum]!=-1) return f[u][sum];
ll tmp=0,maxx=yaoqiu==1?a[u]:9;
if(u%2==1){
for(int i=0;i<=maxx;i++)
if(i!=d) tmp=(tmp+dfs(u+1,(sum*10+i)%m,yaoqiu&&(i==maxx)))%mod;
}
else{
for(int i=0;i<=maxx;i++)
if(i==d) tmp=(tmp+dfs(u+1,(sum*10+i)%m,yaoqiu&&(i==maxx)))%mod;
}
if(!yaoqiu) f[u][sum]=tmp;
return tmp;
}
ll dp(char *s){
memset(f,-1,sizeof(f));
len=strlen(s+1);
for(int i=1;i<=strlen(s+1);i++) a[i]=s[i]-'0';
return dfs(1,0,1);
}
bool check(char *s){
len=strlen(s+1);
int x=0;
for(int i=1;i<=len;i++){
int y=s[i]-'0';
x=(x*10+y)%m;
if(i&1){
if(y==d)
return false;
}
else{
if(y!=d)
return false;
}
}
return !x;
}
int main(){
cin>>m>>d>>l+1>>r+1;
cout<<(dp(r)-dp(l)+check(l)+mod)%mod;
return 0;
}