dp专题咯
這里會放出一些很有意思的dp或者作者不會的,反正我覺得dp這東西,做多才有經驗的嘛。所以可以看題解呀:
來來來;P2758 編輯距離這題嘛其實看出來是dp但也很難轉移,不過其實多想一想就好了。還是用一下別人的題解:http://blog.csdn.net/CQBZLYTina/article/details/75043128?locationNum=9&fps=1
其實就是從上一個狀態轉移上來的狀態的不同嘛,若是刪除,那就把i-1位變成 j 位子咯,若是插入,其實就是把第j位子刪除嘛,那就和刪除一樣啦,若是修改,那不就是將i-1位變成j-1位?那所有狀態都推出來了啦,
轉移方式有三種,可以為刪除: f[i-1][j]+1,為添加: f[i][j-1]+1 ,為修改f[i-1]f[j-1];
代碼:
還有這一道 P1040 [NOIP2003 提高組] 加分二叉樹(https://www.luogu.com.cn/problem/P1040),就挺離譜啊就題目都沒想明白,一看題解不過如此,哈哈哈哈哈哈哈笑死了:
這個題可以用動態規劃或者記憶化搜索來做。因為如果要求加分最大的話,必須要求它的兒子結點加分最大,所以就有了最優子階段。我們可以枚舉根來更新最大值。中序遍歷有個特點,在中序遍歷這個序列上,某個點左邊的序列一定是這個點的左子樹,右邊的序列,一定在這個點的右子樹。
root[i,j]表示[i,j]這段序列的根,遞歸輸出先序遍歷。注意初始化,f[i][i]=v[i],當序列只有I一個元素時,f[i][i]等于這個點本身的權值,當l==r-1時,此時是空樹設為1。
所見代碼來啦!
下一個是P3554 [POI2013]LUK-Triumphal arch,這個是二分加樹上dp。思想是二分答案,然后判斷要提前幫兒子染多少的顏色,注意要舍去小于0的兒子的值。
#include<bits/stdc++.h> using namespace std; int n,m; int len=0,last[310001],son[310001],f[310001]; struct pp {int x,y,next; };pp p[610001]; void ins(int x,int y) {int now=++len;p[now]={x,y,last[x]};last[x]=now;return ; } int getson(int x,int fa) {for(int i=last[x];i!=-1;i=p[i].next){int y=p[i].y;if(y==fa) continue ;son[x]++;getson(y,x);} } void dfs(int x,int fa,int lim) {f[x]=son[x]-lim;for(int i=last[x];i!=-1;i=p[i].next){int y=p[i].y;if(y==fa) continue ;dfs(y,x,lim);if(f[y]>0) f[x]+=f[y];}return ; } int main() {memset(last,-1,sizeof(last));scanf("%d",&n);for(int i=1;i<=n-1;i++){int x,y;scanf("%d%d",&x,&y);ins(x,y);ins(y,x);}getson(1,1);int l=son[1],r=0,ans;for(int i=1;i<=n;i++) r=max(r,son[i]);while(l<=r){int mid=(l+r)/2;dfs(1,1,mid);if(f[1]<=0) r=mid-1,ans=mid;else l=mid+1;}printf("%d",ans);return 0; }今天比賽有兩個dp不過第一個是有關邊權的排排序就能做了就不放了。對于這種具有單向性的題我們都可以嘗試排序來搞定,第二題是
這個不知道叫什么的dp,時間復雜度是n^2的,枚舉每一個位置,考慮從前面一位轉移,怎么辦呢,考慮最后一位插入j(j<=i,因為到目前為止一共只有i位),在這里引入一個 sum[j],表示為前一輪狀態下,最后一位小于等于 j 的情況的和。那么轉移的話一共會有三種情況,若前面強制小于自己,那么就轉移sum[j-1],若強制大于自己sum[i-1]-sum[j-1],若是無要求就sum[i-1]。代碼:
周末電腦報廢了,所以有些東西沒有寫上來刷了幾道dp的題目
Stones博弈論套dp,記住要枚舉位置,不能枚舉集合中的數,因為是轉移狀態,從前面轉移出來
Sushi期望dp,具體轉移看別人推的。
//動態規劃優化的初等方法無非就那么幾種,合并狀態就是最重要的思想之一 #include<bits/stdc++.h> using namespace std; int n,m,a[4]; double f[331][331][331]; int main() {scanf("%d",&n);for(int i=1;i<=n;i++) {int x;scanf("%d",&x);a[x]++;} for(int k=0;k<=n;++k){for(int j=0;j<=n;++j){for(int i=0;i<=n;++i){if(i||j||k){if(i)f[i][j][k]+=f[i-1][j][k]*i/(i+j+k);if(j)f[i][j][k]+=f[i+1][j-1][k]*j/(i+j+k);if(k)f[i][j][k]+=f[i][j+1][k-1]*k/(i+j+k);f[i][j][k]+=(double)n/(i+j+k);}}}}printf("%.15lf\n",f[a[1]][a[2]][a[3]]);return 0; }Coins
//與其算成功的概率,不如將所有情況的可能算出來 //f[i][j]指的是在第i次的時候有j枚朝上 //那這樣就可以從這東西轉移啦,明顯可以滾動優化 //dp的思想便是發現一個問題與上一個問題之間的關系。 #include<bits/stdc++.h> using namespace std; int n,m; double a[10001],f[3001][3001],ans=0; int main() {scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%lf",&a[i]);f[0][0]=1;for(int i=1;i<=n;i++){f[i][0]=f[i-1][0]*(1-a[i]);for(int j=1;j<=i;j++) f[i][j]=f[i-1][j-1]*a[i]+f[i-1][j]*(1-a[i]);}for(int i=1;i<=n;i++){if(i>n-i) ans+=f[n][i];}printf("%.10lf",ans);return 0; }LCS這一題的話,是一個很典型的題目, f[i][j]=max(f[i-1][j],f[i][j-1]);以這種方式向前轉移,就是自己與前面的最佳狀態比較,若沒記錯,倍增里面的題也有用到這種思路的。
#include<bits/stdc++.h> using namespace std; int n,m,len1,len2; char s1[3021],s2[3021],ans[3021]; int f[3001][3001]; int main() {scanf("%s%s",s1+1,s2+1);len1=strlen(s1+1);len2=strlen(s2+1);for(int i=1;i<=len1;i++){for(int j=1;j<=len2;j++) {f[i][j]=max(f[i-1][j],f[i][j-1]);if(s1[i]==s2[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1); }}int i=len1,j=len2;//printf("*");while(f[i][j]!=0){if(s1[i]==s2[j]) ans[f[i][j]]=s1[i],i--,j--;else {if(f[i-1][j]==f[i][j]) i--;else j--;}}printf("%s",ans+1);return 0; }Knapsack 2這個是新型背包,也不算吧,只不過思路新些
//轉換思維,當價值取i時讓重量最小 //最后枚舉價值取重量即可,所以我們枚舉價值(10^6) #include<bits/stdc++.h> using namespace std; int n,m,maxx,v[100001],w[100001],f[2000001]; int main() {memset(f,63,sizeof(f));f[0]=0;scanf("%d%d",&n,&m);maxx=n*1000;for(int i=1;i<=n;i++){scanf("%d%d",&w[i],&v[i]);for(int j=maxx;j>=v[i];j--) f[j]=min(f[j],f[j-v[i]]+w[i]);}for(int i=maxx;i>=0;i--){if(f[i]<=m) {printf("%d",i);return 0;}}return 0; }Deque區間dp,考慮轉移,分類討論。
//考慮做一個區間dp,枚舉區間的長度,再枚舉左端點,計算出右端點 //那么討論發現,若取走的數有奇數個,那么是后手取,若是偶數個,那是先手取 //然后直接轉移便可 具體討論不告訴你。 #include<bits/stdc++.h> using namespace std; int n,m,a[4001],f[4001][4001]; int main() {scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);for(int len=1;len<=n;len++){for(int i=1;i+len-1<=n;i++){int j=i+len-1; if((n-len)%2==1) f[i][j]=min(f[i+1][j]-a[i],f[i][j-1]-a[j]);else f[i][j]=max(f[i+1][j]+a[i],f[i][j-1]+a[j]);} } printf("%d",f[1][n]);return 0; }也是區間dpSlimes,考慮每一個區間如何轉移,那么是(左+右+合并代價)前兩者可以從前面轉移,后者可以計算得出。
#include<bits/stdc++.h> using namespace std; long long n,m,f[1001][1001],a[10001]; int main() {memset(f,63,sizeof(f));scanf("%lld",&n);for(int i=1;i<=n;i++) scanf("%lld",&a[i]),f[i][i]=0;for(int len=2;len<=n;len++){for(int i=1;i+len-1<=n;i++){int j=i+len-1;for(int k=i;k<j;k++) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);//枚舉斷點for(int k=i;k<=j;k++) f[i][j]+=a[k];//加上合并的代價 }}printf("%lld",f[1][n]);return 0; }Candies這一題
//dp首先考慮枚舉位置及那個位置小朋友得到的糖 ,再考慮如何從前面轉移,呃那么就要枚舉前面的小朋友的位置,時間O(n*n*k) //考慮優化,不妨用前綴和,記sum[i][j]=f[i][k](1 <= k <= j) 那么便是(nk)的轉移了 //聽別人說還有一個向后轉移用差分 #include<bits/stdc++.h> #define int long long using namespace std; int n,m,mod=1e9+7; int a[200001],sum[200][200001],f[200][200001]; signed main() {scanf("%lld%lld",&n,&m);f[0][0]=sum[0][0]=1;for(int i=1;i<=n;i++) scanf("%lld",&a[i]);for(int i=1;i<=m;i++) sum[0][i]+=sum[0][i-1];for(int i=1;i<=n;i++){for(int j=0;j<=m;j++){int l=max(0ll,j-a[i]),r=j;f[i][j]=((f[i][j]+sum[i-1][r])%mod-sum[i-1][l-1]+mod)%mod;} sum[i][0]=1;for(int j=1;j<=m;j++) sum[i][j]=(sum[i][j-1]+f[i][j]+mod)%mod;}printf("%lld",f[n][m]);return 0; }Independent Set神奇的樹形dp。
#include<bits/stdc++.h> #define int long long using namespace std; int n,m,len=0,last[200001],f[200001][3],mod=1e9+7;//0表示的是黑色,1表示的白色 //說真的還是白色受我喜歡些吧!變態吧! struct pp {int x,y,next; };pp p[400001]; void ins(int x,int y) {int now=++len;p[now]={x,y,last[x]};last[x]=now;return ; } void dfs(int x,int fa) {f[x][0]=f[x][1]=1;for(int i=last[x];i!=-1;i=p[i].next){int y=p[i].y;if(y==fa) continue ;dfs(y,x);f[x][0]*=f[y][1];f[x][1]*=(f[y][0]+f[y][1]);f[x][0]%=mod;f[x][1]%=mod; }return ; } signed main() {memset(last,-1,sizeof(last));scanf("%lld",&n);for(int i=1;i<=n-1;i++){int x,y;scanf("%lld%lld",&x,&y);ins(x,y);ins(y,x);}dfs(1,1);printf("%lld",(f[1][0]+f[1][1])%mod);return 0; }Flowers,呃不算難,拿樹狀數組維護。從前一個最大值的位置轉移至目前這個。
//很強的樹狀數組思路,每次找到前面的最大值后,加上自己的值后插入自己的位置。 //那么其實就可以達到一個類似于前綴和的效果 #include<bits/stdc++.h> #define int long long #define lowbit(x) x&(-x) using namespace std; int n,m,h[210000],f[410000],ans=0; void cg(int x,int k) {for(;x<=n;x+=lowbit(x)) f[x]=max(f[x],k);return ; } int findsum(int x) {int rt=0;for(x;x>=1;x-=lowbit(x)) rt=max(f[x],rt);return rt; } signed main() {scanf("%lld",&n);for(int i=1;i<=n;i++) scanf("%lld",&h[i]);for(int i=1;i<=n;i++) {int x,k;scanf("%lld",&x);k=findsum(h[i]-1)+x;ans=max(ans,k);cg(h[i],k);}printf("%lld",ans);return 0; }Intervals那個考慮轉移,從不同的狀態轉移過來,那么就是。(最好看看題解)
這樣但是O(n^3)的轉移。
具體如何維護呢對。把 dp 數組扔到線段樹上啊,然后直接像取最大值,區間加一樣維護就行了,再具體的看代碼吧。還有就是我們按右端點來計數,
同樣一道dp轉移題,采用的是前綴和優化Permutation,轉移方式和前面的前綴和轉移也都差不多
#include<bits/stdc++.h> #define int long long using namespace std; int n,m,f[3010][3010],sum[3010],mod=1e9+7; char s[10001]; main() {scanf("%lld%s",&n,s+2);f[1][1]=1;for(int i=2;i<=n;i++){memset(sum,0,sizeof(sum));for(int j=1;j<=i-1;j++) sum[j]=sum[j-1]+f[i-1][j];for(int j=1;j<=i;j++){if(s[i]=='<') f[i][j]=(f[i][j]+sum[j-1]-sum[0])%mod;else f[i][j]=(f[i][j]+sum[i-1]-sum[j-1])%mod; }}int ans=0;for(int i=1;i<=n;i++) ans=(ans+f[n][i])%mod;printf("%lld",ans);return 0; }Subtree考慮樹形dp,但是發現維護困難,(n^2),考慮換根。嗯。
//對于一個點now,你需要分兩類,即 now 子樹里(無now的)和除了 now 的子樹(含 now); //這樣說太麻煩,感性理解為取黑色節點時向上走呢,還是向下走,最終兩種情況的和乘起來即可,當然取法還是有說法的,不表。 //考慮維護,枚舉每一個節點的話會時超,想到換根,那通過什么來轉移呢。用前綴和,但是僅僅是和是無法轉移的, //因為對于每一個子節點,要乘上在他前面的所有兄弟才成 //那么就還有多一個的后綴積,然后取模的話其實用不著逆元,直接搞就行 #include<bits/stdc++.h> #define int long long using namespace std; int n,m,len=0,last[3000001],mod; struct pp {int x,y,next; };pp p[3000001]; int qx[3000001],hx[3000001],f[3000001],ans[3000001]; void ins(int x,int y) {int now=++len;p[now]={x,y,last[x]};last[x]=now;return ; } int getsum(int i,int fa,int val) {if(i==-1) return 1;int y=p[i].y;qx[y]=val;if(y==fa) return hx[y]=getsum(p[i].next,fa,val)%mod;hx[y]=getsum(p[i].next,fa,(val*(f[y]+1))%mod);return (hx[y]*(f[y]+1))%mod; } void dfsdp(int x,int fa) {f[x]=1;for(int i=last[x];i!=-1;i=p[i].next){int y=p[i].y;if(y==fa) continue ;dfsdp(y,x);f[x]=(f[x]*(f[y]+1))%mod;}getsum(last[x],fa,1);return ; } void hg(int x,int fa,int ff) {if(x!=1) {ff=((ff+1)*(qx[x]*hx[x]%mod)%mod)%mod;ans[x]=(f[x]*(ff+1))%mod; }for(int i=last[x];i!=-1;i=p[i].next){int y=p[i].y;if(y==fa) continue ;hg(y,x,ff);}return ;} signed main() {memset(last,-1,sizeof(last));scanf("%lld%lld",&n,&mod);for(int i=1;i<=n-1;i++){int x,y;scanf("%lld%lld",&x,&y);ins(x,y);ins(y,x); }dfsdp(1,1);ans[1]=f[1]%mod;hg(1,1,0);for(int i=1;i<=n;i++) printf("%lld\n",ans[i]%mod);return 0; }一道貪心+dpTower
這里給出別人的貪心證明
一道不知名dp題:Grid 2
)這一題的話其實看出來就不難,提幾個點,第一個是從(1,1)到(ai,bi)這一個點一共要走ai+bi-2步,所以它的貢獻是在1到ai,bi這些點中選擇ai+bi-2那么多個點(好累,寫的不好看別人的去)。
P6475 [NOI Online #2 入門組] 建設城市,呃啊,一個不錯的dp,討論xy在同側或者異側的情況。放別人的題解:
Matching我來補題啦。狀壓dp耶,好欸你看n<=21,那么一眼看出壓法的話是向前枚舉。
#include<bits/stdc++.h> using namespace std; int n,m,f[1<<21+1],mod=1e9+7,a[52][52],nn; int gettot(int x) {int tot=0;while(x) tot+=(x&1),x>>=1;return tot; } int main() {scanf("%d",&n);nn=(1<<n)-1;for(int i=0;i<n;i++) {for(int j=0;j<n;j++) scanf("%d",&a[i][j]);}f[0]=1;for(int i=0;i<=nn;i++){int sum=gettot(i);for(int j=0;j<n;j++){ if(!(i&(1<<j))&&a[sum][j]) (f[i|(1<<j)]+=f[i])%=mod;}}printf("%d",f[nn]);return 0; }總結
- 上一篇: 怎么修改照片大小?一键快速修改图片宽高尺
- 下一篇: 22条创业军规(读书)