题解:P5157 [USACO18DEC] The Cow Gathering P

挺有趣的一道题。

思路

先不考虑那 \(m\) 条限制来看一下这道题,容易发现,如果 \(n\) 个点构成的不是一颗树,则一定是不合法的,因为无论你怎么选择最终每个连通块至少剩下一个点无法离开。

那么剩下的只有 \(n\) 个点构成一颗树的情况,容易发现,每次离开的人一定是度数为 \(1\) 的点,否则它离开后会使得连通块大于一个,也就是说必须保证操作时连通块不会变多

接下来考虑一下限制,先假设只有一条限制,我们默认以一为跟,若 \(x\)\(y\) 先离开且为 \(y\) 的祖先,则一定不会是最后一个的点如下:

否则就是 \(x\) 的子树。

对于多个限制,可能会存在相互约束导致无解情况,我们可以这样做:

刚开始的 \(n-1\) 条边视为两条有向边,然后把 \(m\) 个限制变成有向边,然后每次选择一个入度为一的点,然后删除它连出去的边,如此操作,若最终所有点都被删除,则一定不是全部无解。

不是全部无解的话,这 \(m\) 个限制相当于互相独立,就对每个操作看一下,最后树上差分即可。

code

#include<bits/stdc++.h>
using namespace std;
namespace IO
{
	template<typename T>
	void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
	template<typename T,typename... Args>
	void read(T &_x,Args&...others){Read(_x);Read(others...);}
	const int BUF=20000000;char buf[BUF],top,stk[32];int plen;
	#define pc(x) buf[plen++]=x
	#define flush(); fwrite(buf,1,plen,stdout),plen=0;
	template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++top]=48+x%10;while(top) pc(stk[top--]);}
}
using namespace IO;
const int N = 1e5+10;
int v[N],n,m,x,y,f[N],lg[N],in[N],dep[N],head[N],head1[N],cnt,cnt1,fa[N][20],sum,ans[N],o;
queue<int>p;
struct w
{
	int to,nxt;
}b[N<<1],c[N<<2];
int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]); 
}
inline void add(int x,int y)
{
	b[++cnt].nxt = head[x];
	b[cnt].to = y;
	head[x] = cnt;
}
inline void add1(int x,int y)
{
	c[++cnt1].nxt = head1[x];
	c[cnt1].to = y;
	head1[x] = cnt1;
}
void dfs(int x,int y)
{
	dep[x] = dep[y]+1,fa[x][0] = y;
	for(int i = 1;i <= lg[dep[x]];i++) fa[x][i] = fa[fa[x][i-1]][i-1];
	for(int i = head[x];i;i = b[i].nxt)
		if(b[i].to != y)
			dfs(b[i].to,x);
}
void dfs1(int x,int y)
{
	ans[x] += ans[y];
	for(int i = head[x];i;i = b[i].nxt)
		if(b[i].to != y)
			dfs1(b[i].to,x);
}
inline int go_up(int x,int y)
{
	for(int i = lg[dep[x]];i >= 0;i--)
		if(((1<<i)&y)) x = fa[x][i];
	return x;
}
inline void bfs()
{
	while(!p.empty())
	{
		x = p.front(); p.pop();
		for(int i = head1[x];i;i = c[i].nxt) 
		{
			in[c[i].to]--;
			if(in[c[i].to] == 1) p.push(c[i].to),o++;
		}
	}
} 
signed main()
{
	read(n),read(m);
	for(int i = 1;i < n;i++) read(x),read(y),add(x,y),add(y,x),add1(x,y),add1(y,x),in[x]++,in[y]++,f[find(x)] = find(y);
	for(int i = 1;i <= n;i++)
		if(find(i) == i) sum++;
	if(sum > 1)//多个联通快一定无解 
	{
		for(int i = 1;i <= n;i++) print(0),pc('\n');
		flush();
		return 0;
	} 
	for(int i = 2;i <= n;i++) lg[i] = lg[i/2]+1;
	cnt= 0,dfs(1,0);
	for(int i = 1;i <= m;i++) 
	{
		read(x),read(y);//x比y先走 
		add1(x,y),in[y]++;
		if(x == y) ans[1]++; //相等显然不合法 
		else if(dep[x] < dep[y] && fa[go_up(y,dep[y]-dep[x]-1)][0] == x) ans[1]++,ans[go_up(y,dep[y]-dep[x]-1)]--;//x为y祖先 
		else ans[x]++;
	}
	for(int i = 1;i <= n;i++)
		if(in[i] == 1) p.push(i),o++;
	bfs();//判是否无解 
	if(o != n)
	{
		for(int i = 1;i <= n;i++) print(0),pc('\n');
		flush();
		return 0;
	}
	dfs1(1,0);
	for(int i = 1;i <= n;i++) print((ans[i] == 0)),pc('\n');
	flush();
	return 0;
}
/*
首先发现原图必须是一颗树,否则一定全部无解 
在没m条限制的时候,先走的一定是叶子节点,然后会有新的叶子节点出现
m条限制通过观察可得,以任意一个点为根,若x比y先选,则以y为根时x为子树不可行
不过多条限制可能会互相约束以达到无解,可以跑拓扑判是否有解 
直接树上差分即可,单log,来自lca 
*/
posted @ 2025-02-17 11:17  kkxacj  阅读(10)  评论(0)    收藏  举报