1. 程式人生 > >一些DP雜題

一些DP雜題

algo type const else 範圍 one cnblogs 轉移 class

1.

[HNOI2001] 產品加工

一道簡單的背包,然而我還是寫了很久QAQ

時間範圍是都小於5 顯然考慮一維背包,dp[i]表示目前A消耗了i的最小B消耗

註意

if(b[i]) dp[j]=dp[j]+b[i];
else dp[j]=1e9+7;

可以用B則直接轉移,否則要把上一次的這個狀態設為正無窮,只能用後兩個轉移。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include
<cstring> #include<queue> #include<vector> using namespace std; const int maxn=6000+299; const int N=5*6000+9; int n,a[maxn],b[maxn],c[maxn],dp[N],lz[N],ans=1e9+7; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i],&b[i],&c[i]); memset(dp,
127,sizeof(dp)); dp[0]=0; for(int i=1;i<=n;i++) for(int j=i*5;j>=0;j--){ if(b[i]) dp[j]=dp[j]+b[i]; else dp[j]=1e9+7;//!!!!!!!!!!!!!!!!!!!! if(a[i]&&j>=a[i]&&dp[j]>dp[j-a[i]]) dp[j]=dp[j-a[i]]; if(c[i]&&j>=c[i]&&dp[j]>dp[j-c[i]]+c[i]) dp[j]=dp[j-c[i]]+c[i];
if(i==n) ans=min(max(dp[j],j),ans); } cout<<ans; return 0; }

2.

[HAOI2007]上升序列

看數據範圍似乎是n^2可以過的,然而自己之前並不會寫nlongn求最長上升子序列的算法就自己YY了一下,寫得很醜,單調棧裏從短到長從大到小,用了兩個二分,一次找找最長的比它小的,一次找長度為它的位置是否可以更新。

這樣找到以每個元素打頭的最長上升序列,詢問就從1到n跑一遍問它能不能到那麽長,就保證了字典序最小。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<vector>
using namespace std;
const int maxn=10000+299;
int now,n,m,x,maxx,a[maxn],pre[maxn],dp[maxn],sta[maxn],sl=1,sr;
int ef(int l,int r,int x){
    int res=-1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(a[sta[mid]]>x) res=mid,l=mid+1;
        else r=mid-1;
    }
    return res;
}
void ef2(int l,int r,int len,int x){
    while(l<=r){
        int mid=(l+r)>>1;
        if(dp[sta[mid]]==len) { if(a[sta[mid]]<=a[x]) sta[mid]=x; break;}
        if(dp[sta[mid]]<len) l=mid+1;
        else if(dp[sta[mid]]>len) r=mid-1;  
    }
}
void work() { 
    for(int i=n;i>=1;i--) {
        dp[i]=1;
        if(sl<=sr) {
        now=ef(sl,sr,a[i]);
        if(now!=-1)
            dp[i]=dp[sta[now]]+1;
        }
        maxx=max(maxx,dp[i]);
        while(sr>=sl&&dp[sta[sr]]<=dp[i]&&a[sta[sr]]<=a[i]) {
            sr--;
        }
        if(sr<sl||dp[sta[sr]]<dp[i]) sta[++sr]=i;
        else ef2(sl,sr,dp[i],i);
    }
}
void query(int x){
    if(x>maxx) puts("Impossible");
    else {
        int pre=0;
        for(int i=1;i<=n;i++) {
            if(dp[i]>=x&&a[i]>pre) {
                pre=a[i];
                if(x==1)
                printf("%d",a[i]);
                else printf("%d ",a[i]);
                x--;
                if(!x) break;
            }
        }
        printf("\n");
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    work();
    scanf("%d",&m);
    for(int i=1;i<=m;i++) {
        scanf("%d",&x);
        query(x); 
    }
    return 0;
}

然而正確的nlogn求最長上升序列並不是這麽寫的,只用一個二分,不過在下並不是很清楚,懶得學以後再說吧。

從LLJ大佬那裏學到了用線段樹的做法,開一顆權值線段樹,從後往前把Dp值存進去,每個點找它後面的最大Dp值來更新,感覺和正常的nlogn的思路可能差不多。

3.

UVA - 12063 Zeros and Ones

谷歌翻譯神坑,1和0頻率相同翻譯成0和0頻率相同,喵喵喵?

把Case打成case被坑了一波。。對拍才發現QAQ

最好的做法是往後加0或者1 不用特判,不會炸整,往前加的話就要特判,然後註意開 long long ,要模兩次保證不會炸 (LLJ大佬說要開usinged long long ,因為 long long 只到2^64-1,實際這題只到2^63所以不用)

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef unsigned long long LL;
const int maxn=100;
const int maxk=105;
int T,n,k;
LL dp[maxn][maxn][maxk],ans,ll=1; 
void work(){
    if(!k||n&1) {printf("0\n"); return;}
    memset(dp,0,sizeof(dp));
    dp[0][0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++) 
            for(int l=0;l<k;l++) {
               if(j) dp[i][j][(l+((ll=1)<<(i-1))%k)%k]+=dp[i-1][j-1][l];
               if(i!=n) dp[i][j][l]+=dp[i-1][j][l];
            }
    ans=0;
    printf("%llu\n",dp[n][n/2][0]);
}
int main()
{

    scanf("%d",&T);
    for(int i=1;i<=T;i++){
        scanf("%d%d",&n,&k);
        printf("Case %d: ",i);
        work(); 
    }
    return 0;
}

這是往後加的版本

    for(int i=1;i<n;i++)
        for(int j=0;j<=i;j++) 
            for(int l=0;l<k;l++) {
               dp[i+1][j+1][((l<<1)|1)%k]+=dp[i][j][l];
               dp[i+1][j][(l<<1)%k]+=dp[i][j][l];
            }

4.

UVA - 1628 Pizza Delivery

我愛記憶化搜索,記憶化搜索最強。

dp[i][j][o][k]表示i到j的訂單已處理好,現在在i或者j 還要送k家的最優解

枚舉,記憶化

for(int i=1;i<l;i++) u=max(u,dfs(i,r,0,k-1)+e[i]-k*abs(p[i]-p[o==0?l:r]));
    for(int i=n;i>r;i--) u=max(u,dfs(l,i,1,k-1)+e[i]-k*abs(p[i]-p[o==0?l:r]));

註意這一段先搜兩邊再中間,可以達到記憶化效果

代碼

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=100+5;
int T,now,dp[maxn][maxn][2][maxn],vis[maxn][maxn][2][maxn],n,p[maxn],e[maxn],ans;
int dfs(int l,int r,int o,int k) {
    if(k==0) return 0;
    if(vis[l][r][o][k]==now) return dp[l][r][o][k];
    vis[l][r][o][k]=now;
    int &u=dp[l][r][o][k];
    u=0;
    for(int i=1;i<l;i++) u=max(u,dfs(i,r,0,k-1)+e[i]-k*abs(p[i]-p[o==0?l:r]));
    for(int i=n;i>r;i--) u=max(u,dfs(l,i,1,k-1)+e[i]-k*abs(p[i]-p[o==0?l:r]));
    return u;  
}
int main()
{
    scanf("%d",&T);
    for(now=1;now<=T;now++) {
    ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&p[i]);
    for(int i=1;i<=n;i++) scanf("%d",&e[i]);
    for(int kk=1;kk<=n;kk++) 
        for(int i=1;i<=n;i++)
        ans=max(ans,dfs(i,i,0,kk-1)+e[i]-kk*abs(p[i]));
    printf("%d\n",ans); 
    }
    return 0;
}

一些DP雜題