基环树
一棵树多了一条边。。。就变成了基环树。
首先把这个环找出来,对于这个环,断掉一条边就变成树了,固定一个端点跑树形 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;
}
骑士
双倍经验???
坑点多,直接把上一道题的板子推翻了。
-
会有森林,dp 要覆盖所有连通块。
-
会有重边,不能用并查集只判点,需要建有向图找环。
我们在每一个连通图各找一次环,记录父亲,建外向图(都背向环),这样每次跳父亲一定会终止在环里。
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;
}