【动态规划】城市环路

106 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情

城市环路

题目背景

一座城市,往往会被人们划分为几个区域,例如住宅区、商业区、工业区等等。

B 市就被分为了以下的两个区域——城市中心和城市郊区。在这两个区域的中间是一条围绕 B 市的环路,环路之内便是 B 市中心。

题目描述

整个城市可以看做一个 nn 个点,nn 条边的单圈图(保证图连通),唯一的环便是绕城的环路。保证环上任意两点有且只有 22 条简单路径互通。图中的其它部分皆隶属城市郊区。

现在,有一位名叫 Jim 的同学想在 B 市开店,但是任意一条边的 22 个点不能同时开店,每个点都有一定的人流量,第 ii 个点的人流量是 pip_i,在该点开店的利润就等于 pi×kp_i×k,其中 kk 是一个常数。

Jim 想尽量多的赚取利润,请问他应该在哪些地方开店?

输入格式

第一行一个整数 nn,代表城市中点的个数。城市中的 nn 个点由 0n10 \sim n-1 编号。

第二行有 nn 个整数,第 (i+1)(i + 1) 个整数表示第 ii 个点的人流量 pip_i

接下来 nn 行,每行有两个整数 u,vu, v,代表存在一条连接 uuvv 的道路。

最后一行有一个实数,代表常数 kk

输出格式

输出一行一个实数代表答案,结果保留一位小数。

样例 #1

样例输入 #1

4
1 2 1 5
0 1
0 2
1 2
1 3
2

样例输出 #1

12.0

提示

数据规模与约定

  • 对于 20%20\% 的数据,保证 n100n \leq 100
  • 另有 20%20\% 的数据,保证环上的点不超过 20002000 个。
  • 对于 100%100\% 的数据,保证 1n1051 \leq n \leq 10^51pi1041 \leq p_i \leq 10^40u,v<n0 \leq u, v < n0k1040 \leq k \leq 10^4kk 的小数点后最多有 66 位数字。
#include <bits/stdc++.h>
using namespace std;
int n,u,v,ans=0;
double k;
int cnt=0;
int w[200005];
struct edge{
	int num;
	int next;
}p[200005];
int head[200005]={0};
void add(int a,int b){
	p[++cnt].num=b;
	p[cnt].next=head[a];
	head[a]=cnt;
}
bool flag=0;
bool vis[100005]={0};
int dp[100005][2]={0};
void dfs(int u,int pre,int no){
	dp[u][1]=w[u];
	dp[u][0]=0;
	for(int i=head[u];i;i=p[i].next){
		int v=p[i].num;
		if(v==pre){//跳过父节点
			continue;
		}
		if(v!=no){
			dfs(v,u,no);
			dp[u][1]+=dp[v][0];//转移状态,取 不取上个点的情况
			dp[u][0]+=max(dp[v][0],dp[v][1]);//取 上个点的两状态的最大值
		}
	}
}
void fc(int x,int pre){
	if(flag){//假如已经找到了环,继续就没有意义
		return ;
	}
	if(!vis[x]){//vis记录有没有走过
		vis[x]=1;
	}
	for(int i=head[x];i;i=p[i].next){
		int v=p[i].num;
		if(v==pre){
			continue;
		}
		if(!vis[v]){
			fc(v,x);
		}else{//x v在环上 
			flag=1;
            //相当于断开x v,分别以两点为根节点dp
			dfs(x,v,x);//以x为起点,记录x不会作为子节点
			ans=max(ans,dp[x][0]);
			dfs(v,x,v);//以v为起点,记录x不会作为子节点
			ans=max(ans,dp[v][0]);
            //最终答案相当于是不取u或者不取v的答案的最大值,因为显然两者不能同时取
			return;
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&w[i]);//每个点的价值
	} 
	for(int i=1;i<=n;i++){
		scanf("%d%d",&u,&v);
		u++;//处理为1-n
		v++;
		add(u,v);//连接u v,类似链式前向星
		add(v,u);
	}
	scanf("%lf",&k);
	fc(1,1);//从一号点开始找环,先默认它的祖先是自己
	double answer=(double)ans*k;//利润是k倍的人流量
	printf("%.1lf",answer);
	return 0;
}