缩点
题意:
给牛的个数n,和m条可传递关系,例如a->b表示a喜欢b
问被所有牛喜欢的牛有多少
分析:
缩点之后如果只有一个出度为0的超级点,那么这个点里的原始点数量就是答案,否则答案为0
code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<stack>
#include<algorithm>
typedef long long ll;
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=1e5+5;
int low[maxm],dfn[maxm],idx;
int head[maxm],to[maxm],nt[maxm],cnt;
int out[maxm];
int belong[maxm],sum,num[maxm];
bool ins[maxm];
stack<int>s;
void init(){
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
memset(head,-1,sizeof head);
memset(belong,0,sizeof belong);
memset(out,0,sizeof out);
memset(ins,0,sizeof ins);
while(!s.empty())s.pop();
idx=0;
cnt=1;
sum=0;
}
void add(int x,int y){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs(int u){
low[u]=dfn[u]=++idx;
s.push(u);
ins[u]=1;
for(int i=head[u];i!=-1;i=nt[i]){
int v=to[i];
if(!dfn[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}else if(ins[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
sum++;
while(1){
int x=s.top();
s.pop();
ins[x]=0;
belong[x]=sum;
num[sum]++;
if(x==u)break;
}
}
}
int main(){
init();
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
add(a,b);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
dfs(i);
}
}
for(int k=1;k<=n;k++){
for(int i=head[k];i!=-1;i=nt[i]){
int v=to[i];
if(belong[k]!=belong[v]){
out[belong[k]]++;//out++
}
}
}
vector<int>ans;
for(int i=1;i<=sum;i++){
if(!out[i]){
ans.push_back(i);
}
}
if(ans.size()==1){
cout<<num[ans[0]]<<endl;
}else{
cout<<0<<endl;
}
return 0;
}
文件传递问题&&最少加多少边成为强连通图
题意:
n个学校,每个学校可以通讯若干个学校(即有向边)
问题1:现在要传递文件,问开始最少给多少个学校,能由这些学校传递给其他所有学校
问题2:要添加多少条边使得所有学校强连通
分析:
先缩点,计算超级点的入度和出度。
问题1的答案是:入度为0的超级点的个数
问题2的答案是:入度为0的点的个数,出度为0的点个数,两者的最大值
这里就不证明了
另外要特判一下开始就直接缩成一个点的情况,这种情况下问题2的答案为0
code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<map>
#include<stack>
#include<vector>
#include<cmath>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=1e4+5;
int head[maxm],nt[maxm],to[maxm],cnt;
int low[maxm],dfn[maxm],idx;
int belong[maxm],sum;
int in[maxm],out[maxm];
bool ins[maxm];
stack<int>s;
void init(){
memset(head,-1,sizeof head);
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
memset(in,0,sizeof in);
memset(out,0,sizeof out);
memset(belong,0,sizeof belong);
memset(ins,0,sizeof ins);
sum=0;
idx=0;
cnt=1;
}
void add(int x,int y){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs(int u){
low[u]=dfn[u]=++idx;
s.push(u);
ins[u]=1;
for(int i=head[u];i!=-1;i=nt[i]){
int v=to[i];
if(!dfn[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}else if(ins[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
sum++;
while(1){
int x=s.top();
s.pop();
ins[x]=0;
belong[x]=sum;
if(x==u)break;
}
}
}
int main() {
init();
int n;
cin>>n;
for(int i=1;i<=n;i++){
while(1){
int t;
cin>>t;
if(!t)break;
add(i,t);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
dfs(i);
}
}
if(sum==1){//特判1的时候
cout<<1<<endl;
cout<<0<<endl;
return 0;
}
for(int k=1;k<=cnt;k++){
for(int i=head[k];i!=-1;i=nt[i]){
int v=to[i];
if(belong[k]!=belong[v]){
in[belong[v]]++;
out[belong[k]]++;
}
}
}
int inn=0,outt=0;
for(int i=1;i<=sum;i++){
if(!in[i])inn++;
if(!out[i])outt++;
}
cout<<inn<<endl;
cout<<max(inn,outt)<<endl;
return 0;
}
无向图割点
例题:uva315(以前写的)
题意:
求割点数量
割点求法:
1.如果是根节点,有两棵以上子树,则该根节点是割点
2.如果不是根节点,假设当前点为x,去的点为y,当low[y]>=dfn[x]的时候点x是割点
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<sstream>
#include<algorithm>
#define lowbit(x) (x&(-(x)))
#define coff ios::sync_with_stdio(0)
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=1e3+5;
int low[maxm],dfn[maxm],idx;
int head[maxm],nt[maxm<<1],to[maxm<<1],cnt;
set<int>ans;
int n;
int root;
void init(){
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
memset(head,-1,sizeof head);
cnt=1;
idx=0;
ans.clear();
}
void add(int x,int y){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void input(){
string s;
while(getline(cin,s)){
if(s=="0")break;
stringstream str(s);
int st;
str>>st;
int ed;
while(str>>ed){
add(st,ed);
add(ed,st);
}
}
}
void dfs(int x){
low[x]=dfn[x]=++idx;
int ch=0;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(!dfn[v]){
dfs(v);
low[x]=min(low[x],low[v]);
if(low[v]>=dfn[x]){
ch++;//这一步也可以放在外面,因为根节点的low肯定最小
if(x!=root){//如果是根且low[v]>=dfn[x]
ans.insert(x);
}
}
}else{
low[x]=min(low[x],dfn[v]);
}
}
if(x==root&&ch>=2){//如果是根节点且子树数量大于等于2
ans.insert(x);
}
}
void solve(){
for(int i=1;i<=n;i++){
if(!dfn[i]){
root=i;
dfs(i);
}
}
printf("%d\n",ans.size());
}
int main(){
while(scanf("%d",&n)!=EOF&&n){
getchar();
init();
input();
solve();
}
return 0;
}
无向图割边(桥)
求有重边图的割边:
递归过程改为记录入边的编号,编号就是边在邻接表的下标(链式前向星)。
利用链式前向星成对变换的技巧,无向图的每一条边都可以看作是双向边,
因为下标是成对存储的,所以利用异或就可以判断是否是回边,
如果不是回边则可以更新low[x]
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<sstream>
#include<algorithm>
#define lowbit(x) (x&(-(x)))
#define coff ios::sync_with_stdio(0)
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=1e3+5;
int head[maxm],nt[maxm<<1],to[maxm<<2],cnt;
int low[maxm],dfn[maxm],idx;
int n,m;//n是点的数量,m是边的数量
vector<pair<int,int> >ans;//存放答案
void init(){
memset(head,-1,sizeof head);
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
cnt=1;
idx=0;
ans.clear();
}
void add(int x,int y){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void input(){
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
}
void dfs(int x,int pre){
low[x]=dfn[x]=++idx;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(!dfn[v]){
dfs(v,i);
low[x]=min(low[x],low[v]);
if(dfn[x]<low[v]){
int a=to[i];
int b=to[i^1];
// if(a>b)swap(a,b);//可选,编号小的在左边
ans.push_back({a,b});
}
}else if(i!=(pre^1)){//如果不是回边才能更新
low[x]=min(low[x],dfn[v]);
}
}
}
void solve(){
for(int i=1;i<=n;i++){//dfs
if(!dfn[i]){
dfs(i,-1);
}
}
// sort(ans.begin(),ans.end());//编号排序,可选
int len=ans.size();
printf("%d critical links\n",len);//输出割边数量
for(int i=0;i<len;i++){//输出割边的端点
printf("%d - %d\n",ans[i].first,ans[i].second);
}
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
init();
input();
solve();
}
return 0;
}
无向图双连通分量
待续
题集
2019南昌邀请赛Winner (tarjan)
题意:
n个人q次询问,每个人有三种能力值
n个人共进行n-1场比赛,每场都由你决定谁和谁比以及用哪种类型的能力值比,能力值大的获胜
然后每次询问给一个编号x,问由你决定比赛顺序,最后x能否获得冠军
思路:
对于每种能力值,排序一下,然后能力值大的向能力值小的连有向边
边是具有传递性,只需要建成一条链即可
三种能力值的图建在一起,跑tarjan缩点,入度为0的超级点内都是可以得到冠军的点
对于每次询问判断一下x所属超级点是否入度为0即可
code:
//https://nanti.jisuanke.com/t/40259
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
struct Node{
int v,id;
}e[maxm];
vector<int>g[maxm];
int n,q;
stack<int>stk;
int mark[maxm];
int dfn[maxm];
int low[maxm];
int belong[maxm];
int in[maxm];
int idx,sum;
bool cmp(Node a,Node b){
return a.v>b.v;
}
void dfs(int x){
dfn[x]=low[x]=++idx;
mark[x]=1;
stk.push(x);
for(int v:g[x]){
if(!dfn[v]){
dfs(v);
low[x]=min(low[x],low[v]);
}else if(mark[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(low[x]==dfn[x]){
sum++;
while(1){
int t=stk.top();
stk.pop();
belong[t]=sum;
mark[t]=0;
if(t==x)break;
}
}
}
signed main(){
scanf("%d%d",&n,&q);
//第一种
for(int i=1;i<=n;i++){
scanf("%d",&e[i].v);
e[i].id=i;
}
sort(e+1,e+1+n,cmp);
for(int i=1;i<n;i++){
g[e[i].id].push_back(e[i+1].id);
}
//第二种
for(int i=1;i<=n;i++){
scanf("%d",&e[i].v);
e[i].id=i;
}
sort(e+1,e+1+n,cmp);
for(int i=1;i<n;i++){
g[e[i].id].push_back(e[i+1].id);
}
//第三种
for(int i=1;i<=n;i++){
scanf("%d",&e[i].v);
e[i].id=i;
}
sort(e+1,e+1+n,cmp);
for(int i=1;i<n;i++){
g[e[i].id].push_back(e[i+1].id);
}
//缩点
for(int i=1;i<=n;i++){
if(!dfn[i])dfs(i);
}
//标记入度不为0的超级点
for(int x=1;x<=n;x++){
for(int v:g[x]){
if(belong[x]!=belong[v]){
in[belong[v]]=1;
}
}
}
//处理询问
while(q--){
int x;
scanf("%d",&x);
if(in[belong[x]]==0){//如果入度为0则说明可以第一
puts("YES");
}else{
puts("NO");
}
}
return 0;
}