• 动态规划23题解析


    最近两周做了动态规划的23道经典好题,涉及到区间、树形、数位等三种动态规划类型,现在将这23道题的题解写在下面,方便大家借鉴以及我加深记忆。

    upd at:20190815 13:41.T14周年纪念晚会

    1、石子合并

    经典的区间DP问题,枚举合并的堆数作为阶段,设f[i][j]表示i->j这段区间内的最优方案,考虑在这段区间内枚举断点k,不难得到f[i][j]=min(f[i][k]+f[k+1][j]+sum(i,j))(最大值同理)。破环为链后直接进行DP即可。

    #include<iostream>
    #include<cstring>
    #include<cstdio> 
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        if(f)return x;return -x;
    }
    int n,a[1005],f[505][505],g[505][505],prefix[505],minn=21374404,maxn; 
    int main()
    {
        //freopen("A.in","r",stdin);
        //freopen("A.out","w",stdout);
        memset(f,20,sizeof(f));
        n=read();
        for(int i=1;i<=n;i++)
        {
            a[i]=read();
            a[i+n]=a[i];
        }
        for(int i=1;i<=n*2;i++)
        {
            prefix[i]=prefix[i-1]+a[i];
            f[i][i]=0;
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=2*n-i+1;j++)
            {
                int end=i+j-1;
                for(int k=j;k<end;k++)
                {
                    f[j][end]=min(f[j][end],f[j][k]+f[k+1][end]+prefix[end]-prefix[j-1]);
                    g[j][end]=max(g[j][end],g[j][k]+g[k+1][end]+prefix[end]-prefix[j-1]);
                }
            }
        }
        for(int i=1;i<=n;i++)
        {
            minn=min(minn,f[i][i+n-1]);
            maxn=max(maxn,g[i][i+n-1]);
        }
        printf("%d
    %d
    ",minn,maxn);
        //fclose(stdin);
        //fclose(stdout);
        return 0;
    }
    石子合并

    2、能量项链

    同“石子合并”,将第一题的求和换成题目指定的模拟规则即可。

    #include<iostream>
    #include<cstring>
    #include<cstdio> 
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        if(f)return x;return -x;
    }
    int n,a[1005],f[505][505],b[1005],maxn; 
    int main()
    {
        //freopen("B.in","r",stdin);
        //freopen("B.out","w",stdout);
        n=read();
        for(int i=1;i<=n;i++)
        {
            a[i]=read();
            a[i+n]=a[i];
        }
        for(int i=1;i<=n*2-1;i++)
        {
            b[i]=a[i+1];
        }
        b[n*2]=a[1];
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=2*n-i+1;j++)
            {
                int end=i+j-1;
                for(int k=j;k<end;k++)
                {
                    f[j][end]=max(f[j][end],f[j][k]+f[k+1][end]+a[j]*b[k]*b[end]);
                }
            }
        }
        for(int i=1;i<=n;i++)
        {
            maxn=max(maxn,f[i][i+n-1]);
        }
        printf("%d
    ",maxn);
        //fclose(stdin);
        //fclose(stdout);
        return 0;
    }
    能量项链

    3、凸多边形的划分

    设f[i][j]为i号节点到j号节点组成的凸多边形的最优剖分,我们可以在这段区间内找到一个非i、j的顶点k,剖分出一个以i,j,k为顶点的三角形和两个凸多边形.

    然后问题就转化成了如何求这两个小凸多边形的和,从小区间到大区间转移的过程中,小区间是已经被计算过的,直接调用即可。

    状态转移方程如下:f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[i]*a[j]*a[k]);

    注意,此题需要用高精度或int128

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define int __uint128_t
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        if(f)return x;return -x;
    }
    void myitoa(int a,char* s)
    {
        int w=0;
        while(a>0)
        {
            s[++w]=a%10+'0';
            a/=10;
        }
        s[0]=w;
    }
    int n,a[505],f[110][110];
    char s[110];
    signed main()
    {
        //freopen("C.in","r",stdin);
        //freopen("C.out","w",stdout);
        n=read();
        for(int i=1;i<=n;i++)a[i]=read();
        for(int i=2;i<=n-1;i++)
        {
            for(int j=1;j<=n-i;j++)
            {
                int end=i+j;
                f[j][end]=1e30;
                for(int k=j+1;k<end;k++)
                {
                    f[j][end]=min(f[j][end],f[j][k]+f[k][end]+a[j]*a[k]*a[end]);
                }
            }
        }
        myitoa(f[1][n],s);
        for(int i=s[0];i>=1;i--)
        {
            cout<<s[i];
        }
        //fclose(stdin);
        //fclose(stdout);
        return 0;
    }
    凸多边形的划分

    4、括号匹配

    通过题目很容易发现这道题的边界值:当i和i+1可以匹配的时候,它们合并的代价为0,否则为2。直接按照区间DP模板进行合并,两部分的代价加起来取最小就是最优值,特别地,当两个端点本身就可以匹配的时候,还要和中间的再取一次min。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        if(f)return x;return -x;
    }
    char a[155];
    int n,f[155][155];
    int main()
    {
        memset(f,0x3f,sizeof(f));
        cin>>(a+1);
        n=strlen(a+1);
        for(int i=1;i<=n;i++)f[i][i]=1;
        for(int i=1;i<=n;i++)
        {
            if(a[i]=='('&&a[i+1]==')')f[i][i+1]=0;
            else if(a[i]=='['&&a[i+1]==']')f[i][i+1]=0;
            else f[i][i+1]=2;
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=n-i+1;j++)
            {
                int end=i+j-1;
                for(int k=j;k<end;k++)
                {
                    f[j][end]=min(f[j][end],f[j][k]+f[k+1][end]);
                }
                if(a[j]=='('&&a[end]==')')f[j][end]=min(f[j][end],f[j+1][end-1]);
                else if(a[j]=='['&&a[end]==']')f[j][end]=min(f[j][end],f[j+1][end-1]);
            } 
        }
        cout<<f[1][n]<<endl;
        return 0;
    }
    括号匹配

    5、分离与合体

    需要记录合并的地方,然后递归输出即可。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        if(f)return x;return -x;
    }
    int n,a[505],f[505][505],b[505][505];
    void dfs(int x,int y,int dep,int k)
    {
        if(x>=y)return;
        if(dep==k)
        {
            printf("%d ",b[x][y]);
            return;
        }
        dfs(x,b[x][y],dep+1,k);
        dfs(b[x][y]+1,y,dep+1,k);
    }
    int main()
    {
        n=read();
        for(int i=1;i<=n;i++)a[i]=read();
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=n-i+1;j++)
            {
                int end=i+j-1;
                for(int k=j;k<end;k++)
                {
                    if(f[j][k]+f[k+1][end]+(a[j]+a[end])*a[k]>f[j][end])
                    {
                        f[j][end]=f[j][k]+f[k+1][end]+(a[j]+a[end])*a[k];
                        b[j][end]=k;
                    }
                }
            }
        }
        printf("%d
    ",f[1][n]);
        for(int i=1;i<n;i++)
        {
            dfs(1,n,1,i);
        }
    }
    分离与合体

    6、矩阵取数游戏

    DP五分钟,高精两小时!!!!!!这题DP其实还蛮好想的,因为小区间是由大区间转移而来,所以每一个区间要么是由左边取一个数后得到,要么是由右边取一

    数得到,不难得到如下的转移方程:

    f[i][j]=max(f[i-1][j]+base[m-j+i-1]*a[i-1],f[i][j+1]+base[m-j+i-1]*a[j+1]);

    最后由于空区间无法表示,最后还要再取一边max。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #define int long long
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        if(f)return x;return -x;
    }
    int n,m,a[110],f[110][110][110],base[110][110],ans[110],maxn,s1[110],s2[110],s3[110],s4[110],s5[110],s6[110],s7[110],s8[110];
    void Mark(int c[])
    {
        for(int i=1;i<=30;i++)
        {
            c[i+1]+=c[i]/10000;
            c[i]%=10000;
        }
        for(int i=30;i>=1;i--)
        {
            if(c[i])
            {
                c[0]=i;break;
            }
        }
    }
    void Mul(int a[],int b,int c[])
    {
        for(int i=1;i<=a[0];i++)c[i]=a[i]*b;
        Mark(c);
    }
    void Add(int a[],int b[],int c[])
    {
        Mark(a);Mark(b);
        if(a[0]>b[0])c[0]=a[0];
        else c[0]=b[0];
        for(int i=1;i<=c[0];i++)c[i]=a[i]+b[i];
        Mark(c);
    }
    bool compare(int a[],int b[])
    {
        Mark(a);Mark(b); 
        if(a[0]<b[0])return 0;if(a[0]>b[0])return 1;
        for(int i=a[0];i>=1;i--)
        {
            if(a[i]<b[i])return 0;
            if(b[i]<a[i])return 1;
        }
        return 0;
    }
    void pre()
    {
        base[0][0]=base[0][1]=1;
        for(int i=1;i<=m;i++)Mul(base[i-1],2,base[i]);
    }
    void write(int ans[])
    {
        cout<<ans[ans[0]];
        for(int i=ans[0]-1;i>=1;i--)
        {
            cout<<ans[i]/1000;
            cout<<ans[i]/100%10;
            cout<<ans[i]/10%10;
            cout<<ans[i]%10;
        }
    }
    signed main()
    {
        //freopen("F.in","r",stdin);
        //freopen("F.out","w",stdout);
        n=read();m=read();
        pre();
        while(n--)
        {
            memset(f,0,sizeof(f));  
            memset(s5,0,sizeof(s5));
            memset(s6,0,sizeof(s6));
            for(int i=1;i<=m;i++)a[i]=read();
            for(int i=1;i<=m;i++)
            {
                for(int j=m;j>=i;j--)
                {
                    memset(s1,0,sizeof(s1));
                    memset(s2,0,sizeof(s2));
                    memset(s7,0,sizeof(s7));
                    memset(s8,0,sizeof(s8));
                    Mul(base[m-j+i-1],a[i-1],s1);
                    Add(s1,f[i-1][j],s7);
                    Mul(base[m-j+i-1],a[j+1],s2);
                    Add(s2,f[i][j+1],s8);
                    if(compare(s7,s8))memcpy(f[i][j],s7,sizeof(f[i][j]));
                    else memcpy(f[i][j],s8,sizeof(f[i][j]));
                }
            }
            for(int i=1;i<=m;i++)
            {
                memset(s3,0,sizeof(s3));
                memset(s4,0,sizeof(s4));
                Mul(base[m],a[i],s3);
                Add(f[i][i],s3,s4);
                if(compare(s4,s5))memcpy(s5,s4,sizeof(s5));
            }
            Add(ans,s5,s6);
            memcpy(ans,s6,sizeof(ans));
        }
        write(ans);
        //fclose(stdin);
        //fclose(stdout);
        return 0;
    }
    /*
    2 10
    96 56 54 46 86 12 23 88 80 43
    16 95 18 29 30 53 88 83 64 67
    */
    矩阵取数游戏

    7、二叉苹果树

    建树后依次枚举给左右子树保留的树枝,并不难的记忆化搜索。

    #include<iostream>
    #include<cstring>
    using namespace std;
    int mp[1005][1005],n,q,l[1005],r[1005],a[1005],f[1005][1005];
    void Maketree(int node)
    {
        for(int i=1;i<=n;i++)
        {
            if(mp[node][i]==-1)continue;
            l[node]=i;a[i]=mp[node][i];
            mp[node][i]=mp[i][node]=-1;
            Maketree(i);
            break;
        }
        for(int i=1;i<=n;i++)
        {
            if(mp[node][i]==-1)continue;
            r[node]=i;a[i]=mp[node][i];
            mp[node][i]=mp[i][node]=-1;
            Maketree(i);
            break;
        }
    }
    int dfs(int u,int w)
    {
        if(w==0)return 0;
        if(l[u]==0&&r[u]==0)return a[u];
        if(f[u][w])return f[u][w];
        for(int i=0;i<=w-1;i++)
        {
            f[u][w]=max(f[u][w],dfs(l[u],i)+dfs(r[u],w-i-1)+a[u]);
        }
        return f[u][w];
    }
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        memset(mp,-1,sizeof(mp));
        cin>>n>>q;
        for(int x,y,z,i=1;i<n;i++)
        {
            cin>>x>>y>>z;
            mp[x][y]=mp[y][x]=z;
        }
        Maketree(1);
        cout<<dfs(1,q+1)<<endl;
        return 0;
    }
    二叉苹果树

     8、选课
    首先把先修课设为该门课程的父亲,我们就得到了一棵以0号节点为根的树,设f[x][j]表示以x为根的子树,由于每个节点每种状态只能有一个状态转移到父亲节点。

    我们就可以建立一个分组背包模型,用记忆化搜索求解即可

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    using namespace std;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
        return x*f;
    }
    int n,m,s[505],f[505][505];
    vector<int>son[505];
    void dp(int x)
    {
        for(int i=0;i<son[x].size();i++)
        {
            int v=son[x][i];
            dp(v);
            for(int t=m;t>=0;t--)
            {
                for(int j=t;j>=0;j--)
                {
                    f[x][t]=max(f[x][t],f[x][t-j]+f[v][j]);
                }
            }
        }
        if(x!=0)
        {
            for(int t=m;t>0;t--)
            {
                f[x][t]=f[x][t-1]+s[x];
            }
        }
        return;
    }
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=n;i++)
        {
            int x;
            x=read();s[i]=read();
            son[x].push_back(i);
        }
        dp(0);
        cout<<f[0][m]<<endl;
        return 0;
    }
    选课

    9、数字转换

    可以先把所有数的约数和求出来,然后把符合条件的两个数之间建边,最后以1为根求树的直径即可。

    #include<iostream>
    using namespace std;
    int n,sum[50005],v[500005],head[500005],nxt[500005],cnt,ans,d[50005];
    bool vis[50005];
    void add(int a,int b)
    {
        v[++cnt]=b;
        nxt[cnt]=head[a];
        head[a]=cnt;
    }
    void dp(int x)
    {
        for(int i=head[x];i;i=nxt[i])
        {
            int y=v[i];
            if(vis[y])continue;
            vis[y]=1;
            dp(y);
            ans=max(ans,d[x]+d[y]+1);
            d[x]=max(d[x],d[y]+1);
        }
    }
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            for(int j=2;i*j<=n;j++)
            {
                sum[i*j]+=i;
            }
        }
        for(int i=1;i<=n;i++)
        {
            if(sum[i]<i)
            {
                add(sum[i],i);
            }
        }
        dp(1);
        cout<<ans<<endl;
        return 0;
    }
    数字转换

    10、战略游戏

    题意大致为要我们求一个点集,使得每条边上至少有一个顶点属于该点集。

    这个问题又叫做“树的最大独立集”问题,可以用树形DP求解。

    由于每一个点都有取和不取两种状态,因此我们可以针对两种情况进行讨论:

    设f[x][0]为x号节点不选,以x为根的子树符合条件的最小值,由于该点不选,所以其所有儿子节点必须选,则该状态的状态转移方程为:f[x][0]=∑f[y][1](y∈son[x])

    设f[x][1]表示x节点选,以x为根的子树符合条件的最小值,由于该点必选,所以其所有儿子均可选可不选,取最小值即可,DP方程:f[x][1]=∑min(f[y][0],f[y][1])(y∈son[x])

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    int n,s,k,t,v[50005],head[50005],nxt[50005],cnt,f[3505][2];
    void add(int a,int b)
    {
        v[++cnt]=b;
        nxt[cnt]=head[a];
        head[a]=cnt;
    }
    void dp(int x,int fa)
    {
        f[x][1]=1;
        for(int i=head[x];i;i=nxt[i])
        {
            int y=v[i];
            if(y==fa)continue;
            dp(y,x);
            f[x][0]+=f[y][1];
            f[x][1]+=min(f[y][0],f[y][1]);
        }
    }
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>s>>k;s++;
            for(int i=1;i<=k;i++)
            {
                cin>>t;t++;
                add(s,t);add(t,s);
            }
        }
        dp(1,0);
        cout<<min(f[1][0],f[1][1])<<endl;
        return 0;
    }
    战略游戏

    11、皇宫看守

    这题和T10差不多,只不过是由边变成了点,可以设计三种状态分别对应父亲看守、儿子看守、和自己看守。

    f[x][0]表示父亲看守,所以这个节点不用放兵,他的子节点自己解决或者让孙子节点解决,取小即可f[x][0]=∑min(f[y][1],f[y][2])(y∈son[x])

    f[x][1]表示儿子看守,这个时候x不用看守,儿子节点指望不上父亲看守,所以只能选择孙子看守和自己看守,需要注意的是,如果所有节点都让儿子看守(也就是x的孙子),

    那么还需要挑一个差值最小的补上来看父亲。

    f[x][2]表示自己看守,此时儿子三种选择均可,取小即可。

    #include<iostream>
    using namespace std;
    int n,k,dl,xps,r,a[1550],v[150005],head[150005],nxt[150005],cnt,f[1550][3],vis[1555];
    void add(int a,int b)
    {
        v[++cnt]=b;
        nxt[cnt]=head[a];
        head[a]=cnt;
    }
    void dp(int x)
    {
        f[x][2]=a[x];
        int d=21374404;
        for(int i=head[x];i;i=nxt[i])
        {
            int y=v[i];
            dp(y);
            f[x][0]+=min(f[y][1],f[y][2]);
            f[x][1]+=min(f[y][1],f[y][2]);
            f[x][2]+=min(f[y][0],min(f[y][1],f[y][2]));
            d=min(d,f[y][2]-min(f[y][1],f[y][2]));
        }
        f[x][1]+=d;
    }
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>dl;
            cin>>a[dl]>>k;
            for(int j=1;j<=k;j++)
            {
                cin>>xps;
                add(dl,xps);
                //add(xps,dl);
                vis[xps]++;
            }
        }
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                r=i;
                break;
            }
        }
        dp(r);
        //for(int i=1;i<=n;i++)cout<<i<<" "<<a[i]<<endl;
        cout<<min(f[r][1],f[r][2]);
        return 0;
    }
    皇宫看守

    12、加分二叉树

    二叉树的基本概念不在这里细说,这题本质上是个区间DP,枚举断点作为子树的根,最后递归遍历输出即可。

    #include<iostream>
    #define int long long
    using namespace std;
    int n,f[505][505],root[505][505];
    void write(int l,int r)
    {
        if(l>r)return;
        if(l==r&&r!=n){cout<<l<<" ";return;}
        else if(l==r){cout<<l;return;}
        if(root[l][r]!=n)cout<<root[l][r]<<" ";
        else cout<<root[l][r];
        write(l,root[l][r]-1);
        write(root[l][r]+1,r);
    }
    signed main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;i++)cin>>f[i][i],f[i][i-1]=1,root[i][i]=i;
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=n-i+1;j++)
            {
                int end=i+j-1;
                f[j][end]=f[j+1][end]+f[j][j];
                root[j][end]=j;
                for(int k=j+1;k<=end-1;k++)
                {
                    if(f[j][end]<f[j][k-1]*f[k+1][end]+f[k][k])
                    {
                        f[j][end]=f[j][k-1]*f[k+1][end]+f[k][k];
                        root[j][end]=k;
                    }
                }
            }
        }
        cout<<f[1][n]<<endl;
        write(1,n);
        return 0;
    }
    加分二叉树

    13、旅游规则

    这道题考察对树的直径的灵活运用。树的直径可以看做是两条链拼成的,所以在树的直径上的点一定可以引出没有公共边的两条链,总和加起来为树的直径,我们只需枚举每一个点,看他往父亲节点引边收益的最大值以及次短链的最大值,加起来和最长链拼在一起,如果是直径就是答案,特别地,要注意该点本身就在父亲节点的最长链上,需要特殊处理

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    int n,v[400005],head[400005],nxt[400005],cnt,f[200005][3],ans,son[200005];
    void add(int a,int b)
    {
        v[++cnt]=b;
        nxt[cnt]=head[a];
        head[a]=cnt;
    }
    void dp(int x,int fa)
    {
        for(int i=head[x];i!=-1;i=nxt[i])
        {
            int y=v[i];
            if(y==fa)continue;
            dp(y,x);
            int k=f[y][0]+1;
            if(k>f[x][0]){f[x][1]=f[x][0];f[x][0]=k;son[x]=y;}
            else if(k>f[x][1]){f[x][1]=k;}
            ans=max(ans,f[x][0]+f[x][1]);
        }
    }
    void dfs(int x,int fa)
    {
        if(x!=0)
        {
            if(son[fa]!=x)f[x][2]=1+max(f[fa][2],f[fa][0]);
            else f[x][2]=1+max(f[fa][2],f[fa][1]);
        }
        for(int i=head[x];i!=-1;i=nxt[i])
        {
            int y=v[i];
            if(y==fa)continue;
            dfs(y,x);
        }
    }
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        memset(head,-1,sizeof(head));memset(son,-1,sizeof(son));
        cin>>n;
        for(int x,y,i=1;i<n;i++)
        {
            cin>>x>>y;
            add(x,y);add(y,x);
        }
        dp(0,-1);
        dfs(0,-1);
        for(int i=0;i<n;i++)if(max(f[i][2],f[i][1])+f[i][0]==ans)cout<<i<<endl;
        //cout<<ans<<endl;
        //for(int i=0;i<n;i++)cout<<f[i][0]<<" "<<f[i][1]<<" "<<f[i][2]<<endl;
        return 0;
    }
    旅游规则

    14、周年纪念晚会

    同“战略游戏”,只不过由必须有端点变成不能同时有两个端点。

    #include<iostream>
    using namespace std;
    int n,a[6005],f[6005][2],v[15005],head[15005],nxt[15005],cnt,root;
    bool vis[6005];
    void add(int a,int b)
    {
        v[++cnt]=b;
        nxt[cnt]=head[a];
        head[a]=cnt;
    }
    void dp(int x)
    {
        f[x][1]=a[x];
        for(int i=head[x];i;i=nxt[i])
        {
            int y=v[i];
            dp(y);
            f[x][0]+=max(f[y][0],f[y][1]);
            f[x][1]+=f[y][0];
        }
    }
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        for(int x,y,i=1;i<n;i++)
        {
            cin>>x>>y;
            add(y,x);
            vis[x]=1;
        }
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                root=i;
                break;
            }
        }
        dp(root);
        cout<<max(f[root][0],f[root][1])<<endl;
        return 0;
    }
    周年纪念晚会
  • 相关阅读:
    JavaScript字符串和字符数组
    JavaScript数组&类数组转换
    JavaScript判断值是否是NaN
    JavaScript中七种数据类型·中·一
    QRcode.js 生成二维码
    你不知道的JavasScript上篇·第五章·原型·下
    你不知道的JavasScript上篇·第五章·原型·上
    你不知道的JavasScript上篇·第四章·混合对象·类
    你不知道的JavasScript上篇·第三章·对象
    Vue2.x之父子组件数据传递
  • 原文地址:https://www.cnblogs.com/szmssf/p/11354589.html
Copyright © 2020-2023  润新知