专题六——复杂DP

1.1050.鸣人的影分身

状态表示:f(i,j):所有总和是i且分成j个数的总方案
状态计算:分成两个集合:最小值是0:f(i,j-1);最小值不是0::让每个方案里面的数减一:f(i-j,j)
f(i,j)=f(i,j-1)+f(i-j,j)

#include <bits/stdc++.h>

using namespace std;
const int N=11;
int f[N][N];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m;
        scanf("%d%d",&m,&n);
        f[0][0]=1;
        for(int i=0;i<=m;i++)
            for(int j=1;j<=n;j++)
        {
            f[i][j]=f[i][j-1];
            if(i>=j) f[i][j]+=f[i-j][j];
        }
        printf("%d\n",f[m][n]);
    }
    return 0;
}

2.1047糖果

状态表示:所有从前i个物品中选且糖数总和除以k的余数是j的所有方案
状态计算:不选第i个物品:f[i-1][j] ;选第i个物品f[i][j-w%k]+w

#include <bits/stdc++.h>

using namespace std;
const int N=110;
int n,k;
int f[N][N];
int main()
{
    scanf("%d%d",&n,&k);
    memset(f,-0x3f,sizeof f);
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        int w;
        scanf("%d",&w);
        for(int j=0;j<k;j++)
            f[i][j]=max(f[i-1][j],f[i-1][(j+k-w%k)%k]+w);
    }
    printf("%d\n",f[n][0]);
    return 0;
}

3.1222. 密码脱落

tips:
①一个字符串添加多少字符可以变成回文串<=>删除多少字符可以变成回文串
②答案=总长度-最长回文串的长度
③最长回文串在本题中可以不连续
④枚举过程先枚举长度,再枚举左端点,可以算出右端点,省去边界问题

#include <bits/stdc++.h>

using namespace std;
const int N=1010;
char s[N];
int f[N][N];
int main()
{
    scanf("%s",s);
    int n=strlen(s);
    for(int len=1;len<=n;len++)
        for(int l=0;l+len-1<n;l++)
    {
        int r=l+len-1;
        if(len==1) f[l][r]=1;
        else
        {
            if(s[l]==s[r]) f[l][r]=f[l+1][r-1]+2;
            if(f[l][r-1]>f[l][r]) f[l][r]=f[l][r-1];
            if(f[l+1][r]>f[l][r]) f[l][r]=f[l+1][r];
        }
    }
    printf("%d\n",n-f[0][n-1]);
    return 0;
}

4.1220.生命之树

状态表示:在以u为根的子树中包含u的所有连通块的权值最大值
f[u]=wu+max{f(si),0}

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N=100010,M=N*2;//分别代表无向图的点数和边数
int n;
int w[N];
int h[N],e[M],ne[M],idx;
LL f[N];
void add(int a, int b)
{
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs(int u,int father)
{
    f[u]=w[u];
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j!=father)
        {
            dfs(j,u);
            f[u]+=max(0ll,f[j]);
        }
    }
}
int main()
{
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=0;i<n-1;i++)//n-1条边,无向图
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    dfs(1,-1);
    LL res=f[1];
    for(int i=2;i<=n;i++) res=max(res,f[i]);
    printf("%lld\n",res);
    return 0;
}

5.1303.斐波那契前n项和

使用矩阵快速幂进行求解

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N=3;
int n,m;
void mul(int c[],int a[],int b[][N])//矩阵乘法:1维*2维
{
    int temp[N]={0};
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
        temp[i]=(temp[i]+(LL)a[j]*b[j][i])%m;
    memcpy(c,temp,sizeof temp);
}
void mul(int c[][N],int a[][N],int b[][N])//矩阵乘法:2维*2维
{
    int temp[N][N]={0};
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
        for(int k=0;k<N;k++)
        temp[i][j]=(temp[i][j]+(LL)a[i][k]*b[k][j])%m;
    memcpy(c,temp,sizeof temp);
}
int main()
{
    cin>>n>>m;
    int f1[N]={1,1,1};
    int a[N][N]={
        {0,1,0},
        {1,1,1},
        {0,0,1}
    };
    n--;
    while(n)//矩阵快速幂模板
    {
        if(n&1) mul(f1,f1,a);//res=res*a
        mul(a,a,a);//a=a*a
        n>>=1;
    }
    cout<<f1[2]<<endl;
    return 0;
}

6.1226.包子凑数

状态表示:f[i][j]:所有只考虑前i个物品,且总和是j的方案数
属性:bool类型,判断集合是否为空即j能否被凑出
状态计算:同完全背包

#include <bits/stdc++.h>

using namespace std;
const int N=10010;
int a[110];
bool f[110][N];
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
int main()
{
    int n;
    scanf("%d",&n);
    int d=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        d=gcd(d,a[i]);//找到n个数的最大公约数
    }
    if(d!=1) puts("INF");//凑不出来的无解的条件
    else//问题转化为完全背包
    {
        f[0][0]=true;
        for(int i=1;i<=n;i++)
            for(int j=0;j<N;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=a[i]) f[i][j]|=f[i][j-a[i]];
        }
        int res=0;
        for(int i=0;i<N;i++)
            if(!f[n][i]) res++;
        printf("%d\n",res);
    }
    return 0;
}

7.1070.括号配对

区间DP的枚举顺序:区间长度,左端点,右端点,与上面的回文串的分析类似

#include <bits/stdc++.h>

using namespace std;
const int N=110,INF=100000000;
int n;
int f[N][N];
bool is_match(char l,char r)
{
    if(l=='('&&r==')') return true;
    if(l=='['&&r==']') return true;
    return false;
}
int main()
{
    string s;
    cin>>s;
    n=s.size();
    for(int len=1;len<=n;len++)
        for(int i=0;i+len-1<n;i++)
    {
        int j=i+len-1;
        f[i][j]=INF;
        if(is_match(s[i],s[j])) f[i][j]=f[i+1][j-1];
        if(j>=1) f[i][j]=min(f[i][j],min(f[i][j-1],f[i+1][j])+1);
        for(int k=i;k<j;k++)
            f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
    }
    cout<<f[0][n-1]<<endl;
    return 0;
}

8.1078.旅游规划

题意:判断各个点是否在树的直径上
树的直径:图上相距最远的两个点的距离

#include <bits/stdc++.h>
using namespace std;
const int N=200010,M=N*2;
int n,maxd;
int h[N],e[M],ne[M],idx;
int d1[N],d2[N],up[N],p1[N];
void add(int a,int b)
{
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs_d(int u,int father)
{
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j!=father)
        {
            dfs_d(j,u);
            int distance=d1[j]+1;
            if(distance>d1[u])
            {
                d2[u]=d1[u];d1[u]=distance;
                p1[u]=j;
            }
            else if(distance>d2[u]) d2[u]=distance;
        }
    }
    maxd=max(maxd,d1[u]+d2[u]);
}
void dfs_u(int u,int father)
{
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j!=father)
        {
            up[j]=up[u]+1;
            if(p1[u]==j) up[j]=max(up[j],d2[u]+1);
            else up[j]=max(up[j],d1[u]+1);
            dfs_u(j,u);
        }
    }
}
int main()
{
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    dfs_d(0,-1);
    dfs_u(0,-1);
    for(int i=0;i<n;i++)
    {
        int d[3]={d1[i],d2[i],up[i]};
        sort(d,d+3);
        if(d[1]+d[2]==maxd) printf("%d\n",i);
    }
    return 0;
}

9.1217.垒骰子

与上方的斐波那契前n项和的算法基本相似
状态表示:f[i][j]:由i个骰子垒在一起,最上面的数字是j的所有方案的集合

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N=6,mod=1e9+7;
int n,m;
int get_op(int x)
{
    if(x>=3) return x-3;
    return x+3;
}
void mul(int c[][N],int a[][N],int b[][N])
{
    static int t[N][N];
    memset(t,0,sizeof t);
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
        for(int k=0;k<N;k++)
        t[i][j]=(t[i][j]+(LL)a[i][k]*b[k][j])%mod;
    memcpy(c,t,sizeof t);
}
int main()
{
    cin>>n>>m;
    int a[N][N];
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
        a[i][j]=4;
    while(m--)
    {
        int x,y;
        cin>>x>>y;
        x--;y--;
        a[x][get_op(y)]=0;
        a[y][get_op(x)]=0;
    }
    int f[N][N]={4,4,4,4,4,4};
    for(int k=n-1;k;k>>=1)//快速幂
    {
        if(k&1) mul(f,f,a);
        mul(a,a,a);
    }
    int res=0;
    for(int i=0;i<N;i++) res=(res+f[0][i])%mod;
    cout<<res<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值