一些DP杂题

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;
}
View Code

 

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("
");
    }
}
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;
}
View Code

 

 

然而正确的nlogn求最长上升序列并不是这么写的,只用一个二分,不过在下并不是很清楚,懒得学以后再说吧。

从LLJ大佬那里学到了用线段树的做法,开一颗权值线段树,从后往前把Dp值存进去,每个点找它后面的最大Dp值来更新,感觉和正常的nlogn的思路可能差不多。

 

3.

UVA - 12063 Zeros and Ones

二进制数和它们的位模式对于计算机程序员来说总是非常有趣的。 在这个
您需要计算具有以下属性的正数二进制数的问题:
•数字正好是N位宽,它们没有前导零。
•一和零的频率相等。
•数字是K的倍数。
输入
输入文件包含几个测试用例。 输入的第一行为您提供了测试用例数,
T(1≤T≤100)。 那么T测试用例会跟随,每一行都在一行。 每个测试用例的输入包括
的两个整数,N(1≤N≤64)和K(0≤K≤100)。
产量
对于每组输入,首先打印测试用例编号。 然后打印二进制数的数字

谷歌翻译神坑,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
"); 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
",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;
}
View Code

 

这是往后加的版本

    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
",ans); 
    }
    return 0;
}
View Code

 

5.BZOJ 1009 [HNOI2008]GT考试

一开始傻逼地kmp直接往前跳一个就停了,后来改了又调了好久发现自己Kmp写错了,贼难受。。

就kmp转移然后矩阵优化。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int ans,n,m,p,top,a[21],nxt[21],cnt[21],vis[11];
struct jz{
    int x[21][21];
    friend jz operator *(const jz&l,const jz&r){
         jz res;
         for(int i=0;i<21;i++)
         for(int j=0;j<21;j++)
         res.x[i][j]=0;
         for(int i=0;i<21;i++)
             for(int j=0;j<21;j++) {
                 for(int k=0;k<21;k++)
                     (res.x[i][j]+=(l.x[i][k]*r.x[k][j])%p)%=p;
             } 
         return res;
    }
}base,ret,tmp;
void input() {
    scanf("%d%d%d",&n,&m,&p);
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) a[++top]=ch-'0';
}
int make_nxt(int a[],int *nxt) {
    for (int j=0,i=2;i<=m;i++) {
        while (j&&a[j+1]!=a[i]) j=nxt[j];
        if (a[j+1]==a[i]) j++;
        nxt[i]=j;
     }
}
void ksm(int b){
    while(b){
        if(b&1) ret=ret*base;
        base=base*base;
        b>>=1;
    }
}
void work() {
    for(int i=0;i<m;i++) { 
        cnt[i]++; 
        vis[a[i+1]]=i+1;
        if(i+1!=m) {base.x[i][i+1]++;}
        int tp=i;
        while(nxt[tp]) {
            tp=nxt[tp];
            if(vis[a[tp+1]]!=i+1) {
            vis[a[tp+1]]=i+1;
            cnt[i]++;
            base.x[i][tp+1]++;
            }
        }
        if(vis[a[1]]!=i+1) {
            cnt[i]++;
            base.x[i][1]++;
        }
        base.x[i][0]+=(10-cnt[i]);
    } 
    for(int i=0;i<m;i++)
        for(int j=0;j<m;j++) 
            if(i==j) ret.x[i][j]=1;
    ksm(n);
    ans=0;
    for(int i=0;i<m;i++) 
    (ans+=ret.x[0][i])%=p;
    printf("%d
",ans);
}
int main()
{
    input();
    make_nxt(a,nxt);
    work();
    return 0;
}
GT考试

 

6.BZOJ 1855 [SCOI2010]股票交易

在某位学长的博客里看到这题第一句话是省选怎么会考这么简单的题呢,然后就发现自己不会做。。。

神奇的单调队列优化,可能是我单调队列写得太少了。。

很容易知道状态dp[i][j]表示时间为i手中股票为j的最大收益,初始化为极小值,dp[0][0]=0;

然后转移分三种

不买不卖:dp[i][j]=max(dp[i][j],dp[i-1][j]);

买入:dp[i][j]=max(dp[i][j],dp[i-w-1][j-k]-k*ap[i]);

卖出:dp[i][j]=max(dp[i][j],dp[i-w-1][j+k]+k*bp[i]);

然后我们发现后两个枚举k会超时,就把它优化一下 (其实我也。。比较懵逼)

买入:dp[i][j]=max(dp[i][j],dp[i-w-1][k]-(j-k)*ap[i]);

                    =max(dp[i][j],(dp[i-w-1][k]+k*ap[i])-j*ap[i]);

卖出:dp[i][j]=max(dp[i][j],dp[i-w-1][k]+(k-j)*bp[i]);

                     =max(dp[i][j],(dp[i-w-1][k]+k*ap[i])-j*bp[i]);

以买入为例,我们发现 这一部分 dp[i-w-1][k]+k*ap[i] (j-k>0,j-k<as[i])具有单调性,

                   k>j-as[i] 我们枚举k,然后丢进单调队列, 更新现在的dp[i][k];

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=2000+299; 
int ans,n,maxp,w,dp[maxn][maxn];
int ap[maxn],bp[maxn],as[maxn],bs[maxn],que[maxn],ql=1,qr;
int main()
{
    scanf("%d%d%d",&n,&maxp,&w);
    for(int i=1;i<=n;i++) 
    scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
    memset(dp,128,sizeof(dp));
    dp[0][0]=0;
    for(int i=1;i<=n;i++) {
        ql=1,qr=0;
        for(int j=0;j<=maxp;j++) {
            dp[i][j]=max(dp[i][j],dp[i-1][j]);
            if(i-w-1>0) {
                while(ql<=qr&&que[ql]<j-as[i]) ql++;
                while(ql<=qr&&dp[i-w-1][que[qr]]+que[qr]*ap[i]-j*ap[i]<=dp[i-w-1][j]) qr--;
                que[++qr]=j;
                if(ql<=qr) dp[i][j]=max(dp[i][j],dp[i-w-1][que[ql]]+que[ql]*ap[i]-j*ap[i]);
            }
            else if(j<=as[i]) dp[i][j]=max(dp[i][j],-ap[i]*j);
        }
        ql=1,qr=0;
        for(int j=maxp;j>=0;j--) {
           if(i-w-1>0){
               while(ql<=qr&&que[ql]>j+bs[i]) ql++;
               while(ql<=qr&&dp[i-w-1][que[qr]]+que[qr]*bp[i]-j*bp[i]<=dp[i-w-1][j]) qr--;
               que[++qr]=j;
               if(ql<=qr) dp[i][j]=max(dp[i][j],dp[i-w-1][que[ql]]+que[ql]*bp[i]-j*bp[i]);
           }
        }
    }
    for(int i=0;i<=maxp;i++) ans=max(ans,dp[n][i]);
    printf("%d
",ans);
    return 0;
}
股票交易

 

原文地址:https://www.cnblogs.com/Achenchen/p/7469135.html