Luogu P1453 城市环路|基环树+DP
题目大意:
一个 \(n\) 个点,\(n\) 条边的单圈图(保证图连通) 注:即基环树。现在要在图上开店,但是任意一条边的 \(2\) 个点不能同时开店,每个点都有一定的人流量,第 \(i\) 个点的人流量是 \(p_i\),在该点开店的利润就等于 \(p_i\times k\),其中 \(k\) 是一个常数。
求最大利润。
\(1\le n\le 10^5\)
题目思路:
假如是一颗树,那么我们很容易想到Dp。现在多了一条边,成为了基环树,问题便显得有些棘手。
其实我们前文说基环树相当于环+树。那么我们先把环看作一个点,做树上DP。最后环上的每个点引出的边的答案都存在该点中。然后我们处理环。对于环上的每个点,有选与不选两种情况,有不同的权值。Dp即可。
#include<bits/stdc++.h>
#define N 100200
using namespace std;
int cc,to[N*2],net[N*2],fr[N*2],q[N*2],ri[N],f[N][2],g[N][2];
int n,u,v,t,qt,p[N];bool vis[N],r[N];double k;
void addedge(int u,int v)
{
cc++;to[cc]=v;net[cc]=fr[u];fr[u]=cc;
cc++;to[cc]=u;net[cc]=fr[v];fr[v]=cc;
}
bool findr(int x)
{
vis[x]=true;
q[++t]=x;
for (int i=fr[x];i;i=net[i])
{
if (vis[to[i]]&&q[t-1]!=to[i])
{
int tt=t;
while (q[tt]!=to[i])
{
ri[++qt]=q[tt];
r[q[tt]]=true;
tt--;
}
ri[++qt]=q[tt];
r[to[i]]=true;
t--;vis[x]=false;return true;
}
if (!vis[to[i]])
if (findr(to[i]))
{
vis[x]=false;
return true;
}
}
vis[x]=false;t--;
return false;
}//找环
void dfs(int x)
{
vis[x]=true;f[x][1]=p[x];
for (int i=fr[x];i;i=net[i])
{
if (vis[to[i]]||r[to[i]]) continue;
dfs(to[i]);
f[x][0]+=max(f[to[i]][1],f[to[i]][0]);
f[x][1]+=f[to[i]][0];
}
vis[x]=false;
}//树DP
int main()
{
scanf("%d",&n);
for (int i=0;i<n;i++)
{
scanf("%d",&p[i]);
}
cc=1;
for (int i=1;i<=n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
cin>>k;
findr(0);
for (int i=0;i<n;i++)
{
if (r[i])
dfs(i);
}
//以下为环DP
g[1][1]=f[ri[1]][1];
int ans=0;
for (int i=2;i<qt;i++)
{
g[i][0]=max(g[i-1][0],g[i-1][1])+f[ri[i]][0];
g[i][1]=max(g[i-1][0],max(g[i-2][1],g[i-2][0]))+f[ri[i]][1];
ans=max(ans,max(g[i][0],g[i][1]));
}
g[1][0]=f[ri[1]][0];g[1][1]=0;
for (int i=2;i<=qt;i++)
{
g[i][0]=max(g[i-1][0],g[i-1][1])+f[ri[i]][0];
g[i][1]=max(g[i-1][0],max(g[i-2][1],g[i-2][0]))+f[ri[i]][1];
ans=max(ans,max(g[i][0],g[i][1]));
}
printf("%.1f\n",k*ans);
return 0;
}