基环树
基环树
一棵树多了一条边。。。就变成了基环树。
首先把这个环找出来,对于这个环,断掉一条边就变成树了,固定一个端点跑树形 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;
}
旅行
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;
}