复习动规(3)

继续复习动规。

单调队列优化DP(没错还是这个)

第一道,[SCOI2010]股票交易看起来限制多,其实就是纸老虎。

可设f[i][j]指第i天拥有j张股票所赚的最大钱数,所以f[i][j]可为负,所以初值设为-inf。

四种情况:1.什么都不买,f[i][j]=max{f[i-1][j]}

2.第一次买,f[i][j]=max{-j*APi}(0<=j<=ASi

3.之后的买,f[i][j]=max{f[i-W-1][k]+k*APi}-j*APi(j-ASi<=k<j)

4.卖,f[i][j]=max{f[i-W-1][k]+k*BPi}-j*BPi(j<k<=j+BSi

前面两种直接O(n2),后面两种用单调队列优化,第三种从前往后,第四种从后往前,到这里就不难写了。

数据结构优化DP

第一道,[SCOI2014]方伯伯的玉米田

首先得发现一个性质,每次修改的区间的右端点一定是最后一个点,证明如下:

对于这个区间外的左边的玉米,其最长不下降子序列会增大或不变。

而对于右边的玉米,其最长不下降子序列会减小或不变。故要减小右边的玉米数,当然为0最好。证明毕。

设f[i][j]指当前这个玉米被拔高后的高度为i且这个玉米被j个拔高区间覆盖的最长不下降子序列长度。

可得:f[i][j]=max{f[k][l]}+1(k<=i,l<=j)。单做要O(n4),但是前缀取最大值我们可以想到树状数组。

于是定义一个二维树状数组(就是我代码里的f数组),优化后的时间复杂度为O(n2*logn*logn);

这题想到性质很重要,数组的定义也很难想到(I am vegetable),再多写点同类型的题吧。

记得j要倒着枚举避免重复计算,代码的具体实现如下:

#include<bits/stdc++.h>
#define Lowbit(i) (i&(-i))
using namespace std;
const int N=1e4+1e3;
int n,k,ans,d[N],f[5600][510];
int Max(int x,int y){ return x>y?x:y;}
int rd(){
    int s=0,ff=1;
    char w=getchar();
    while(!isdigit(w))
        ff^=w=='-',w=getchar();
    while(isdigit(w))
        s=s*10+w-'0',w=getchar();
    return ff?s:-s;
}
int Query(int x,int y){
    int ss=0;
    for(int i=x;i;i-=Lowbit(i))
        for(int j=y;j;j-=Lowbit(j))
            ss=Max(ss,f[i][j]);
    return ss;
}
void Change(int x,int y,int ss){
    for(int i=x;i<=5500;i+=Lowbit(i))
        for(int j=y;j<=k+1;j+=Lowbit(j))
            f[i][j]=Max(ss,f[i][j]);
}
int main(){
    int x; n=rd(),k=rd();
    for(int i=1;i<=n;i++)
        d[i]=rd();
    for(int i=1;i<=n;i++)
        for(int j=k;j>=0;j--){
            x=Query(d[i]+j,j+1)+1;
            if(x>ans) ans=x;
            Change(d[i]+j,j+1,x);
        }
    printf("%d
",ans);
    return 0;
}
[SCOI2014]方伯伯的玉米田

第二道,[BalticOI 2017] Toll很有意思的题。

题目给出了(b/k)=1+(a/k)(括号为下取整号)。所以我们从这个方面去思考。

在思考了几个世纪后,我们发现(i/k)相同的有很多个i,将这些i当做一整块,于是n个地点就被分为ceil(n/k)个块。

然后又可以发现a->b恰好是一个块中的点到后面的一个块中的点,于是定义f[i]为i到终点的最短路,就可以用矩阵来转移状态。

| disx->a    disx->b    disx->c |         | f[a] |       | f[x] |

| disy->a    disy->b    disy->c |    *   | f[b] |   =   | f[y] |

| disz->a    disz->b    disz->c |         | f[c] |       | f[z] |    (这里的*不是乘法,是新定义的运算C[i][j]=max{A[i][k]+B[k][j]})

然后想到了什么?没错,就是我们的动态dp。接下来就很简单了,把dis存下来放到线段树里,每次查询“l的块~r的块”的矩阵连乘。

又因为f[终点]=0而其他的f为无穷大,所以答案就是矩阵中的某个值。(由l,r决定哪行哪列)。

最后记得判断-1。代码的具体实现如下:

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+1e3,M=7e8;
int k,n,m,q,t;
int Min(int x,int y){return x<y?x:y;}
struct Num{
    int p[6][6];
    Num(){
        memset(p,127/3,sizeof(p));
    }
    inline Num operator*(Num b){
        Num c;
        for(int i=1;i<=k;i++)
            for(int j=1;j<=k;j++)
                for(int l=1;l<=k;l++)
                    c.p[i][j]=Min(c.p[i][j],p[i][l]+b.p[l][j]);
        return c;
    }
}d[N],f[N*4];
int rd(){
    int s=0,ff=1;
    char w=getchar();
    while(!isdigit(w))
        ff^=w=='-',w=getchar();
    while(isdigit(w))
        s=s*10+w-'0',w=getchar();
    return ff?s:-s;
}
void Build(int x,int l,int r){
    if(l==r){
        f[x]=d[l]; return ;
    }
    int mid=(l+r)>>1;
    Build(x<<1,l,mid);
    Build(x<<1|1,mid+1,r);
    f[x]=f[x<<1]*f[x<<1|1];
}
Num Query(int x,int l,int r,int L,int R){
    if(L<=l&&r<=R) return f[x];
    int mid=(l+r)>>1;
    if(mid>=R) return Query(x<<1,l,mid,L,R);
    else if(mid+1<=L) return Query(x<<1|1,mid+1,r,L,R);
    else return Query(x<<1,l,mid,L,R)*Query(x<<1|1,mid+1,r,L,R);
}
int main(){ int x,y,z; Num A;
    k=rd(),n=rd(),m=rd(),q=rd();t=ceil(1.0*n/k);
    while(m--){
        x=rd(),y=rd(),z=rd();
        d[y/k].p[x%k+1][y%k+1]=z;
    }
    Build(1,1,t-1);
    while(q--){
        x=rd(),y=rd();
        if(x/k+1<=y/k) z=Query(1,1,t-1,x/k+1,y/k).p[x%k+1][y%k+1];// 别忘了这里的if 
        else z=M+1;    z>M?printf("-1
"):printf("%d
",z);
    }
    return 0;
}
[BalticOI 2017] Toll

状压DP

第一道,花园

由m<=5很容易想起状压dp,于是设f[i][j]为当前做到第i盆花且最后m盆花的状态为j的方案数。

这个定义非常快乐,然后我们快乐地转移。那么怎么处理环形呢?

我们只需要每次给一种状态j的f[m][j]赋值为1,并做到f[n+m][j],将答案累加即可。

再就快乐地提交,然后快乐地WA(不是

发现n有1015,考虑快速幂。f的转移可以用邻接矩阵代替,因此需要矩阵快速幂。

到这里应该就没问题了,上代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+1e4,P=1e9+7;
int m,k,ans,tot,f[41],g[41]; ll n;
struct Num{
    int p[41][41];
    Num(){
        memset(p,0,sizeof(p));
    }
}b,c;
ll rd(){
    ll s=0,ff=1;
    char w=getchar();
    while(!isdigit(w))
        ff^=w=='-',w=getchar();
    while(isdigit(w))
        s=s*10+w-'0',w=getchar();
    return ff?s:-s;
}
void Dfs(int x,int s,int d){
    if(x>m){
        if(d<=k) f[s]=++tot,g[tot]=s; return ;
    }
    Dfs(x+1,s<<1,d);
    Dfs(x+1,s<<1|1,d+1);
}
Num Chen(Num A,Num B){
    Num C;
    for(int i=1;i<=tot;i++)
        for(int j=1;j<=tot;j++)
            for(int k=1;k<=tot;k++)
                C.p[i][j]=(C.p[i][j]+(ll)A.p[i][k]*B.p[k][j]%P)%P;
    return C;
}
int main(){ int x;
    n=rd(); m=rd(); k=rd(); Dfs(1,0,0);
    for(int i=1;i<=tot;i++) c.p[i][i]=1;
    for(int i=1;i<=tot;i++){
        x=(g[i]>>1)+(1<<(m-1));
        if(f[x]) b.p[f[x]][i]=1;
        x=(g[i]>>1);
        if(f[x]) b.p[f[x]][i]=1;
    }
    while(n){
        if(n&1) c=Chen(b,c);
        b=Chen(b,b); n>>=1;
    }
    for(int i=1;i<=tot;i++)
        ans=(ans+c.p[i][i])%P;
    printf("%d
",ans);
    return 0;
}
花园

最后做了道小水题[EER2]谔运算,对每个数位进行讨论就行了。行吧就这样,明天再说。拜拜。

原文地址:https://www.cnblogs.com/manmanjiangQwQ/p/13303726.html