题解 P1453 【城市环路】
题目链接:城市环路。
关于题目:一道比较显然的 基环树 上的 dp 题。
应用算法:树形 dp
写在前面:
$\qquad\!\!f_{u,1}\gets f_{u,1}+f_{v,0}$
- 3、环形 dp:
$\qquad\!\!$_没有接触过 环形 dp 的同学可以左转先看一下这两道题:[P6064 [USACO05JAN]Naptime G](https://www.luogu.com.cn/problem/P6064) 和 [SP283 NAPTIME - Naptime](https://www.luogu.com.cn/problem/SP283)。_
$\qquad\!\!$由于此时每棵树内部节点的关系均已满足题目的限定,剩下的就是环上的各个节点之间的关系,那么我们只需要对每棵树的根节点进行一次 环形 dp 即可得出答案。
$\qquad\!\!$设 $c_i$ 为环上第 $i$ 个点的编号,$tot$ 为环上的节点总数。
$\qquad\!\!$那么此时 $f_{c_i,0}$ 代表以 $c_i$ 为根的树不选 $c_i$ 最大贡献; $f_{c_i,1}$ 代表以 $c_i$ 为根的树选 $c_i$ 最大贡献。
$\qquad\!\!$设 $g_{i,0}$ 表示点 $c_i$ 不选,前 $i$ 个节点的最大贡献,设 $g_{i,1}$ 表示点 $c_i$ 选,前 $i$ 个节点的最大贡献。
$\qquad\!\!$转移方程:
$\qquad\!\!g_{i,0}\gets \max(g_{i-1,0},g_{i-1,1})+f_{c_i,0}$
$\qquad\!\!g_{i,1}\gets g_{i-1,0}+f_{c_i,1}$
$\qquad\!\!$由于是进行 环形 dp,为了枚举全面所有的情况,我们需要进行两次 dp 过程:
$\qquad\!\!$①、规定 $c_1$ 节点一定不选:
$\qquad\!\!\qquad\!\!$初始化 $g$ 中的每一个元素为 $-inf$;$g_{1,0}=f_{c_1,0}$。
$\qquad\!\!\qquad\!\!$进行 dp,统计答案 $ans=\max(g_{tot,0},g_{tot,1})$。
$\qquad\!\!$②、规定 $c_1$ 节点一定选:
$\qquad\!\!\qquad\!\!$初始化 $g$ 中的每一个元素为 $-inf$;$g_{1,1}=f_{c_1,1}$。
$\qquad\!\!\qquad\!\!$进行 dp,统计答案 $ans=\max(ans,g_{tot,0})$。
-
三、复杂度分析:
-
时间复杂度:无论是在找环还是在两次 dp 的过程中,每个点最多被经过一次,所以时间复杂度为
O(n) 。 -
空间复杂度:由几个数组的定义可知,每个数组所用的空间均为
O(n) 的数量级,所以总空间复杂度为O(n) 。
-
-
四、最后,上代码(码风不喜勿喷):
//P1453 城市环路
#include<bits/stdc++.h>
#define in inline
#define re register
#define N 100010
using namespace std;
int n;
int cnt,head[N];
struct edge
{
int to,nxt;
};
edge e[N<<1];
bool b[N];
int tot;
int rd[N],c[N];
int f[N][2],g[N][2];
int ans;
double k;
in int qread()
//快读
{
int x=0,y=1;
int ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
{
y=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*y;
}
in void mr(re int u,re int v)
//建边
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
return ;
}
in void dfs0(re int u,re int fa)
//找环
{
for(re int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
//父节点或已在环上就不算
if(v==fa||b[v])
{
continue;
}
//找到一个点就加入环
if(rd[v]==2)
{
b[v]=1;
c[++tot]=v;
//递归
dfs0(v,u);
//加入一个点后就跳出循环
break;
}
}
return ;
}
in void tppx()
//拓扑排序
{
queue<int> q;
for(re int i=1;i<=n;i++)
{
if(rd[i]==1)
{
q.push(i);
}
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(re int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
rd[v]--;
if(rd[v]==1)
{
q.push(v);
}
}
}
for(re int i=1;i<=n;i++)
{
//找到第一个在环上的点加入环并进行 dfs
if(rd[i]==2)
{
b[i]=1;
c[++tot]=i;
dfs0(i,0);
//加入一个点后就跳出循环
break;
}
}
return ;
}
in void dfs(re int u,re int fa)
//树形 dp
{
for(re int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa||b[v])
//父节点或已在环上就不算
{
continue;
}
dfs(v,u);
//转移
f[u][0]+=max(f[v][1],f[v][0]);
f[u][1]+=f[v][0];
}
return ;
}
int main()
{
//读入
n=qread();
for(re int i=1;i<=n;i++)
{
f[i][1]=qread();
}
for(re int i=1;i<=n;i++)
{
int u=qread()+1;
int v=qread()+1;
//统计入度
rd[u]++;
rd[v]++;
//建边
mr(u,v);
mr(v,u);
}
//拓扑排序
tppx();
//对于每棵树 树形 dp
for(re int i=1;i<=tot;i++)
{
dfs(c[i],0);
}
//环形 dp
memset(g,-0x3f,sizeof(g));
g[1][0]=f[c[1]][0];
for(re int i=2;i<=tot;i++)
{
g[i][1]=g[i-1][0]+f[c[i]][1];
g[i][0]=max(g[i-1][0],g[i-1][1])+f[c[i]][0];
}
ans=max(g[tot][0],g[tot][1]);
memset(g,-0x3f,sizeof(g));
g[1][1]=f[c[1]][1];
for(re int i=2;i<=tot;i++)
{
g[i][1]=g[i-1][0]+f[c[i]][1];
g[i][0]=max(g[i-1][0],g[i-1][1])+f[c[i]][0];
}
ans=max(ans,g[tot][0]);
//读入 k
scanf("%lf",&k);
//输出答案
printf("%.1lf\n",ans*k);
return 0;
}