【P1453】城市环路 树形Dp+拆环

题目背景

一座城市,往往会被人们划分为几个区域,例如住宅区、商业区、工业区等等。B市就被分为了以下的两个区域——城市中心和城市郊区。在着这两个区域的中间是一条围绕B市的环路,环路之内便是B市中心。

题目描述

整个城市可以看做一个N个点,N条边的单圈图(保证图连通),唯一的环便是绕城的环路。保证环上任意两点有且只有2条路径互通。图中的其它部分皆隶属城市郊区。
现在,有一位名叫Jim的同学想在B市开店,但是任意一条边的2个点不能同时开店,每个点都有一定的人流量Pi,在该点开店的利润就等于该店的人流量Pi×K(K≤10000),K的值将给出。
Jim想尽量多的赚取利润,请问他应该在哪些地方开店?

输入输出格式

输入格式:
第一行一个整数N 代表城市中点的个数。城市中的N个点由0~N-1编号。
第二行N个正整数,表示每个点的人流量Pi(Pi≤10000)。
下面N行,每行2个整数A,B,表示A,B建有一条双向路。
最后一行一个实数K。
输出格式:
一个实数M,(保留1位小数),代表开店的最大利润。

输入输出样例

输入样例#1:
4
1 2 1 5
0 1
0 2
1 2
1 3
2
输出样例#1:
12.0

题解

我们发现直接Dp不行,因为存在环,考虑拆环,我们对任意一个点进行dfs,当到达的点已经到达过时,就说明这条边位于环中,由于题目只存在一个环,我们把这条边和它的反向边标记,分别进行dp就可以了。
Dp的话就是树形Dp f[i][0] f [ i ] [ 0 ] 表示该点不选时的最大值,可以从 f[i][0],f[i][1] f [ i ] [ 0 ] , f [ i ] [ 1 ] 转移过来, f[i][1] f [ i ] [ 1 ] 表示改点选时的最大值,只能从 f[j][0],jE f [ j ] [ 0 ] , j ∈ E 转移过来。、

代码

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
const int MAXN=1e6;
using namespace std;
struct Node
{
    int next,to;
}edge[MAXN];
int head[MAXN],cnt,f[MAXN][2],n,x,y,z,flag,vis[MAXN],begin_1,begin_2,Delete,val[MAXN];
double k;
inline void Add_Edge(int u,int v)
{
    edge[cnt]=(Node){head[u],v};
    head[u]=cnt++;
}
void dfs(int x,int fa)//拆环
{
    if(flag) return ;
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to; 
        if(vis[v]==1&&v!=fa)
        {
            begin_1=x;
            begin_2=v;
            flag=1; 
            Delete=i;
            //cout<<Delete;
            return ;
        }
        if(vis[v]==0&&v!=fa)
        {
            vis[v]=1;
            dfs(v,x);
        }
    }
}
void dp(int x,int fa)//树形Dp
{
    f[x][0]=0;f[x][1]=val[x];
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v!=fa&&i!=Delete&&(i^1)!=Delete)
        {
            dp(v,x);
            f[x][0]+=max(f[v][1],f[v][0]);
            f[x][1]+=f[v][0];   
        }
    }
}
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        Add_Edge(x+1,y+1);
        Add_Edge(y+1,x+1); 
    }
    scanf("%lf",&k);
    dfs(1,0);
    dp(begin_1,0);
    int ans=0;
    ans=max(ans,f[begin_1][0]);
    dp(begin_2,0);
    printf("%.1f",(double)k*max(ans,f[begin_2][0]));
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值