10.21 NOTE

P4376 [USACO18OPEN] Milking Order G

题目传送门

tag:拓扑排序 二分答案

思路

题目显然要最大化一个 \(X\) 使得对于前 \(X\) 个约束,满足整张图联通并且是一个 \(DEG\),有一个很显然的结论是:如果一张图有一个环,那么无论加多少边还是有个环;如果一张图没有环,那么无论删多少边还是没有环。

也就是说,这道题的答案是单调的,对于一个 \(X\),若合法,则比它小的数都是合法的;若不合法,则比它大是数都是不合法的。

因此考虑二分答案

那么现在的问题来到了怎么判这张图是否有环,有一个关于拓扑排序的小技巧:若一张图有环,那么在删边删到一定程度时这张图一定没有入度为 \(0\) 的结点了,因此在进行一遍拓扑排序之后判断是否有结点的入度仍然大于 \(0\) 即可。

根据题意,要输出拓扑序,那么直接对最终答案拓扑排序即可。

总结

拓扑排序判环

一遍拓扑排序之后判断是否还有节点的入度不为 \(0\)

Code

#include<bits/stdc++.h>
#define Iseri namespace
#define Nina std
#define Kawaragi int
#define Momoka main
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define ll long long
#define ull unsigned long long
#define endl "\n"
#define pii pair<ll,ll>
const int maxn=100005;
const int inf=0x3f3f3f3f;

using Iseri Nina;

inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

//===========================================================

ll n,m,rd[maxn],u;
vector<ll>c[maxn],v[maxn];

inline void init(ll x){
	for(ll i=1;i<=n;i++)rd[i]=0;
	for(ll i=1;i<=n;i++)v[i].clear();
	for(ll i=1;i<=x;i++){
		for(ll j=0;j<c[i].size()-1;j++){
			v[c[i][j]].push_back(c[i][j+1]);
			rd[c[i][j+1]]++;
		}
	}
	return;
}

inline bool check(ll x){
	init(x);
	queue<ll>q;
	for(ll i=1;i<=n;i++){
		if(rd[i]==0)q.push(i);
	}

	while(!q.empty()){
		ll a=q.front();
		q.pop();

		for(auto i:v[a]){
			rd[i]--;
			if(rd[i]==0)q.push(i);
		}
	}

	bool flag=false;
	for(ll i=1;i<=n;i++)if(rd[i]){flag=1;break;}
	if(flag)return false;
	else return true;
}

inline void prt(ll x){
	init(x);
	priority_queue<ll,vector<ll>,greater<ll> >q;
	for(ll i=1;i<=n;i++)if(rd[i]==0)q.push(i);

	while(!q.empty()){
		ll a=q.top();
		printf("%lld ",a);
		q.pop();

		for(auto i:v[a]){
			rd[i]--;
			if(rd[i]==0)q.push(i);
		}
	}
	return;
}

Kawaragi Momoka(){
	n=read(),m=read();
	for(ll i=1;i<=m;i++){
		ll s=read();
		for(ll j=1;j<=s;j++){
			u=read();
			c[i].push_back(u);
		}
	}

	ll l=1,r=m;
	while(l<=r){
		ll mid=(l+r)>>1;
		if(check(mid))l=mid+1;
		else r=mid-1;
	}

	prt(r);
	return 0;
}

P2607 [ZJOI2008] 骑士

题目传送门

思路

树形 DP,但是显然这是一个基环树上的 DP,这道题比较板,就是没有上司的舞会的形式。

总结

基环树上的 DP

先找到环,随便断开一条边,以这条边两个端点为根分别跑一次树形 DP,注意特判这条边即可。

注意:在建边的时候记得建成外向基环树,不然会因为递归深度而 MLE。

Code

#include<bits/stdc++.h>
#define Iseri namespace
#define Nina std
#define Kawaragi int
#define Momoka main
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define ll long long
#define ull unsigned long long
#define endl "\n"
#define pii pair<ll,ll>
const int maxn=1000005;
const int inf=0x3f3f3f3f;

using Iseri Nina;

inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

//===========================================================

ll n,w[maxn],x,vis[maxn],rt,fa[maxn],f[maxn][2],ans;
vector<ll>v[maxn];

inline void dfs(ll u){
	vis[u]=1;
	f[u][0]=0,f[u][1]=w[u];

	for(auto i:v[u]){
		if(i!=rt){
			dfs(i);
			f[u][0]+=max(f[i][0],f[i][1]);
			f[u][1]+=f[i][0];
		}else f[i][1]=-inf;
	}
 	return;
}

inline void find(ll u){
	vis[u]=1;
	rt=u;
	while(!vis[fa[rt]]){
		rt=fa[rt];
		vis[rt]=1;
	}

	dfs(rt);
	ll tmp=max(f[rt][0],f[rt][1]);
	vis[rt]=1;
	rt=fa[rt];
	dfs(rt);
	ans+=max(tmp,max(f[rt][0],f[rt][1]));
	return;
}

Kawaragi Momoka(){
	n=read();
	for(ll i=1;i<=n;i++){
		w[i]=read(),x=read();
		v[i].push_back(x);
		fa[i]=x;
	}

	for(ll i=1;i<=n;i++)if(!vis[i])find(i);

	printf("%lld",ans);
	return 0;
}
posted @ 2025-11-13 23:56  Amiyawasdonkey  阅读(0)  评论(0)    收藏  举报