Jeanny
寂兮,寥兮,独立不改,周行而不殆

春有百花秋有月,夏有凉风冬有雪,若无闲事挂心头,便是人间好时节。  --《无门关》

解析:如果能没有忧思悲恐缠绕心田,那么每年每季每天都是人间最好的时节。

Tarjan讲解,时间戳和low[]


 

P2341(受欢迎的奶牛)

//统计出度为0的奶牛联通块
//如果出度为0的奶牛连通块不止一个,则没有一只奶牛为明星奶牛
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,x,y,cnt,tim,tp,scc_num,tot,hd[10005],dfn[10005],low[10005],stk[10005],vis[10005],blong[10005],num[10005],ot[10005];
struct Edge{
   int st,to,nxt;
}edge[50005];
void add(int u,int v){
  cnt++;
  edge[cnt].st = u;
  edge[cnt].to = v;
  edge[cnt].nxt = hd[u];
  hd[u] = cnt;
}
void tarjan(int u){
  dfn[u] = low[u] = ++tim;
  stk[++tp] = u;
  vis[u] = 1;    //是否在栈中,用来判断横叉边
  for(int i = hd[u]; i ; i = edge[i].nxt){
      int v = edge[i].to;
      if(!dfn[v]){ //如果没有被访问过
        tarjan(v);
          low[u] = min(low[u],low[v]);
      }
      else if(vis[v]){//如果被访问过了,则看是不是横叉边,如果是,则不处理,如果是返祖边,则更新
          low[u] = min(dfn[v],low[u]);
      }
  }//要访问完所有的边,从而确定当前点的最大强连通分量
  //如果当前点的所有边都访问完了,且其low == dfn 则说明他们子孙节点再也没有返祖的情况,则该栈中的点独立成为强连通分量了
  int tmp;
  if(low[u] == dfn[u]){
    scc_num++;
    do{
      tmp = stk[tp--];
      blong[tmp] = scc_num;
      vis[tmp] = 0;//出栈
      num[scc_num]++;//该强连通分量中有几个元素
    }while(tmp != u);
  }
}

int main(){
  scanf("%d%d",&n,&m);
  for(int i = 1; i <= m; i++){
    scanf("%d%d",&x,&y);
    add(x,y);
  }
  for(int i = 1; i <= n; i++){
    if(dfn[i] == 0){
      tarjan(i);
    }
  }
  for(int i = 1; i <= cnt; i++){
      if(blong[edge[i].st] != blong[edge[i].to]){
          ot[blong[edge[i].st]]++;
      }
  }
  int p;
  for(int i = 1; i <= scc_num; i++){
    if(ot[i] == 0)
      p = i, tot++;
  }
  if(tot == 1) printf("%d\n",num[p]);
  else printf("0\n");
  return 0;
}

 ### 割边

 

 

### 割点

P3469 [POI2008]BLO-Blockade

 

      对于非割点,答案显然是2(n-1) (因为它不能影响别的点对连通性,能影响的只是别人到它以及它到别人)

对于割点,它把那几块弄得无法联通,即那几块中不同块的两个点肯定就无法联通了,答案也就是每组块的点的数量互相乘出来t[1](n-t[1])+t[2](n-t[2])+......+t[a](n-t[a])+(n-1)+(1+sum)(n-sum-1),最后再加上2(n-1)。

      一开始想不明白为什么(n-1)*1就要乘以2,但是t[1]*(n-t[1])就不用乘以2,原因在于遍历每一棵子树,如果x(子树)与y(另一个子树)一个数对,那么遍历到y子树,就会和x成一个数对。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000010;
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*t;
}
int n,m,head[maxn],num=0;
int dfn[maxn],low[maxn],size[maxn],tot=0;
long long ans[maxn];
bool cut[maxn];
struct node{
    int v,nex;
}e[maxn];
void add(int u,int v)
{
    e[++num].v=v;
    e[num].nex=head[u];
    head[u]=num;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++tot;
    size[u]=1;
    int flag=0,sum=0;
    for(int i=head[u];i;i=e[i].nex)
    {
        int v=e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            size[u]+=size[v];
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])
            {
                ans[u]+=(long long)size[v]*(n-size[v]);
                sum+=size[v];
                flag++;
                if(u!=1||flag>1) cut[u]=true;
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
    if(!cut[u]) ans[u]=2*(n-1);
    else ans[u]+=(long long)(n-sum-1)*(sum+1)+(n-1);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        x=read(),y=read();
        add(x,y),add(y,x);
    }
    tarjan(1);
    for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
    return 0;
}

 

###P5022 旅行 2018年NOIP提高组day2t1

题目简意:n个点m条边的图,从1号城市开始访问其他城市,访问方法分两种,以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市。在每到达一个新的城市时,将它的编号记录下来作为字典序。要求所有城市都访问过以后的最小字典序.

输入:  输出:1 3 2 5 4 6
6 5 
1 3 
2 3 
2 5 
3 4 
4 6

输入: 输出:1 3 2 4 5 6
6 6 
1 3 
2 3 
2 5 
3 4 
4 5 
4 6
说明:n<=5000,一部分数据是m = n-1, 另一部分是m=n

解析: 本题考查的是搜索+环的判定+细节处理。

代码1:是一棵树,可以发现访问一个点,必须将该点的子树都访问完再访问别的子树。如果访问该点后去访问其他子树,根据两种访问方式的要求,该点子树将无法再次访问。所有城市都访问的要求无法满足。因此,循环与该点相连的点,找到编号最小的进行访问,深搜后回溯。
/*
不存在两条连接同一对城市的道路,->没有重边
也不存在一条连接一个城市和它本身的道路。-> 没有自环
并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。->一定连通
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdlib>
using namespace std;
int n,m,cnt,hd[5005],vis[5005],tot,f[5005],num[5005];
struct Edge{
    int to,nxt;
}edge[10000];//
void add(int u, int v){
    cnt++;
    edge[cnt].to = v;
    edge[cnt].nxt = hd[u];
    hd[u] = cnt;
    num[u]++;
}
int t ;
void dfs(int u){
    printf("%d ",u); int tot = 1;
    while(tot <= num[u]){
        int mv = n+1; tot++;
        for(int i = hd[u]; i ; i = edge[i].nxt){
            int v = edge[i].to;
            if(num[u]==1 && vis[v]) return;
            if(!vis[v] && v < mv) mv = v;
        }
        if(mv < n+1){//mv发生了变化
            vis[mv] = 1; dfs(mv);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m); int x,y;
    for(int i = 1; i <= m; i++){
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    vis[1] = 1; dfs(1);
    return 0;
}

 

 代码2:n=m,只有一个环,我们发现在环上的路径可以转换为,断开环的一条边,然后再进行代码1的操作。因此可以先找环,然后循环断开。

断开边的时候注意因为是双向边,因此如果该边边号是奇数,那么对应双向边的另一条是奇数+1,如果是偶数,对应是偶数-1. 找环的方式是tarjan变形。

 

/*
不存在两条连接同一对城市的道路,->没有重边
也不存在一条连接一个城市和它本身的道路。-> 没有自环
并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。->一定连通
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<stack>
using namespace std;
int n,m,cnt,p,hd[5005],vis[5005],tot,num[5005],nm,res[5005],ans[5005],del[10005],dfn[5005],e[10005],low[5005],istack[5005];
stack<int> q;
struct Edge{
    int to,nxt,fr;
}edge[10005];//
void add(int u, int v){
    cnt++;
    edge[cnt].fr = u;
    edge[cnt].to = v;
    edge[cnt].nxt = hd[u];
    hd[u] = cnt;
    num[u]++;
}
void tarjan(int u,int fa){
    dfn[u] = ++tot; low[u] = tot;
    istack[u] = 1;
    q.push(u);
    for(int i = hd[u]; i; i = edge[i].nxt){
        int v = edge[i].to;
        if(!dfn[v]){
            tarjan(v,u);
            if(low[v] <= low[u] && v != fa){
                low[u] = low[v], e[++nm] = i;
            }
        }else if(istack[v]){
            if(dfn[v] < low[u] && v != fa){
                low[u] = dfn[v], e[++nm] = i;
            }
        }
    }
    if(low[u] == dfn[u]){
        int x;
        do{
            x = q.top(); q.pop(); istack[x] = 0;
        }while(low[x] != dfn[x]);
    }
}
int t ;
void dfs(int u){
    res[++p] = u; int tot = 1;
    while(tot <= num[u]){
        int mv = n+1; tot++;
        for(int i = hd[u]; i ; i = edge[i].nxt){
            int v = edge[i].to;
            if(del[i] || (num[u]==1 && vis[v])) continue;//rgt: return -> continue
            if(!vis[v] && v < mv) mv = v;
        }
        if(mv < n+1){//mv发生了变化
            vis[mv] = 1; dfs(mv);
        }
    }
}
void work(){
    // for(int i = 1; i<= n ;i++) printf("%d ",res[i]);
    // cout<<endl;
    for(int i = 1; i<= n; i++){
        if(res[i] > ans[i]) return;
        if(res[i] < ans[i]){
            for(int j = 1; j <= n; j++) {
                ans[j] = res[j];
            }
            break;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m); int x,y;
    for(int i = 1; i <= m; i++){
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    if(m == n-1){
        vis[1] = 1; dfs(1);
        for(int i = 1; i <= n; i++)
            printf("%d ",res[i]);
    }else{
        tarjan(1,0);
        memset(ans,0x3f,sizeof ans);
        for(int i = 1; i <= nm; i++){//删除一条边以后,再跑dfs,之后怎么做?
            memset(del,0,sizeof del); p = 0;
            memset(vis,0,sizeof vis);

            del[e[i]] = 1; num[edge[e[i]].fr]--;
            if(e[i] % 2 == 1) del[e[i] + 1] = 1, num[edge[e[i] + 1].fr]--;
            else del[e[i]-1] = 1, num[edge[e[i]-1].fr]--;

            vis[1] = 1;    dfs(1);

            if(e[i] % 2 == 1) num[edge[e[i] + 1].fr]++;
            else num[edge[e[i]-1].fr]++;

            work();
        }
        for(int i = 1; i <= n; i++)
            printf("%d ",ans[i]);
    }
    return 0;
}
/*
3
1 3 4 5 2 6
6
1 3 2 4 5 6

1 3 2 5 4 6
*/

代码3:可以像网路流那样将边从2开始记录,这样双向边的两条边可以用抑或1来处理。

/*
不存在两条连接同一对城市的道路,->没有重边
也不存在一条连接一个城市和它本身的道路。-> 没有自环
并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。->一定连通
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<stack>
using namespace std;
int n,m,cnt=1,p,hd[5005],vis[5005],tot,num[5005],nm,res[5005],ans[5005],del[10005],dfn[5005],e[10005],low[5005],istack[5005];
stack<int> q;
//2 ^ 1 = 10 ^ 1 = 11 , 11 ^ 1 = 10
struct Edge{
    int to,nxt,fr;
}edge[10005];//
void add(int u, int v){
    cnt++;
    edge[cnt].fr = u;
    edge[cnt].to = v;
    edge[cnt].nxt = hd[u];
    hd[u] = cnt;
    num[u]++;
}
void tarjan(int u,int fa){
    dfn[u] = ++tot; low[u] = tot;
    istack[u] = 1;
    q.push(u);
    for(int i = hd[u]; i; i = edge[i].nxt){
        int v = edge[i].to;
        if(!dfn[v]){
            tarjan(v,u);
            if(low[v] <= low[u] && v != fa){
                low[u] = low[v], e[++nm] = i;
            }
        }else if(istack[v]){
            if(dfn[v] < low[u] && v != fa){
                low[u] = dfn[v], e[++nm] = i;
            }
        }
    }
    if(low[u] == dfn[u]){
        int x;
        do{
            x = q.top(); q.pop(); istack[x] = 0;
        }while(low[x] != dfn[x]);
    }
}
int t ;
void dfs(int u){
    res[++p] = u; int tot = 1;
    while(tot <= num[u]){
        int mv = n+1; tot++;
        for(int i = hd[u]; i ; i = edge[i].nxt){
            int v = edge[i].to;
            if(del[i] || (num[u]==1 && vis[v])) continue;//rgt: return -> continue
            if(!vis[v] && v < mv) mv = v;
        }
        if(mv < n+1){//mv发生了变化
            vis[mv] = 1; dfs(mv);
        }
    }
}
void work(){
    // for(int i = 1; i<= n ;i++) printf("%d ",res[i]);
    // cout<<endl;
    for(int i = 1; i<= n; i++){
        if(res[i] > ans[i]) return;
        if(res[i] < ans[i]){
            for(int j = 1; j <= n; j++) {
                ans[j] = res[j];
            }
            break;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m); int x,y;
    for(int i = 1; i <= m; i++){
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    if(m == n-1){
        vis[1] = 1; dfs(1);
        for(int i = 1; i <= n; i++)
            printf("%d ",res[i]);
    }else{
        tarjan(1,0);
        memset(ans,0x3f,sizeof ans);
        for(int i = 1; i <= nm; i++){//删除一条边以后,再跑dfs,之后怎么做?
            memset(del,0,sizeof del); p = 0;
            memset(vis,0,sizeof vis);

            del[e[i]] = 1; num[edge[e[i]].fr]--;
            del[e[i] ^ 1] = 1, num[edge[e[i] ^ 1].fr]--;

            vis[1] = 1;    dfs(1);

            num[edge[e[i]].fr]++,num[edge[e[i] ^ 1].fr]++;
            work();
        }
        for(int i = 1; i <= n; i++)
            printf("%d ",ans[i]);
    }
    return 0;
}
/*
3
1 3 4 5 2 6
6
1 3 2 4 5 6

1 3 2 5 4 6
*/

 ### 点双 P3225 [HNOI2012]矿场搭建

 

/* 首先看到割点就是Tarjan搞 但是怎么搞

首先假设我们把所有的点双都缩点 那么我们一定可以得到一棵树 然后我们就会发现

1.叶子节点(只含有一个割点的点双)必须建 因为叶子节点如果不建 一旦割点被爆就死翘了

2.非叶节点(含有两个或两个以上的割点的点双)不用建 因为即使一个割点被爆了也可以沿着另一个割点走到一个叶节点

3.还有一种情况就是整个联通块都是点双(即不含割点的点双) 这样我们讨论点双的大小

如果只有一个点 那么这个点必须建 数据没有卡这个的点所以我没写(其实是我忘写了 然后还过了)

如果有两个或两个以上的点 那么要建两个 一个被爆了还可以走另一个

方案数就是乘法原理的问题了 注意叶节点那里出口不能建在割点上

所以先Tarjan求割点再dfs一下每个联通块就好了。

*/

*/
#include<iostream>#include<cstdio>#include<cstring>#define ll long longusing namespace std;int head[505],dfn[505],low[505],vis[505],stack[505];bool cut[505],in_stack[505];int n,m,cnt,num,tot,deg,ans1,T,cases,root,top;
ll ans2;struct node
{
    int from;
    int to;
    int next;
}e[1010];inline void first(){
    memset(head,0,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(cut,0,sizeof(cut));
    memset(vis,0,sizeof(vis));
    top=cnt=tot=n=ans1=T=0; ans2=1;
}inline void insert(int from,int to){
    e[++num].from=from;
    e[num].to=to;
    e[num].next=head[from];
    head[from]=num;
}inline int read(){
    int x=0,f=1; char c=getchar();
    while (c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while (c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}void Tarjan(int now,int father)//求割点 {
    dfn[now]=low[now]=++tot;
    for(int i=head[now];i;i=e[i].next)
        {
            int v=e[i].to;
            if(!dfn[v])
            {
                Tarjan(v,now);
                low[now]=min(low[now],low[v]);
                if(low[v]>=dfn[now])
                {
                    if(now==root) deg++;
                    else cut[now]=true;
                }
            }
            else if(v!=father) low[now]=min(low[now],dfn[v]);//不要跟求环混了 具体原理去网上找 
        }
}void dfs(int x)//遍历每个连通块 {
    vis[x]=T;//标记 
    if(cut[x]) return;
    cnt++;//数量 
    for(int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if(cut[v]&&vis[v]!=T) num++,vis[v]=T;//统计割点数目。 
        //如果是割点且标记不与遍历的的连通块相同就修改标记。 
        if(!vis[v])dfs(v);
    }
}int main(){
    m=read();
    while (m)
    {
        first();
        for (int i=1;i<=m;i++)
        {
            int u=read(),v=read();
            n=max(n,max(u,v));//这个地方要处理一下 
            insert(u,v); insert(v,u);
        }
        for (int i=1;i<=n;i++)
        {
            if (!dfn[i]) Tarjan(root=i,0);
            if (deg>=2) cut[root]=1;//根节点的割点 
            deg=0;//不要忘记是多组数据 
        }
        for (int i=1;i<=n;i++)
            if (!vis[i]&&!cut[i])//不是割点 
            {
                T++; cnt=num=0;//T为连通块的标记 
                dfs(i);
                if (!num) ans1+=2,ans2*=cnt*(cnt-1)/2;//建两个 别忘记除以二 因为两个建立的出口没有差异 
                if (num==1) ans1++,ans2*=cnt;//建一个 
            }
        printf("Case %d: %d %lld\n",++cases,ans1,ans2);
        m=read();
    }
    return 0;
}

 

posted on 2020-08-15 10:41  Jeanny  阅读(140)  评论(0)    收藏  举报