AT_dp
AT_dp_a Frog 1
設 \(dp_i\) 表示從 \(1\) 跳到 \(n\) 至少需要多少費用,那么 \(i\) 只能從 \(i-1\) 或 \(i-2\) 跳過來,因此得到
時間復雜度 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100005],dp[100005];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+abs(a[i]-a[i-1]);
if(i>2)dp[i]=min(dp[i],dp[i-2]+abs(a[i]-a[i-2]));
}
cout<<dp[n];
return 0;
}
AT_dp_b Frog 2
看見 \(K\) 非常小,所以可以暴力。
設 \(dp_i\) 表示從 \(1\) 跳到 \(n\) 至少需要多少費用,那么與上題類似
時間復雜度 \(O(NK)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100005],dp[100005],k;
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=2;i<=n;i++){
dp[i]=LONG_LONG_MAX;
for(int j=1;j<=min(i-1,k);j++)dp[i]=min(dp[i],dp[i-j]+abs(a[i]-a[i- j]));
}
cout<<dp[n];
return 0;
}
AT_dp_c Vacation
還是很好想的。設 \(dp_{i,j}\) 表示第 \(i\) 天選第 \(j\) 種事情的最大幸福值,那么
最終答案是 \(\max\{dp_{n,0},dp_{n,1},dp_{n,2}\}\)。
時間復雜度 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100005],b[100005],c[100005],dp[100005][3];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i]>>b[i]>>c[i];
for(int i=1;i<=n;i++){
dp[i][0]=max(dp[i-1][1],dp[i-1][2])+a[i];
dp[i][1]=max(dp[i-1][0],dp[i-1][2])+b[i];
dp[i][2]=max(dp[i-1][0],dp[i-1][1])+c[i];
}
cout<<max({dp[n][0],dp[n][1],dp[n][2]});
return 0;
}
AT_dp_d Knapsack 1
01背包板子。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,W,dp[100005],v[105],w[105],ans;
signed main(){
cin>>n>>W;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)for(int j=W;j>=w[i];j--)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
for(int i=0;i<=W;i++)ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
AT_dp_e Knapsack 2
注意到 \(\sum{v_i}\le 10^5\),所以我們可以設 \(dp_i\) 表示選擇的物品價值總和是 \(i\) 中的花費最小值,然后類似轉移一下就行了,答案也很好求。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,W,w[105],v[105],dp[100005];
signed main(){
cin>>n>>W;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
memset(dp,0x3f,sizeof dp);
dp[0]=0;
for(int i=1;i<=n;i++)for(int j=100000;j>=v[i];j--)dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
for(int i=100000;i;i--)if(dp[i]<=W){
cout<<i;
return 0;
}
}
AT_dp_f LCS
求兩個字符串的最長公共子序列是很簡單的,我們只需要想如何輸出最長公共子序列就行了。
實際上可以找到最大的 \(dp_{i,j}\) 對應的 \(i\) 和 \(j\),接著逆著 dp 的方向進行倒退,就能求出最長公共子序列了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[3005][3005],ans;
string s,t,anss;
signed main(){
cin>>s>>t;
for(int i=0;i<s.size();i++)for(int j=0;j<t.size();j++){
if(s[i]==t[j])dp[i+1][j+1]=dp[i][j]+1;
else dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
}
for(int i=1;i<=s.size();i++)for(int j=1;j<=t.size();j++)ans=max(ans,dp[i][j]);
for(int i=1;i<=s.size();i++)for(int j=1;j<=t.size();j++)if(dp[i][j]==ans){
int ii=i,jj=j;
while(ii>0&&jj>0){
if(s[ii-1]==t[jj-1])anss+=s[ii-1],ii--,jj--;
else if(dp[ii][jj-1]<dp[ii-1][jj])ii--;
else jj--;
}
reverse(anss.begin(),anss.end());
cout<<anss;
return 0;
}
return 0;
}
AT_dp_g Longest Path
直接 DAG 上 dp。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,u,v,dp[100005],vis[100005],ans;
vector<int> ve[100005];
void dfs(int u){
if(vis[u])return;
vis[u]=1;
for(int v:ve[u]){
dfs(v);
dp[u]=max(dp[u],dp[v]+1);
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++)cin>>u>>v,ve[u].emplace_back(v);
for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
AT_dp_h Grid 1
可以直接 dp。
設 \(dp_{i,j}\) 表示走到 \((i,j)\) 的方案數,那么
時間復雜度 \(O(HW)\)。
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int h,w,dp[1005][1005];
char a[1005][1005];
signed main(){
cin>>h>>w;
for(int i=1;i<=h;i++)cin>>(a[i]+1);
dp[1][1]=1;
for(int i=1;i<=h;i++)for(int j=1;j<=w;j++){
if(a[i][j]=='#'||(i==1&&j==1))continue;
dp[i][j]=dp[i-1][j]+dp[i][j-1],dp[i][j]%=mod;
}
cout<<dp[h][w];
return 0;
}
AT_dp_i Coins
大力 dp。
設 \(dp_{i,j}\) 表示前 \(i\) 個數中有 \(j\) 個正面,那么很顯然的
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
double ans,p[3005],dp[3005][3005];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>p[i];
dp[1][0]=1-p[1],dp[1][1]=p[1];
for(int i=2;i<=n;i++)for(int j=0;j<=i;j++){
dp[i][j]=dp[i-1][j]*(1.0-p[i])+dp[i-1][j-1]*p[i];
}
for(int j=(n+1)/2;j<=n;j++)ans+=dp[n][j];
printf("%.10lf",ans);
return 0;
}
AT_dp_j Sushi
可以想到,答案與 \(a_i\) 的排列方式無關。
那么求出 \(1\) 的數量,\(2\) 的數量,\(3\) 的數量,然后用 \(dp(a,b,c)\) 表示有 \(a\) 個 \(1\),\(b\) 個 \(2\),\(c\) 個 \(3\) 的答案,那么容易得到\(\displaystyle{dp(a,b,c)=\frac{n}{a+b+c}+\frac{a}{a+b+c}\times dp(a-1,b,c)+\frac{b}{a+b+c}\times dp(a+1,b-1,c)+\frac{c}{a+b+c}\times dp(a,b+1,c-1)}\)
這四個式子分別是選了 \(0\) 到 \(3\) 的期望個數。
然后就能記憶化搜索了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x,cnt1,cnt2,cnt3;
double dp[305][305][305];
double dfs(int a,int b,int c){
if(!a&&!b&&!c)return 0;
if(dp[a][b][c])return dp[a][b][c];
double ans=double(n)/double(a+b+c);
if(a>0)ans+=dfs(a-1,b,c)*double(a)/double(a+b+c);
if(b>0)ans+=dfs(a+1,b-1,c)*double(b)/double(a+b+c);
if(c>0)ans+=dfs(a,b+1,c-1)*double(c)/double(a+b+c);
return dp[a][b][c]=ans;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>x;
if(x==1)cnt1++;
if(x==2)cnt2++;
if(x==3)cnt3++;
}
printf("%.10lf",dfs(cnt1,cnt2,cnt3));
return 0;
}
AT_dp_k Stones
博弈論。
可以類似背包的轉移。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,dp[100005],a[105],k;
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)if(i>=a[j])if(!dp[i-a[j]])dp[i]=1;
if(dp[k])cout<<"First";
else cout<<"Second";
return 0;
}
AT_dp_l Deque
可以區間 dp。
\(dp_{i,j}\) 表示剩下 \(i\) 到 \(j\) 時的答案。
然后如果剩下段的長度與 \(n\) 同奇偶,那么
如果剩下段的長度與 \(n\) 不同奇偶,那么
答案是 \(dp_{1,n}\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,dp[3005][3005],a[3005];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
if((len&1)==(n&1))dp[i][j]=max(dp[i+1][j]+a[i],dp[i][j-1]+a[j]);
else dp[i][j]=min(dp[i+1][j]-a[i],dp[i][j-1]-a[j]);
}
}
cout<<dp[1][n];
return 0;
}
AT_dp_m Candies
很顯然的 dp 加上個前綴和優化就過了。
\(dp_{i,j}\) 表示前 \(i\) 個人分 \(j\) 個糖果,那么可以得到
然后直接前綴和優化就行了。
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,k,a[105],dp[105][100005],sum[105][100005];
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=0;i<=k;i++)sum[0][i]=1;
for(int i=1;i<=n;i++)dp[i][0]=sum[i][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
if(j>=a[i])dp[i][j]=((sum[i-1][j]-sum[i-1][j-a[i]-1])%mod+mod)%mod;
else dp[i][j]=sum[i-1][j];
sum[i][j]=(sum[i][j-1]+dp[i][j])%mod;
}
}
cout<<dp[n][k];
return 0;
}
AT_dp_n Slimes
區間 dp 板子。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[405],dp[405][405],sum[405];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],sum[i]=sum[i-1]+a[i];
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
dp[i][j]=LONG_LONG_MAX;
for(int k=i;k<j;k++)dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+(sum[k]-sum[i-1])+(sum[j]-sum[k]));
}
}
cout<<dp[1][n];
return 0;
}
AT_dp_o Matching
有趣的狀壓。
\(dp_S\) 表示前一個集合的前 \(|S|\) 個數對應后一個集合的中屬于 \(S\) 的個數,那么
時間復雜度 \(O(2^n\times n)\)
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,a[25][25],dp[4000005];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
dp[0]=1;
for(int S=1;S<(1<<n);S++){
for(int i=1;i<=n;i++)if(S&(1<<(i-1)))if(a[__builtin_popcount(S)][i])dp[S]+=dp[S-(1<<(i-1))],dp[S]%=mod;
}
cout<<dp[(1<<n)-1];
return 0;
}
AT_dp_p Independent Set
簡單的樹形 dp。
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,u,v,dp[100005][2];
vector<int> ve[100005];
void dfs(int u,int fa){
dp[u][0]=dp[u][1]=1;
for(int v:ve[u]){
if(v==fa)continue;
dfs(v,u);
dp[u][0]*=(dp[v][0]+dp[v][1])%mod;
dp[u][1]*=dp[v][0];
dp[u][0]%=mod,dp[u][1]%=mod;
}
}
signed main(){
cin>>n;
for(int i=1;i<n;i++)cin>>u>>v,ve[u].emplace_back(v),ve[v].emplace_back(u);
dfs(1,0);
cout<<(dp[1][0]+dp[1][1])%mod;
return 0;
}
AT_dp_q Flowers
板子題,暴力套一個權值線段樹優化就過了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,h[200005],a[200005],dp[200005],ans;
struct tree{
int l,r,maxx;
}tr[800005];
void pushup(int u){
tr[u].maxx=max(tr[u<<1].maxx,tr[u<<1|1].maxx);
}
void build(int u,int l,int r){
tr[u]={l,r};
if(l==r)return;
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void change(int u,int pos,int k){
if(tr[u].l==tr[u].r){
tr[u].maxx=k;
return;
}
int mid=tr[u].l+tr[u].r>>1;
if(pos<=mid)change(u<<1,pos,k);
else change(u<<1|1,pos,k);
pushup(u);
}
int query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r)return tr[u].maxx;
int mid=tr[u].l+tr[u].r>>1,res=LONG_LONG_MIN;
if(l<=mid)res=max(res,query(u<<1,l,r));
if(r>mid)res=max(res,query(u<<1|1,l,r));
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>h[i];
for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,2e5);
for(int i=1;i<=n;i++){
dp[i]=query(1,1,h[i])+a[i];
change(1,h[i],dp[i]);
}
for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
AT_dp_r Walk
一個經典的結論是,如果要求一個圖有多少個長度為 \(k\) 的路徑,那么可以把鄰接矩陣做 \(k\) 次冪,然后這個鄰接矩陣中所有數的和就是答案了。
那么這題可以直接這樣做,時間復雜度 \(O(n^3\log k)\)。
#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
int n,k,anss;
struct matrix{
int a[55][55],len;
friend matrix operator*(const matrix&a,const matrix&b){
matrix c;c.len=a.len;
for(int i=1;i<=a.len;i++)for(int j=1;j<=a.len;j++){
c.a[i][j]=0;
for(int k=1;k<=a.len;k++)c.a[i][j]+=a.a[i][k]*b.a[k][j]%mod,c.a[i][j]%=mod;
}
return c;
}
}mt,ans;
matrix ksm(matrix a,int b){
matrix ans=a;b--;
while(b){
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
signed main(){
cin>>n>>k,mt.len=n;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>mt.a[i][j];
ans=ksm(mt,k);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)anss+=ans.a[i][j],anss%=mod;
cout<<anss;
return 0;
}
AT_dp_s Digit Sum
數位 dp 入門題。
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int d,a[10005],dp[10005][105];
string s;
int dfs(int now,int md,int tp){
if(!now){
if(!md)return 1;
else return 0;
}
if(!tp&&dp[now][md]>=0)return dp[now][md];
int maxx=tp?a[now]:9,res=0;
for(int i=0;i<=maxx;i++)res+=dfs(now-1,(md+i)%d,tp&&i==maxx),res%=mod;
if(!tp)dp[now][md]=res;
return res;
}
signed main(){
cin>>s>>d;
memset(dp,-0x3f,sizeof dp);
for(int i=0;i<s.size();i++)a[s.size()-i]=s[i]-'0';
cout<<(dfs(s.size(),0,1)-1+mod)%mod;
return 0;
}
AT_dp_t Permutation
\(dp_{i,j}\) 表示前 \(i\) 個數中的最后一個數是 \(j\),而且這 \(i\) 個數是 \(1\) 到 \(i\) 的方案數。
那么容易想到如果 \(s_{i-1}\) 為大于號時
如果 \(s_{i-1}\) 為小于號時
加個前綴和優化就能過,復雜度 \(O(n^2)\)。
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,f[3005][3005],sum[3005][3005],ans;
string s;
signed main(){
cin>>n>>s;s=" "+s;
f[1][1]=sum[1][1]=1;
for(int i=2;i<=n;i++)for(int j=1;j<=i;j++){
if(s[i-1]=='>'){
f[i][j]=sum[i-1][i-1]-sum[i-1][j-1];
f[i][j]+=mod,f[i][j]%=mod;
}
else{
f[i][j]=sum[i-1][j-1];
f[i][j]%=mod;
}
sum[i][j]=sum[i][j-1]+f[i][j],sum[i][j]%=mod;
}
for(int i=1;i<=n;i++)ans+=f[n][i],ans%=mod;
cout<<ans;
return 0;
}
AT_dp_u Grouping
很好想的狀壓。
先預處理出每個集合 \(S\) 如果分成一組的貢獻 \(val_S\),然后設 \(dp_S\) 表示集合 \(S\) 分組可以得到的最大值,那么
時間復雜度 \(O(3^n)\)。
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,dp[1000005],a[25][25],c[1000005];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
for(int S=1;S<(1<<n);S++)for(int j=1;j<=n;j++)if(S&(1<<(j-1)))for(int k=j+1;k<=n;k++)if(S&(1<<(k-1)))c[S]+=a[j][k];
for(int S=1;S<(1<<n);S++)for(int V=S;V;V=(V-1)&S)dp[S]=max(dp[S],dp[S-V]+c[V]);
cout<<dp[(1<<n)-1];
return 0;
}
AT_dp_v Subtree
待補。
AT_dp_w Intervals
今年NOIP T4的原題。待補。
AT_dp_x Tower
我們可以貪心地想到如果兩個箱子 \(i\) 和 \(j\),且 \(s_i-w_j<s_j-w_i\) 的話,\(i\) 一定比 \(j\) 優先選擇。
把這個式子變一下變成 \(s_i+w_i<s_j+w_j\),就能排序了。
然后使用類似 01 背包的方法就做完了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,dp[100005],ans;
struct nd{
int w,s,v;
friend bool operator<(const nd&a,const nd&b){
return a.w+a.s<b.w+b.s;
}
}a[1005];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].w>>a[i].s>>a[i].v;
sort(a+1,a+n+1);
memset(dp,-0x3f,sizeof dp),dp[0]=0;
for(int i=1;i<=n;i++)for(int j=a[i].s;~j;j--)dp[j+a[i].w]=max(dp[j+a[i].w],dp[j]+a[i].v);
for(int i=0;i<=20000;i++)ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
AT_dp_y Grid 2
待補。
AT_dp_z Frog 3
待補。

浙公網安備 33010602011771號