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;
}
posted @ 2021-04-14 22:17  fmj_123  阅读(39)  评论(0编辑  收藏  举报