基环树

基环树

一棵树多了一条边。。。就变成了基环树

首先把这个环找出来,对于这个环,断掉一条边就变成树了,固定一个端点跑树形 dp。

城市环路

很板子,没坑点。找环可以用并查集,不用知道具体有哪些点。

我们只需要找出其中一条边的两个端点作为断点就好了。

for(int i=1;i<=n;i++)
{
	int x,y; scanf("%d%d",&x,&y); x++,y++;
	if(find(x)!=find(y)) add(x,y),add(y,x),fa[find(x)]=y;
	else r1=x,r2=y;
}

每一个点可以选或不选,注意我们强制锁定断点时只能锁定它不选。

否则因为两个断点间没有连边,无法判断会不会两个断点都选的情况。

dp 是裸的树上 dp。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
#define LL long long
int n,p[N],r1,r2;
long double k,ans;
int head[N],tot;
struct E {int u,v;} e[N<<1];
void add(int u,int v) {e[++tot]={head[u],v}; head[u]=tot;}
int fa[N];
LL f[N][2];
int find(int x)
{
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
void dp(int u,int fa)
{
	f[u][0]=0,f[u][1]=p[u];
	for(int i=head[u];i;i=e[i].u)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dp(v,u);
		f[u][0]+=max(f[v][0],f[v][1]);
		f[u][1]+=f[v][0];
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&p[i]),fa[i]=i;
	for(int i=1;i<=n;i++)
	{
		int x,y; scanf("%d%d",&x,&y); x++,y++;
		if(find(x)!=find(y)) add(x,y),add(y,x),fa[find(x)]=y;
		else r1=x,r2=y;
	}
	scanf("%Lf",&k);
	dp(r1,0); ans=max(k*f[r1][0],ans);//只能限制不要,如果强制要的话会和 r2 冲突
	dp(r2,0); ans=max(k*f[r2][0],ans);
	printf("%.1Lf\n",ans);
	return 0;
}

骑士

双倍经验???

坑点多,直接把上一道题的板子推翻了。

  1. 会有森林,dp 要覆盖所有连通块。

  2. 会有重边,不能用并查集只判点,需要建有向图找环。

我们在每一个连通图各找一次环,记录父亲,建外向图(都背向环),这样每次跳父亲一定会终止在环里。

void work(int u)
{
	while(!vs[u]) vs[u]=1,u=fa[u];//一定会跳到环上一个点,手摸一下 
	dfs(u,u);
	LL res=f[u][0];
	dfs(fa[u],fa[u]);
	ans+=max(res,f[fa[u]][0]);
}

然后 dp 连通块,dp 过程中记录那个开始的父亲,因为有向图,只需要不等于这个父亲就一直遍历。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
#define LL long long 
int n,fa[N],a[N];
LL f[N][2],ans;
int head[N],tot;
struct E {int u,v;} e[N<<1];
void add(int u,int v) {e[++tot]={head[u],v}; head[u]=tot;}
bool vs[N];
void dfs(int u,int x)//有向图,不用判父亲,判环接头处 
{
	vs[u]=1; f[u][0]=0,f[u][1]=a[u];
	for(int i=head[u];i;i=e[i].u)
	{
		int v=e[i].v;
		if(v!=x)
		{
			dfs(v,x);
			f[u][0]+=max(f[v][0],f[v][1]);
			f[u][1]+=f[v][0];			
		}
	}
}
void work(int u)
{
	while(!vs[u]) vs[u]=1,u=fa[u];//一定会跳到环上一个点,手摸一下 
	dfs(u,u);
	LL res=f[u][0];
	dfs(fa[u],fa[u]);
	ans+=max(res,f[fa[u]][0]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%d",&a[i],&fa[i]);//会有重边,不能用并查集判点 
		add(fa[i],i);
	}
	for(int i=1;i<=n;i++) if(!vs[i]) work(i);
	printf("%lld\n",ans);
	return 0;
}

旅行

vector 好用呢。

vector 存图,可以直接排序,dfs 时就不需要多余的判断了。

仍然是特殊处理有环的情况,根据数据范围,我们可以暴力拆边,复杂度 \(O(n^2)\)

暴力枚举每一条边,dfs 后判连不连通(点数等于 \(n\)),很暴力。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 5005;
int n,m,u[N],v[N],r1,r2;
vector<int> e[N],ans,tmp;

bool cmp(int u,int v) {return (r1==u&&r2==v)||(r1==v&&r2==u);}
bool jd()
{
	for(int i=0;i<n;i++)
	{
		if(ans[i]>tmp[i]) return 1;
		else if(ans[i]<tmp[i]) return 0;
	}
	return 0;
}
int vs[N];
void dfs(int u,int fa,int k) 
{
	vs[u]=k;
	tmp.push_back(u);
	for(int v:e[u])
	{
		if(vs[v]==k||v==fa||cmp(u,v)) continue;
		dfs(v,u,k);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&u[i],&v[i]);
		e[u[i]].push_back(v[i]);
		e[v[i]].push_back(u[i]);
	}
	for(int i=1;i<=n;i++) sort(e[i].begin(),e[i].end());
	if(m==n-1) dfs(1,0,1),ans=tmp;
	else
	{
		for(int i=1;i<=m;i++)
		{
			r1=u[i],r2=v[i];
			tmp.clear();
			dfs(1,0,i);
			if(tmp.size()==n&&(ans.empty()||jd())) ans=tmp;
		}
	}
	for(int i:ans) printf("%d ",i);
	return 0;
}

咕咕咕。。。

posted @ 2024-07-22 11:08  ppllxx_9G  阅读(13)  评论(0编辑  收藏  举报