pyyzDay16

Kruskal重构树(最小瓶颈路)

每次连两个联通块

开一个新点将两个点的fa连起来

则u->v最小瓶颈路为lca(u,v)的边权

Teleporter

先找简单例子贪心

发现若原图1号点有自环,则直接从叶子节点贪心,每K层向1号点连边

否则,先连自环,将1号点向外连的边删了,同上贪心

[NOI2018] 归程

按照海拔Kruskal重构树(最大生成树)

倍增查找第一个积水>p的x的祖先

预处理最短路/树形DP(重构树上dis最小值)

答案即为祖先的dp值

强连通分量(有向图)

缩点

https://www.luogu.com.cn/article/vql821kl

[USACO03FALL / HAOI2006] 受欢迎的牛 G

缩点

若出度为0的点有一个,这个点权即为答案

否则答案为0

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;
}
inline int read(){
	int 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;
}
void out(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10)putchar(x+'0');
	else out(x/10),putchar(x%10+'0');
}
int low[500005],dfn[500005],belong[500005],st[500005],top,col,cnt,vis[500005];
vector<int> tu[500005],to[500005];
int sum[500005],a[500005],cd[500005];
queue<int> q;
void dfs(int x){
	low[x]=dfn[x]=++cnt;
	vis[x]=1;
	st[++top]=x;
	for(auto ed:tu[x]){
		if(dfn[ed]==0){
			dfs(ed);
			low[x]=min(low[ed],low[x]);
		}
		else if(vis[ed]==1){
			low[x]=min(low[x],dfn[ed]);
		}
	}
	if(dfn[x]==low[x]){
		int y=-1;
		col++;
		while(1){
			y=st[top--];
			vis[y]=0;
			belong[y]=col;
			sum[col]+=a[y];
			if(x==y) break;
		}
	}
} 
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=1;
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		tu[u].push_back(v);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			dfs(i);
		}
	}
	for(int i=1;i<=n;i++){
		for(auto ed:tu[i]){
			int bi=belong[i];
			int bed=belong[ed];
			if(bi!=bed){
				to[bi].push_back(bed);
				cd[bi]++;
			}
		}
	}
	int cc=0;
	for(int i=1;i<=col;i++){
		if(cd[i]==0){
			if(cc){
				cout<<0<<'\n';
				return 0;
			}
			cc=sum[i];
		}
	}
	cout<<cc<<'\n';
	return 0;
}

[ZJOI2007] 最大半连通子图

缩点后找最长链

Catowice City

缩点

任意一个出度为0的点选人

其余点选猫

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;
}
inline int read(){
	int 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;
}
void out(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10)putchar(x+'0');
	else out(x/10),putchar(x%10+'0');
}
int low[1000005],dfn[1000005],belong[1000005],st[1000005],top,col,cnt,vis[1000005];
vector<int> tu[1000005],to[1000005];
int sum[1000005],a[1000005],cd[1000005];
void dfs(int x){
	low[x]=dfn[x]=++cnt;
	vis[x]=1;
	st[++top]=x;
	for(auto ed:tu[x]){
		if(dfn[ed]==0){
			dfs(ed);
			low[x]=min(low[ed],low[x]);
		}
		else if(vis[ed]==1){
			low[x]=min(low[x],dfn[ed]);
		}
	}
	if(dfn[x]==low[x]){
		int y=-1;
		col++;
		while(1){
			y=st[top--];
			vis[y]=0;
			belong[y]=col;
			sum[col]+=a[y];
			if(x==y) break;
		}
	}
} 
void solve(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++){
		tu[i].clear();
		to[i].clear();
	}
	top=col=cnt=0;
	for(int i=1;i<=n;i++){
		dfn[i]=low[i]=cd[i]=vis[i]=sum[i]=belong[i]=0;
	}
	for(int i=1;i<=n;i++) a[i]=1;
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		if(u==v) continue;
		tu[u].push_back(v);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			dfs(i);
		}
	}
	if(col==1){
		cout<<"No"<<'\n';
		return ;
	}
	for(int i=1;i<=n;i++){
		for(auto ed:tu[i]){
			int bi=belong[i];
			int bed=belong[ed];
			if(bi!=bed){
				to[bi].push_back(bed);
				cd[bi]++;
			}
		}
	}
	int an=0,cc=0;
	for(int i=1;i<=col;i++){
		if(cd[i]==0){
			an+=sum[i];
			cc=i;
			break;
		}
	}
	if(cc==0){
		cout<<"No"<<'\n';
		return ;
	}
	cout<<"Yes"<<'\n';
	cout<<an<<' '<<n-an<<'\n';
	for(int i=1;i<=n;i++){
		if(belong[i]==cc){
			cout<<i<<' ';
		}
	}
	cout<<'\n';
	for(int i=1;i<=n;i++){
		if(belong[i]!=cc){
			cout<<i<<' ';
		}
	}
	cout<<'\n';
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int T=read();
	while(T--){
		solve();
	}
	return 0;
}

[ARC092F] Two Faced Edges

大分讨

n^2暴力看每个点能到达哪些点

记录经过的边即可

【UR #9】App 管理器

题目保证了一定有解,那么整个图可以看成是一个强连通图加上若干条边,也一定是强连通图

枚举每条无向边,若删掉这条边,x可以走到y,则保留y->x

否则x->y

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int 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;
}
vector<int> tu[5005],g[5005];
int l[5005],r[5005],cnt,ans[5005],vis[5005],id[5005];
void dfs(int x){
	vis[x]=1;
	for(auto ed:tu[x]){
		if(vis[ed]) continue;
		dfs(ed);
	}
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int n=read(),m=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read(),opt=read();
		if(opt==1){
			g[u].push_back(v);
		}
		else{
			l[++cnt]=u;
			r[cnt]=v;
			id[cnt]=i;
		}
	}
	for(int i=1;i<=cnt;i++){
		for(int j=1;j<=n;j++){
			tu[j].clear();
			tu[j]=g[j];
		}
		for(int j=1;j<=cnt;j++){
			if(i==j) continue;
			if(!ans[id[j]]) tu[l[j]].push_back(r[j]);
			else tu[r[j]].push_back(l[j]);
		}
		for(int j=1;j<=n;j++) vis[j]=0;
		dfs(l[i]);
		if(vis[r[i]]){
			ans[id[i]]=1;
		}
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}

Pink Floyd

关于竞赛图有一些性质:竞赛图缩点后必然是一条链

如果不存在粉色边,我们可以依次询问1和2,2和3…n-1和n的绿色边的方向,然后缩点,找到入度为0的强连通分量中的任意一个点即可

那如果有粉色边,那么我们对粉色边进行缩点,得到一张DAG

如果说入度为0的强连通分量只有一个,其中任意一个点就是答案

否则我们任意找两个入读为0的强连通分量,设其中的某个点分别为x和y,我们查询x和y的绿色边的方向,如果方向为x指向y,那么我们可以把y从其强连通分量中删去

最后只剩一个入度为0的强连通分量,其中任意一个点就是答案

点双/边双/割点/割边(无向图)

点双:任意两点可以通过不同路径到达

割点:删去后图不连通(位于多个点双内)

无向图只有返祖边,无横叉边/前向边

根节点要特判,至少要有两棵dfn子树

新年的毒瘤

毒瘤节点只需满足:

1.不是割点(保证图联通)

2.删完后图有n-2条边(保证无环),即点的度数为m-n+2

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct Node{
	int nex,to;
}e[500005];
int low[500005],dfn[500005],cc[500005];
int n,m,x,y,h[500005],sum=0,fa,vis[500005];
int cnt=1,ccc;
vector<int> tu[500005];
void add(int x,int y){
	cnt++;
	e[cnt]={h[x],y};
	h[x]=cnt;
}
void Tarjan(int x){
	dfn[x]=low[x]=++sum;
	int su=0;
	for (int i=h[x];i;i=e[i].nex){
		int v=e[i].to;
		if (!dfn[v]){
			su++;
			Tarjan(v);
			low[x]=min(low[x],low[v]);
			if (low[v]>=dfn[x]&&x!=fa&&!vis[x]){
				vis[x]=1;
				ccc++;
			}
		}
		else{
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(!vis[x]&&su>=2&&x==fa){
		ccc++;
		vis[x]=1;
	}
}
signed main(){
	cin>>n>>m;
	for (int i=1;i<=m;i++){
		cin>>x>>y;
		add(x,y);
		add(y,x);
		tu[x].push_back(y);
		tu[y].push_back(x);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			fa=i;
			Tarjan(i);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		if(tu[i].size()==m-n+2&&!vis[i]) ans++;
	}
	cout<<ans<<'\n';
	for(int i=1;i<=n;i++){
		if(tu[i].size()==m-n+2&&!vis[i]) cout<<i<<' ';
	}
	cout<<'\n';
	return 0;
}

image

发现菊花图没有长度>=3的链

找到这样的链,暴力判断这4个点是否是混沌点即可

[POI 2008] BLO-Blockade

若不是割点,答案2*n-2

否则是两个联通块大小乘积

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct Node{
	int nex,to;
}e[5000005];
int low[5000005],dfn[5000005],cc[5000005],siz[5000005],ans[5000005];
int n,m,h[5000005],col,cnt;
void add(int x,int y){
	cnt++;
	e[cnt]={h[x],y};
	h[x]=cnt;
}
void Tarjan(int x){
	dfn[x]=low[x]=++col;
	int sum=0;
	siz[x]=1;
	for (int i=h[x];i;i=e[i].nex){
		int v=e[i].to;
		if(!dfn[v]){
			Tarjan(v);
			low[x]=min(low[x],low[v]);
			siz[x]+=siz[v];
			if(low[v]>=dfn[x]){
				ans[x]+=siz[v]*sum;
				sum+=siz[v];
			}
		}
		else low[x]=min(low[x],dfn[v]);
	}
	ans[x]+=(n-sum-1)*sum;
}
signed main(){
	cin>>n>>m;
	for (int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			Tarjan(i);
		}
	}
	for(int i=1;i<=n;i++){
		ans[i]+=n-1;
		ans[i]*=2;
		cout<<ans[i]<<'\n';
	}
	return 0;
}

[HNOI2012] 矿场搭建

先跑点双

若点双割点>=2,不用建

割点=1,需要建一个,且不能建在割点上,方案数贡献点双siz-1(最后乘起来)

割点=0,也就是整个图是一个巨大的强连通,建2个,方案数n*(n-1)/2;

#include<bits/stdc++.h>
using namespace std;
#define int long long
int low[5005],dfn[5005];
int m,sum,fa,vis[5005];
int ccc;
int st[5005],top;
vector<int> ans[5005],tu[5005];
void Tarjan(int x){
	dfn[x]=low[x]=++sum;
	st[++top]=x;
	int su=0;
	for(auto v:tu[x]){
		if(!dfn[v]){
			su++;
			int qwq=fa;
			fa=x;
			Tarjan(v);
			fa=qwq;
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				if(fa&&!vis[x]) vis[x]=1;
				ccc++;
				while(st[top+1]!=v){
					ans[ccc].push_back(st[top--]);
				}
				ans[ccc].push_back(x);
			}
		}
		else if(v!=fa){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(!vis[x]&&su>=2&&fa==0) vis[x]=1;
	if(su==0&&fa==0){
		ccc++;
		ans[ccc].push_back(x);
	}
}
bool solve(int biao){
	cin>>m;
	int n=0;
	if(!m) return true;
	sum=ccc=top=0;
	for(int i=1;i<=5000;i++){
		tu[i].clear();
		ans[i].clear();
		st[i]=vis[i]=low[i]=dfn[i]=0;
	}
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		tu[u].push_back(v);
		tu[v].push_back(u);
		n=max(n,max(u,v));
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			fa=0;
			Tarjan(i);
		}
	}
	int an=1,suu=0;
	for(int i=1;i<=ccc;i++){
		int cn=0;
		for(auto j:ans[i]){
			if(vis[j]) cn++;
		}
		int zh=ans[i].size();
		if(cn==1){
			suu++;
			an*=(zh-1);
		}
		if(cn==0){
			suu=2;
			an=zh*(zh-1)/2;
		}
	}
	cout<<"Case "<<biao<<": ";
	cout<<suu<<' '<<an<<'\n';
	return false;
}
signed main(){
	int cnt=0;
	while(true){
		cnt++;
		if(solve(cnt)) break;
	}
	return 0;
}

[NOI2016] 网格

注意到答案一定不超过2

若只剩下一个跳蚤或者剩下两个相邻的跳蚤时,答案是-1

若蛐蛐把跳蚤分割成两个联通块,答案为0

若蛐蛐周围跳蚤连成的图存在割点(注意跳蚤要取两圈),答案为1

若都不满足答案为2

边双

注意边双的重边可能有用

边双中一个点只能在一个边双中

有机化学之神偶尔会做作弊

用割边找出来环,缩完点以后树剖即可(但我不会树剖呜呜呜)

image

先求出边双,然后加边后,把两点间的路径标记为非割边,然后输出割边个数即可

image

割边一定要启动

则答案<=min(割边)

然后对环考虑

Case of Computer Network

缩点后定向

Legacy

线段树优化建图

[SNOI2017] 炸弹

发现一个炸弹炸了周围也会炸

形成强连通

考虑缩点

然后DP

然后没了

[POI 2015] PUS

差分约束加上线段树优化建图

缩点,dag上dp即可(差分约束SPFA会被卡)

posted @ 2025-08-21 11:33  gbrrain  阅读(3)  评论(0)    收藏  举报