题解:P5786 [CQOI2008] 传感器网络
题意
从一个 \(n\) 个结点的有向无环图里选出 \(n-1\) 条边,构成一棵树,且 除根节点以外的 点的儿子个数的最大值最小。
输出满足题意的节点的父亲,要求字典序最小。
思路
我们肯定要先把最小值求出来。
很容易看出是 拆点 + 二分答案求解,这里要注意的是拆完的两个点是不用连起来的,将做为儿子的点与源点连边,权值为 \(1\) 来限制;作为父亲的点依据二分的值与汇点连边,剩下的边输入边连就行。
然后就是 \(O(n^2)\) 的枚举来确定答案 蒟蒻不会二分验证 。
从 \(1\) 开始遍历每一个点,先将之前连向可能父节点的边清空,再从小到大附上值并跑一遍最大流,如果可以就证明有解
注意
这道题要保证字典序最小,而控制中心用 \(n\) 表示,所以能连向控制中心的也可以连其他的
代码
剩下要注意的就看代码吧,写了注释
我好像是写的最麻烦的那个
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10,inf=1e9;
int n,m,s,t,tot=1,ans,_,fa[N];
int head[N],nxt[N],to[N],val[N];
int dep[N],cur[N],g[N],gg[N],fl[N];
//_,g,gg 分别用于暂存 tot,val,head方便回溯
vector<int>v[N],mp[N];
//Dinic 板子
inline void add(int u,int v,int w)
{
nxt[++tot]=head[u];
head[u]=tot;
to[tot]=v;
val[tot]=w;
return ;
}
inline void insert(int u,int v,int w)
{
add(u,v,w);
add(v,u,0);
return ;
}
inline bool bfs()
{
for(int i=1;i<=t;i++)
dep[i]=0;
queue<int>q;
q.push(s);
dep[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
cur[x]=head[x];
for(int i=head[x],y;i;i=nxt[i])
{
y=to[i];
if(val[i]&&!dep[y])
{
dep[y]=dep[x]+1;
q.push(y);
}
}
}
return dep[t];
}
inline int dfs(int x,int flow)
{
if(x==t)
return flow;
int res=flow;
for(int i=cur[x],y,k;i&&res;i=nxt[i])
{
y=to[i];
cur[x]=i;
if(val[i]&&dep[y]==dep[x]+1)
{
k=dfs(y,min(res,val[i]));
val[i]-=k;
val[i^1]+=k;
res-=k;
}
}
return flow-res;
}
//暂存
inline void init()
{
for(int i=1;i<=tot;i++)
g[i]=val[i];
for(int i=1;i<=t;i++)
gg[i]=head[i];
_=tot;
return ;
}
//回溯
inline void tini()
{
for(int i=1;i<=tot;i++)
val[i]=g[i];
for(int i=1;i<=t;i++)
head[i]=gg[i];
tot=_;
return ;
}
int main()
{
cin>>n;
s=2*n+1;
t=2*n+2;
char ch;
for(int i=1;i<=n;i++)
{
insert(s,i,1);
cin>>ch;
if(ch=='Y')
{
insert(i,t,1); //这里向汇点连边的时候也要存进 vector 里!!!
v[i].push_back(t);
mp[i].push_back(tot-1);
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>ch;
if(ch=='Y')
{
insert(i,n+j,1);
v[i].push_back(j);
mp[i].push_back(tot-1);
}
}
for(int i=1;i<=n;i++) //写的好唐,其实不用这样的
sort(v[i].begin(),v[i].end());
init();
int l=0,r=1000,mid,res;
while(l<=r) // 二分求最小值
{
mid=l+r>>1;
for(int i=1;i<=n;i++)
insert(n+i,t,mid);
ans=0;
while(bfs())
ans+=dfs(s,inf);
tini();
if(ans==n)
{
r=mid-1;
res=mid;
}
else
l=mid+1;
}
for(int i=1;i<=n;i++)
insert(n+i,t,res);
for(int i=1;i<=n;i++)
{
for(auto it : mp[i])
val[it]=0; //清空所有向可能父亲的连边
init();
for(auto it : v[i])
{
int j=it==t?n+2:it;
insert(i,n+j,1);
ans=0;
while(bfs())
ans+=dfs(s,inf);
tini();
if(ans==n) //确定答案后把边加进去
{
fa[i]=j;
insert(i,n+j,1);
init();
break;
}
}
}
for(int i=1;i<=n;i++)
{
if(fa[i]==n+2) // 写的唐了,也不用这么写的
fa[i]--;
printf("%d ",fa[i]-1);
}
return 0;
}