dp 杂题选做

区间dp

P3146 [USACO16OPEN] 248 G

考虑设 \(f[i][j]\) 表示区间 \([l,r]\) 合并过的值

枚举断点 当 \(f[l][k]==f[k+1][r]\) 且都有值的时候 可以合并出 \(f[l][k]+1\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define gc getchar
#define pc putchar
const int N=305;
const int M=1e5+5;
const int inf=0x7fffffff;
const int mod=998244353;
const double eps=1e-8;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int n,a[N],f[N][N],ans;
signed main(){
    n=read();
    for(int i=1;i<=n;i++)f[i][i]=read();
    for(int i=1;i<=n;i++)ans=max(ans,f[i][i]);
    for(int len=2;len<=n;len++){
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            for(int k=l;k<r;k++){
                if(f[l][k]==f[k+1][r]&&f[l][k]){
                    f[l][r]=max(f[l][r],f[l][k]+1);
                    ans=max(ans,f[l][r]);
                }
            }
        }
    }
    writel(ans);
    return 0;
}

P3205 [HNOI2010] 合唱队

\(f[l][r][0/1]\) 表示排成 \(a[l-r]\) 队形的方案 且上一个排进来的是左/右

那么只能从 \([l+1,r]\)\([l,r-1]\) 转移(因为只会加到左/右)

判一下左右与当前点的大小关系即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define ll long long
#define int ll
const int N=1e3+5;
const int M=1e6+5;
const int inf=0x7fffffff;
const int mod=19650827;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
inl void write(int x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,a[N],f[2][N][N];
signed main(){
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++)f[0][i][i]=0,f[1][i][i]=1;
    for(int len=2;len<=n;len++){
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            f[0][l][r]=(f[0][l][r]+(a[l+1]>a[l])*f[0][l+1][r]+(a[r]>a[l])*f[1][l+1][r])%mod;
            f[1][l][r]=(f[1][l][r]+(a[l]<a[r])*f[0][l][r-1]+(a[r-1]<a[r])*f[1][l][r-1])%mod;
        }
    }
    writel((f[0][1][n]+f[1][1][n])%mod);
    return 0;
}

背包

P1941 [NOIP2014 提高组] 飞扬的小鸟

细节有点多。

发现跳/不跳/跳几次 类似背包的选/不选

\(f[i][j]\) 代表调到第 \(i\) 列 高度为 \(j\) 的最小步数

那么类似01背包的:

\[f[i][j]=\min(f[i][j],f[i-1][j-up[i-1]]+1) \]

\[f[i][j]=\min(f[i][j],f[i-1][j+down[i-1]]) \]

类似完全背包的:

\[f[i][j]=\min(f[i][j],f[i][j-up[i-1]]+1) \]

对于跳的时候顶到上方的情况 特殊处理一下

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define ll long long
const int N=1e4+5;
const int M=1e3+5;
const int inf=0x3f3f3f3f;
const int mod=19650827;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
inl void write(int x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,m,k,f[N][M],up[N],down[N],top[N],flr[N],p,l,h,cnt,mi;
signed main(){
    n=read();m=read();k=read();
    for(int i=0;i<=n-1;i++)up[i]=read(),down[i]=read();
    for(int i=1;i<=n;i++)top[i]=m+1,flr[i]=0;
    while(k--){
        p=read();l=read();h=read();
        top[p]=h,flr[p]=l;
    }
    memset(f,0x3f,sizeof f);
    for(int i=0;i<=m;i++)f[0][i]=0;
    for(int i=1;i<=n;i++){
        for(int j=up[i-1];j<=m;j++)f[i][j]=min(f[i-1][j-up[i-1]]+1,f[i][j-up[i-1]]+1);
        for(int j=m+1;j<=m+up[i-1];j++)f[i][m]=min(f[i][m],min(f[i-1][j-up[i-1]]+1,f[i][j-up[i-1]]+1));
        for(int j=1;j+down[i-1]<=m;j++)f[i][j]=min(f[i][j],f[i-1][j+down[i-1]]);
        for(int j=1;j<=flr[i];j++)f[i][j]=inf;
        for(int j=top[i];j<=m;j++)f[i][j]=inf;
        mi=inf;
        for(int j=1;j<=m;j++)mi=min(mi,f[i][j]);
        if(mi==inf){puts("0");writel(cnt);return 0;}
        cnt+=(bool)(top[i]^(m+1));
    }
    puts("1");writel(mi);
    return 0;
}

计数dp

P6146 [USACO20FEB] Help Yourself G

\(f_i\) 表示取到前 \(i\) 条线段的答案

考虑两种情况:

  • 不选:答案不变 \(f_{i-1}\)
  • 选:与前面连上 答案还是 \(f_{i-1}\) 没连上的情况一定是 满足所有线段右端点都在这条线段左边 的所有子集 为 \(2^x\)

\(x\) 前缀和求一下即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define inl inline
#define int ll
const int N=1e6+5;
const int M=1e5+5;
const int mod=1e9+7;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
inl void write(int x){
    if(x<0){x=-x;putchar('-');}
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,f[N],sum[N],ans;
struct node{
    int l,r;
    friend bool operator<(node a,node b){return a.l<b.l;}
}a[N];
inl int qpow(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
signed main(){
    n=read();
    for(int i=1;i<=n;i++)a[i]={read(),read()};
    for(int i=1;i<=n;i++)sum[a[i].r]++;
    for(int i=1;i<=(n<<1);i++)sum[i]+=sum[i-1];
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++)f[i]=((f[i-1]<<1)+qpow(2,sum[a[i].l-1]))%mod;
    writel(f[n]);
    return 0;
}

数位dp

P4999 烦人的数学作业

题意:给出一个区间 \(L\) ~ \(R\) ,求 \(L\)\(R\) 区间内每个数的数字和,如123这个数的数字和为1+2+3=6。

实际上就是在求 \(L\)\(R\) 区间内 \(1\) ~ \(9\) 出现次数 * 每个数字的值 \([l,r]\) 答案可以用 \([1,r]-[1,l-1]\) 求出

数位dp即可

(这题可以不管前导0 因为0对答案无贡献)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define int ll
#define gc getchar
#define pc putchar
const int N=1e4+5;
const int M=2e3+5;
const int inf=0x7fffffff;
const int mod=1e9+7;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,l,r,f[22][2][22][12],num[12],ans;
int dfs(int pos,int lim,int sum,int x){
    if(!pos)return sum;
    if(~f[pos][lim][sum][x])return f[pos][lim][sum][x];
    int ans=0;
    for(int i=0;i<=9;i++){
        if(lim&&i>num[pos])break;
        ans=(ans+dfs(pos-1,lim&&(i==num[pos]),sum+(i==x),x))%mod;
    }
    return f[pos][lim][sum][x]=ans;
}
inl int solve(int x,int k){
    int pos=0;
    memset(f,-1,sizeof(f));
    while(x){
        num[++pos]=x%10;
        x/=10;
    }
    return dfs(pos,1,0,k);
}
signed main(){
    t=read();
    while(t--){
        l=read();r=read(),ans=0;
        for(int i=1;i<=9;i++)
            ans=(ans+((solve(r,i)-solve(l-1,i)+mod)%mod)*i%mod)%mod;
        writel(ans);
    }
    return 0;
}

P2657 [SCOI2009] windy 数

考虑在状态里存一个pst 表示上一位的数是啥

剩下的就一样了 ans记得初始化

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define gc getchar
#define pc putchar
const int N=1e4+5;
const int M=2e3+5;
const int inf=0x7fffffff;
const int mod=1e9+7;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,l,r,f[12][2][2][22],num[12],ans;
int dfs(int pos,int lim,int zero,int pst){
    if(!pos)return !zero;
    if(~f[pos][lim][zero][pst])return f[pos][lim][zero][pst];
    int ans=0;
    for(int i=0;i<=9;i++){
        if(lim&&i>num[pos])break;
        if(!zero&&abs(i-pst)<2)continue;
        ans+=dfs(pos-1,lim&&(i==num[pos]),zero&&!i,i);
    }
    return f[pos][lim][zero][pst]=ans;
}
inl int solve(int x){
    int pos=0;
    while(x){
        num[++pos]=x%10;
        x/=10;
    }
    memset(f,-1,sizeof f);
    return dfs(pos,1,1,20);
}
signed main(){
    l=read();r=read();
    writel(solve(r)-solve(l-1));
    return 0;
}

P4317 花神的数论题

\(f_i\) 表示 \(sum(i)\) 的数量 那么等价与 求 \(\prod^{n}_{i=1}i^{f_i}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define int ll
#define gc getchar
#define pc putchar
const int N=1e4+5;
const int M=2e3+5;
const int inf=0x7fffffff;
const int mod=10000007;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int n,f[55][2][55][55],num[55],ans=1;
inl int qpow(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int dfs(int pos,int lim,int sum,int x){
    if(!pos)return sum==x;
    if(~f[pos][lim][sum][x])return f[pos][lim][sum][x];
    int ans=0;
    for(int i=0;i<=1;i++){
        if(lim&&i>num[pos])break;
        ans+=dfs(pos-1,lim&&(i==num[pos]),sum+i,x);
    }
    return f[pos][lim][sum][x]=ans;
}
inl int solve(int x){
    int pos=0;
    while(x){
        num[++pos]=x&1;
        x>>=1;
    }
    memset(f,-1,sizeof(f));
    for(int i=1;i<=pos;i++)
        ans=ans*qpow(i,dfs(pos,1,0,i))%mod;
    return ans;
}
signed main(){
    n=read();
    writel(solve(n));
    return 0;
}
posted @ 2023-11-06 20:28  xiang_xiang  阅读(5)  评论(0)    收藏  举报