题解: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;
}

附带数据生成器

posted @ 2024-06-11 19:43  lxyt-415x  阅读(28)  评论(0编辑  收藏  举报