树状DP题目

本文探讨了两个经典算法问题:一是通过修剪花卉以最大化剩余花卉的美丽指数;二是有线电视网络如何选择转播路径,以便在不亏损的前提下让更多用户观看比赛。文章提供了完整的代码实现。

小明对数学饱有兴趣,并且是个勤奋好学的学生,总是在课后留在教室向老师请教一些问题。一天他早晨骑车去上课,路上见到一个老伯正在修剪花花草草,顿时想到了一个有关修剪花卉的问题。于是当日课后,小明就向老师提出了这个问题:

一株奇怪的花卉,上面共连有NN朵花,共有N-1N−1条枝干将花儿连在一起,并且未修剪时每朵花都不是孤立的。每朵花都有一个“美丽指数”,该数越大说明这朵花越漂亮,也有“美丽指数”为负数的,说明这朵花看着都让人恶心。所谓“修剪”,意为:去掉其中的一条枝条,这样一株花就成了两株,扔掉其中一株。经过一系列“修剪“之后,还剩下最后一株花(也可能是一朵)。老师的任务就是:通过一系列“修剪”(也可以什么“修剪”都不进行),使剩下的那株(那朵)花卉上所有花朵的“美丽指数”之和最大。

老师想了一会儿,给出了正解。小明见问题被轻易攻破,相当不爽,于是又拿来问你。

输入格式

第一行一个整数N(1 ≤ N ≤ 16000)N(1≤N≤16000)。表示原始的那株花卉上共NN朵花。

第二行有NN个整数,第II个整数表示第II朵花的美丽指数。

接下来N-1N−1行每行两个整数a,ba,b,表示存在一条连接第aa 朵花和第bb朵花的枝条。

输出格式

一个数,表示一系列“修剪”之后所能得到的“美丽指数”之和的最大值。保证绝对值不超过21474836472147483647。

ac:

#include<iostream>
#include<cstring>
using namespace std;
#define int long long
const int maxn=2e4+5;
const int INF=-1e10;
int head[maxn<<1];
int val[maxn];int vis[maxn];
struct node{
    int next,to;
}edge[maxn<<1];
int cnt=0;
void add_edge(int from,int to)
{
    cnt++;
    edge[cnt].next=head[from];
    edge[cnt].to=to;
    head[from]=cnt;
}
int f[maxn];
void dfs(int root,int fa)
{
    for(int i=head[root];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to!=fa)
        {
            dfs(to,root);
            f[root]+=(f[to]>0?f[to]:0);
        }
    }
}
signed main()
{
    int n;
    cin>>n;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++) cin>>f[i];
    for(int i=0;i<n-1;i++) {
        int from,to;cin>>from>>to;
        add_edge(from,to);
        add_edge(to,from);
    }
    dfs(1,0);int ans=INF;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,f[i]);
    }
    cout<<ans<<endl;
}

题目描述

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。

从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。

现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。

写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。

输入格式

输入文件的第一行包含两个用空格隔开的整数 NN 和 MM,其中 2 \le N \le 30002≤N≤3000,1 \le M \le N-11≤M≤N−1,NN 为整个有线电视网的结点总数,MM 为用户终端的数量。

第一个转播站即树的根结点编号为 11,其他的转播站编号为 22 到 N-MN−M,用户终端编号为 N-M+1N−M+1 到 NN。

接下来的 N-MN−M 行每行表示—个转播站的数据,第 i+1i+1 行表示第 ii 个转播站的数据,其格式如下:

K \ \ A_1 \ \ C_1 \ \ A_2 \ \ C_2 \ \ \ldots \ \ A_k \ \ C_kK  A1​  C1​  A2​  C2​  …  Ak​  Ck​

KK 表示该转播站下接 KK 个结点(转播站或用户),每个结点对应一对整数 AA 与 CC ,AA 表示结点编号,CC 表示从当前转播站传输信号到结点 AA 的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。单次传输成本和用户愿意交的费用均不超过 10。

#include<iostream>
#include<cstring>
using namespace std;
#define int long long
const int maxn=3010;
int dp[maxn][maxn];int val[maxn];
int t1[maxn];
int head[maxn];int n,m;int t;
struct node{
    int to,next,cost;
}edge[maxn*maxn];
int cnt=0;
void add_edge(int from,int to,int cost)
{
    cnt++;
    edge[cnt].to=to;
    edge[cnt].next=head[from];
    edge[cnt].cost=cost;
    head[from]=cnt;
}
int  dfs(int now)
{
    //cout<<now<<endl;
    if(now>t)
    {
        dp[now][1]=val[now];
        return 1;
    }
    int sum=0;
    for(int i=head[now];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        int tk=dfs(to);
        //cout<<now<<" "<<to<<" "<<sum<<endl;
        for (int j=0;j<=sum;j++) t1[j]=dp[now][j];
        for(int j=0;j<=sum;j++)
        {
            for(int k=0;k<=tk;k++)
            {
                dp[now][j+k]=max(dp[now][j+k],dp[to][k]+t1[j]-edge[i].cost);
            }
        }
        sum+=tk;
    }
    return sum;
}
signed main()
{
    cin>>n>>m;
    t=n-m;
    memset(head,-1,sizeof(head));
    memset(dp,~0x3f,sizeof(dp));
    for(int i=1;i<=t;i++)
    {
        int k;cin>>k;
        while(k--) {
            int to;int cost;cin>>to>>cost;
            add_edge(i,to,cost);
        }
    }
    for(int i=1;i<=n;i++) dp[i][0]=0;
    for(int i=t+1;i<=n;i++)
    {
        cin>>val[i];
    }
    dfs(1);
    for(int i=m;i>=0;i--)
    {
        //cout<<dp[1][i]<<endl;
        if(dp[1][i]>=0){
            cout<<i<<endl;
            break;
        }
    }
}

树状dp+回溯

D. Weight the Tree

time limit per test

2 seconds

memory limit per test

256 megabytes

input

standard input

output

standard output

You are given a tree of nn vertices numbered from 11 to nn. A tree is a connected undirected graph without cycles.

For each i=1,2,…,ni=1,2,…,n, let wiwi be the weight of the ii-th vertex. A vertex is called good if its weight is equal to the sum of the weights of all its neighbors.

Initially, the weights of all nodes are unassigned. Assign positive integer weights to each vertex of the tree, such that the number of good vertices in the tree is maximized. If there are multiple ways to do it, you have to find one that minimizes the sum of weights of all vertices in the tree.

Input

The first line contains one integer nn (2≤n≤2⋅1052≤n≤2⋅105) — the number of vertices in the tree.

Then, n−1n−1 lines follow. Each of them contains two integers uu and vv (1≤u,v≤n1≤u,v≤n) denoting an edge between vertices uu and vv. It is guaranteed that the edges form a tree.

Output

In the first line print two integers  — the maximum number of good vertices and the minimum possible sum of weights for that maximum.

In the second line print nn integers w1,w2,…,wnw1,w2,…,wn (1≤wi≤1091≤wi≤109)  — the corresponding weight assigned to each vertex. It can be proven that there exists an optimal solution satisfying these constraints.

If there are multiple optimal solutions, you may print any.

#include<iostream>
using namespace std;
const int maxn=2e5+5;
int dp[maxn][2];
int cost[maxn][2];
int head[maxn];
int du[maxn],ch[maxn];
struct node{
    int to,next;
}edge[maxn<<1];
int cnt=0;
void add_edge(int from,int to)
{
    edge[cnt].to=to;
    edge[cnt].next=head[from];
    head[from]=cnt++;
}
void dfs(int root,int pre)
{
    dp[root][0]=0,dp[root][1]=1;
    cost[root][0]=1,cost[root][1]=du[root];
    int sum1=0,sum2=0,cost1=0,cost2=0;
    for(int i=head[root];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to!=pre)
        {
            dfs(to,root);
            cost2+=cost[to][0];
            sum2+=dp[to][0];
            if(dp[to][1]>dp[to][0]) {
                sum1+=dp[to][1];
                cost1+=cost[to][1];
            }
            else if(dp[to][1]<dp[to][0])
            {
                sum1+=dp[to][0];
                cost1+=cost[to][0];
            }
            else {
                sum1+=dp[to][0];
                cost1+=min(cost[to][0],cost[to][1]);
            }
        }
    }
    dp[root][1]+=sum2;cost[root][1]+=cost2;
    dp[root][0]+=sum1;cost[root][0]+=cost1;
}
void f(int root,int flag,int pre)
{
    for(int i=head[root];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to!=pre)
        {
            if(flag==1)
            {
                ch[to]=0;
                f(to,0,root);
            }
            else
            {
                if(dp[to][0]==dp[to][1])
                {
                    if(cost[to][1]<cost[to][0])
                    {
                        ch[to]=1;
                        f(to,1,root);
                    }
                    else{
                        ch[to]=0;
                        f(to,0,root);
                    }
                }
                else
                {
                    if(dp[to][0]>dp[to][1])
                    {
                        ch[to]=0;
                        f(to,0,root);
                    }
                    else
                    {
                        ch[to]=1;
                        f(to,1,root);
                    }
                }
            }
        }
    }
}
int main()
{
    int n;cin>>n;
    memset(head,-1,sizeof(head));
    for(int i=1;i<n;i++)
    {
        int from,to;cin>>from>>to;
        add_edge(from,to);
        add_edge(to,from);
        du[from]++;du[to]++;
    }
    if(n==2)
    {
        cout<<"2 2\n1 1"<<endl;
        return 0;
    }
    dfs(1,0);
    if(dp[1][0]<dp[1][1])
    {
        cout<<dp[1][1]<<" "<<cost[1][1]<<endl;
        ch[1]=1;
    }
    else if(dp[1][0]>dp[1][1])
    {
        cout<<dp[1][0]<<" "<<cost[1][0]<<endl;
        ch[1]=0;
    }
    else
    {
        cout<<dp[1][0]<<" ";
        if(cost[1][0]<cost[1][1])
        {
            ch[1]=0;
            cout<<cost[1][0]<<endl;
        }
        else
        {
            ch[1]=1;
            cout<<cost[1][1]<<endl;
        }
    }
    f(1,ch[1],0);
    for(int i=1;i<=n;i++)
    {
        if(ch[i]==1) cout<<du[i]<<" ";
        else cout<<1<<" ";
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值