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;
}
发现菊花图没有长度>=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
边双
注意边双的重边可能有用
边双中一个点只能在一个边双中
有机化学之神偶尔会做作弊
用割边找出来环,缩完点以后树剖即可(但我不会树剖呜呜呜)
先求出边双,然后加边后,把两点间的路径标记为非割边,然后输出割边个数即可
割边一定要启动
则答案<=min(割边)
然后对环考虑
Case of Computer Network
缩点后定向
Legacy
线段树优化建图
[SNOI2017] 炸弹
发现一个炸弹炸了周围也会炸
形成强连通
考虑缩点
然后DP
然后没了
[POI 2015] PUS
差分约束加上线段树优化建图
缩点,dag上dp即可(差分约束SPFA会被卡)




浙公网安备 33010602011771号