kuangbin带你飞 专题九 连通图

poj 1236

题意:给你一些有向边,然后求至少给几个学校发消息,才能让所有学校都获得消息,还有个问题是需要添多少条边,才能让这个变成连通图

题解:用tarjan缩点,然后算每个连通分量的入度和出度

第一个问题的答案就是入度为0的个数

第二个问题是入度为0和出度为0的个数的最大值

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>

using namespace std;
#define   MAX       105
#define   MAXN      100005
#define   lson      l,m,rt<<1
#define   rson      m+1,r,rt<<1|1
#define   lrt       rt<<1
#define   rrt       rt<<1|1
#define   mid       int m=(r+l)>>1
#define   LL        long long
#define   ull       unsigned long long
#define   mem0(x)   memset(x,0,sizeof(x))
#define   mem1(x)   memset(x,-1,sizeof(x))
#define   meminf(x) memset(x,INF,sizeof(x))
#define   lowbit(x) (x&-x)

const int    mod   = 1000000007;
const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;

//读入外挂
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}

struct Edge{
    int v,next;
}edge[MAX*MAX];

int head[MAX];
int DFN[MAX];//记录搜到的时间序号
int low[MAX];//记录该点往下搜,能搜到的栈中最小的时间序号
int instack[MAX];//某个元素是否在栈中
int sstack[MAX];//模拟栈
int belong[MAX];
int in[MAX];
int out[MAX];
int index;
int tot;
int top;
int cnt;

void add_edge(int a,int b){
    edge[tot]=(Edge){b,head[a]};
    head[a]=tot++;
}

void tarjan(int u){
    DFN[u]=low[u]=++index;
    instack[u]=1;
    sstack[++top]=u;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(!DFN[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v]) low[u]=min(DFN[v],low[u]);
    }
    if(DFN[u]==low[u]){
        cnt++;
        while(1){
            int k=sstack[top--];
            instack[k]=0;
            belong[k]=cnt;
            if(k==u) break;
        }
    }
}

int main(){
    int n;
    scanf("%d",&n);
    mem1(head);
    mem0(instack);
    mem0(DFN);
    mem0(in);
    mem0(out);
    tot=0;
    top=0;
    cnt=0;
        index=0;
        for(int i=1;i<=n;i++){
            int a;
            while(scanf("%d",&a)&&a){
                add_edge(i,a);
            }
        }
        for(int i=1;i<=n;i++){
            if(!DFN[i]) tarjan(i);
        }
        for(int i=1;i<=n;i++){
            for(int j=head[i];j!=-1;j=edge[j].next){
                int v=edge[j].v;
                if(belong[i]==belong[v]) continue;
                in[belong[v]]++;
                out[belong[i]]++;
            }
        }
        int innum=0;
        int outnum=0;
        for(int i=1;i<=cnt;i++){
            if(in[i]==0) innum++;
            if(out[i]==0) outnum++;
        }
        if(cnt==1) printf("1\n0\n");
        else{
            printf("%d\n",innum);
            printf("%d\n",max(innum,outnum));
        }
    return 0;
}
poj 1144

题意:给你一个无向连通图,求几个点,如果去掉这个点,图就不连通了

题解:这是求割点的模板题,就是输入特别hentai

求割点的思想就用在tarjan上修改,如果是根节点,他有二个儿子或者以上,那么就是割点,如果一个点不是根节点,往下深搜,如果他的子节点的low大于等于他的DFN

(DFN记录这个点到达的时间,low记录这个点或者他的儿子到达栈中的元素的DFN),DFN[u]<=low[v]的意思就是v点只能达到还未搜过的点或者是父节点,意思就是去掉这个u,他们就不连通了。

终于获得了神器——王尼玛的图论模板(感觉逼格暴增,不过仍需努力)

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>

using namespace std;
#define   MAX       105
#define   MAXN      100005
#define   lson      l,m,rt<<1
#define   rson      m+1,r,rt<<1|1
#define   lrt       rt<<1
#define   rrt       rt<<1|1
#define   mid       int m=(r+l)>>1
#define   LL        long long
#define   ull       unsigned long long
#define   mem0(x)   memset(x,0,sizeof(x))
#define   mem1(x)   memset(x,-1,sizeof(x))
#define   meminf(x) memset(x,INF,sizeof(x))
#define   lowbit(x) (x&-x)

const int    mod   = 1000000007;
const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;

//读入外挂
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}

struct Edge{
    int v,next;
}edge[MAX*MAX];

int head[MAX];
int DFN[MAX];//记录搜到的时间序号
int low[MAX];//记录该点往下搜,能搜到的栈中最小的时间序号
int vis[MAX];
int root;
int index;
int tot;
int tmp;

void add_edge(int a,int b){
    edge[tot]=(Edge){b,head[a]};
    head[a]=tot++;
}

void tarjan(int u){
    DFN[u]=low[u]=++index;
    vis[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(!vis[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(DFN[u]<=low[v]) vis[u]++;
        }
        else low[u]=min(DFN[v],low[u]);
    }
    if(u==root&&vis[u]>2||u!=root&&vis[u]>1) tmp++;
}

int main(){
    int n;
    while(scanf("%d",&n)&&n){
        int u,v;
        mem0(DFN);
        mem0(low);
        mem0(vis);
        mem1(head);
        tot=0;
        root=1;
        index=0;
        tmp=0;
        while(scanf("%d",&u)&&u){
            while(getchar()!='\n'){
                scanf("%d",&v);
                add_edge(u,v);
                add_edge(v,u);
            }
        }
        for(int i=1;i<=n;i++){
            if(!DFN[i]) tarjan(i);
        }
        printf("%d\n",tmp);
    }
    return 0;
}

poj 2117

题意:给你n个发电厂,然后m条边,(无向图),问你拆掉一个发电厂,如何让连通块最多

题解:肯定是拆割点啦,就是算每个割点和多少个儿子连着(根和非根要分开算,还要用tarjan算出一开始有多少个连通块)另外要注意trick,如果特殊情况割点为0的时候:如果边是0的话那么就是连通块个数-1,如果有边的话那么就是连通块的个数啊

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>

using namespace std;
#define   MAX       10005
#define   MAXN      100005
#define   lson      l,m,rt<<1
#define   rson      m+1,r,rt<<1|1
#define   lrt       rt<<1
#define   rrt       rt<<1|1
#define   mid       int m=(r+l)>>1
#define   LL        long long
#define   ull       unsigned long long
#define   mem0(x)   memset(x,0,sizeof(x))
#define   mem1(x)   memset(x,-1,sizeof(x))
#define   meminf(x) memset(x,INF,sizeof(x))
#define   lowbit(x) (x&-x)

const int    mod   = 1000000007;
const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;

//读入外挂
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}

struct Edge{
    int v,next;
}edge[50000000];

int head[MAX];
int DFN[MAX];//记录搜到的时间序号
int low[MAX];//记录该点往下搜,能搜到的栈中最小的时间序号
int vis[MAX];
int instack[MAX];
int sstack[MAX];
int Index;
int tot;
int tmp;
int cnt;
int top;

void add_edge(int a,int b){
    edge[tot].v=b;
    edge[tot].next=head[a];
    head[a]=tot++;
}

void tarjan(int u,int fa){
    DFN[u]=low[u]=++Index;
    vis[u]=1;
    instack[u]=1;
    sstack[++top]=u;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(!vis[v]){
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(DFN[u]<=low[v]) vis[u]++;
        }
        else if(instack[v])low[u]=min(DFN[v],low[u]);
    }
    if(fa==-1&&vis[u]>2) tmp=max(tmp,vis[u]-2);
    if(fa!=-1&&vis[u]>1) tmp=max(tmp,vis[u]-1);
    if(DFN[u]==low[u]){
        cnt++;
        while(1){
            int k=sstack[top--];
            instack[k]=0;
            if(k==u) break;
        }
    }
}

int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)&&n){
        int u,v;
        mem0(DFN);
        mem0(low);
        mem0(vis);
        mem1(head);
        mem0(instack);
        tot=0;
        Index=0;
        tmp=0;
        top=0;
        cnt=0;
        for(int i=0;i<m;i++){
            scanf("%d%d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        for(int i=0;i<n;i++){
            if(!DFN[i]) tarjan(i,-1);
        }
        if(tmp==0){
            if(m==0) printf("%d\n",cnt-1);
            else printf("%d\n",cnt);
        }
        else printf("%d\n",tmp+cnt);
    }
    return 0;
}


HDU 4612

题意:给你一个无向图,里面有桥,问你连给它加一条边,桥变为多少

题解:很明显是先求出桥的数量,然后缩点成一棵树,然后求树的直径,答案就是桥-直径

但是这题有20W点100W边,而且有重边,我重边处理的不太好,一直不太会,特别是20W点,我都不知道如何记录,看了题解之后学了个比较屌的方法,而且可以一边求桥一边用树形dp求直径,而不需要缩点后用连通分量做,代码顿时短了很多

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX       200005
#define   MAXN      2000005
#define   lson      l,m,rt<<1
#define   rson      m+1,r,rt<<1|1
#define   lrt       rt<<1
#define   rrt       rt<<1|1
#define   mid       int m=(r+l)>>1
#define   LL        long long
#define   ull       unsigned long long
#define   mem0(x)   memset(x,0,sizeof(x))
#define   mem1(x)   memset(x,-1,sizeof(x))
#define   meminf(x) memset(x,INF,sizeof(x))
#define   lowbit(x) (x&-x)

const LL     mod   = 1000000;
const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;

struct Edge{
    int v,next;
}edge[MAXN];
int head[MAX];
int dfn[MAX];
int low[MAX];
int vis[MAXN];
int dp[MAX][2];//0存的是点u往下的最长路径,1存的是u往下的次长路径
int tot;
int Index;
int bridge;

void init(){
    mem1(head);
    mem0(dfn);
    mem0(low);
    mem0(vis);
    tot=0;
    Index=0;
    bridge=0;
}

void add_edge(int a,int b){
    edge[tot].v=b;
    edge[tot].next=head[a];
    head[a]=tot++;
}

void tarjan(int u){
    dfn[u]=low[u]=++Index;
    dp[u][0]=dp[u][1]=0;
    for(int i=head[u];i!=-1;i=edge[i].next){
        if(!vis[i>>1]){
            vis[i>>1]=1;
            int v=edge[i].v;
            if(!dfn[v]){
                tarjan(v);
                int temp=dp[v][0];//树形dp
                if(dfn[u]<low[v]){
                    bridge++;
                    temp++;//是否在同一个连通块里
                }
                if(temp>dp[u][0]){
                    dp[u][1]=dp[u][0];
                    dp[u][0]=temp;
                }
                else if(dp[u][1]<temp) dp[u][1]=temp;
                low[u]=min(low[v],low[u]);
            }
            else low[u]=min(dfn[v],low[u]);
        }
    }
}
int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)&&n&&m){
        init();
        for(int i=0;i<m;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        tarjan(1);
        int ans=0;
        for(int i=1;i<=n;i++){
            ans=max(ans,dp[i][0]+dp[i][1]);
        }
        printf("%d\n",bridge-ans);
    }
    return 0;
}


hdu 4607

题意:这题给你一棵树,然后要游玩k个点,问你最少走多少距离

题解:这题很明显是和树的直径有关,但是我比较虚,k>直径时怎么弄有点犯二

现在想想挺简单的,比如a,b是直径的起点终点,然后k>直径上的点,那么就从a开始走,走到一个节点,就往不和直径重合的路径上走,直径上有d个点,那么要走的不在直径上的点就有k-d个,那么从a,b之间一些节点去绕路走这k-d个点,最少需要走2*(k-d)的距离(想上去还是挺明显的),然后直径长度d-1。

所以结论是k<=d ,ans=k-1

                    k>d,  ans=d-1+2*(k-d);

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX       200005
#define   MAXN      2000005
#define   lson      l,m,rt<<1
#define   rson      m+1,r,rt<<1|1
#define   lrt       rt<<1
#define   rrt       rt<<1|1
#define   mid       int m=(r+l)>>1
#define   LL        long long
#define   ull       unsigned long long
#define   mem0(x)   memset(x,0,sizeof(x))
#define   mem1(x)   memset(x,-1,sizeof(x))
#define   meminf(x) memset(x,INF,sizeof(x))
#define   lowbit(x) (x&-x)

const LL     mod   = 1000000;
const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;

struct Edge{
    int v,next;
}edge[MAXN];
int head[MAX];
int dp[MAX][2];
int vis[MAX];
int tot;
int d;

void init(){
    mem1(head);
    mem0(vis);
    tot=0;
    d=0;
}

void add_edge(int a,int b){
    edge[tot].v=b;
    edge[tot].next=head[a];
    head[a]=tot++;
}

void tree_dp(int u){//树形dp
    vis[u]=1;
    dp[u][0]=dp[u][1]=0;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(!vis[v]){
            tree_dp(v);
            int temp=dp[v][0]+1;
            if(temp>dp[u][0]){
                dp[u][1]=dp[u][0];
                dp[u][0]=temp;
            }
            else if(temp>dp[u][1]) dp[u][1]=temp;
        }
    }
    d=max(d,dp[u][0]+dp[u][1]);
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        init();
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0;i<n-1;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        tree_dp(1);
        d++;
        while(m--){
            int k;
            scanf("%d",&k);
            if(k<=d) printf("%d\n",k-1);
            else printf("%d\n",d-1+2*(k-d));
        }
    }
    return 0;
}

hdu 2243

题意:给你n个字符串,问你长度小于等于m的字符串中必须出现这n个其中一个子串的字符串有多少情况

题解:前面做过一定不能出现,现在是反过来考虑,所以先考虑所有字符串,就是从26^1+26^2+......+26^m,这个用快速幂+递归求等比数列就好了

然后是计算长度小于等于m的字符串中不能出现上述字符串的个数,两个相减就是结果

由于是模1<<64,直接用unsigned long long即可,会自动模

难点就在于这个n个字符串组成的矩阵

首先是构造AC自动机,然后如果val[j],last[j]不为0的话就continue,为0的地方就在矩阵上++(可以到达)

然后就是求A^1+A^2+……+A^m这个矩阵的第一行的和

如果用快速幂+递归很有可能爆掉

矩阵是一种十分奇妙的东西,假如矩阵A是n行n列,年神教我开成n+1行n+1列,最后一行都是0,最后一列都是1(A[n+1][n+1]=1)

这样的话两个矩阵相乘,第一行的前n个,还是和n*n的两个A相乘一样,第一行的最后一个值,就是n+1个1和另外个A的第一行相乘,值是A的第一行的值+1

以此类推,第二次得到的最后一个值就是A^2+A的第一行的值+1,最后得到的就是A^m-1+……+1,然后只要把最后得到的矩阵的第一行相加即可


如此巧妙的技巧我必须以后多加练习,矩阵的优化是十分的奇妙

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         100005
#define   MAXN        2000005
#define   maxnode     110
#define   sigma_size  26
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem0(x)     memset(x,0,sizeof(x))
#define   mem1(x)     memset(x,-1,sizeof(x))
#define   meminf(x)   memset(x,INF,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const LL     mod   = (1<<64);

/**************¶áèëía1ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct AC{
    int ch[maxnode][sigma_size];
    int val[maxnode], last[maxnode], f[maxnode];
    int sz;
    void init(){
        mem0(ch[0]);
        last[0]=val[0]=0;
        sz = 1;
    }
    int idx(char c){return c-'a';}
    void insert(const char *s,int v){
        int n = strlen(s), u =0;
        for(int i=0;i<n;i++){
            int c = idx(s[i]);
            if(c==-1) return ;
            if(!ch[u][c]){
                mem0(ch[sz]);
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u = ch[u][c];
        }
        val[u]=v;
    }
    void bfs(){
        queue<int>q;
        f[0]=0;
        for(int c = 0; c<sigma_size;c++){
            int u = ch[0][c];
            if(u){q.push(u); f[u]=last[u]=0;}
        }
        while(!q.empty()){
            int r =q.front(); q.pop();
            for(int c = 0;c<sigma_size;c++){
                int u = ch[r][c];
                if(!u){ ch[r][c] = ch[f[r]][c]; continue;}//若不要前面那句,则要加下面那句
                q.push(u);
                int v = f[r];
                while(v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
}ac;

struct Matrix{
    int n;
    ull maze[maxnode][maxnode];

    void init(int n){
        this->n = n;
        mem0(maze);
    }
    Matrix operator * (Matrix &rhs){
        Matrix m;
        m.init(n);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                for(int k=0;k<n;k++)
                    m.maze[i][j] = m.maze[i][j] + maze[i][k] * rhs.maze[k][j];
        return m;
    }
    Matrix operator + (Matrix &rhs){
        Matrix m;
        m.init(n);
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                m.maze[i][j] = maze[i][j] + rhs.maze[i][j];
            }
        }
        return m;
    }
};

ull qpow(Matrix a,int n){
    Matrix ans;
    ans.init(a.n);
    for(int i=0;i<ans.n;i++) ans.maze[i][i] = 1;
    while(n){
        if(n&1) ans = ans * a;
        a = a * a;
        n >>= 1;
    }
    ull tmp=0;
    for(int i=0;i<ans.n;i++) tmp+=ans.maze[0][i];
    return tmp;
}

ull ppow(ull a,int m){
    ull ans=1;
    ull b=a;
    while(m){
        if(m&1) ans*=b;
        b*=b;
        m>>=1;
    }
    return ans;
}

char s[20];

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        ac.init();
        for(int i=0;i<n;i++){
            scanf("%s",s);
            ac.insert(s,1);
        }
        ac.bfs();
        Matrix A;
        A.init(ac.sz+1);
        for(int i=0;i<ac.sz;i++){
            for(int j=0;j<sigma_size;j++){
                int v=ac.ch[i][j];
                if(ac.val[v]) continue;
                else if(ac.last[v]) continue;
                A.maze[i][v]++;
            }
        }
        for(int i=0;i<=ac.sz;i++) A.maze[i][ac.sz]=1;
        Matrix B;
        B.init(2);
        B.maze[0][0]=26;B.maze[0][1]=1;
        B.maze[1][0]=0; B.maze[1][1]=1;
        ull ans=qpow(B,m);
        ull tmp=qpow(A,m);
        cout<<ans-tmp<<endl;
    }
    return 0;
}



我就好好搞通图论了

hdu 3639(起码也是个多校题,虽然也许是多校签到题)

题意:许多人可以互相支持,比如A支持B,但是他不能支持自己,然后问你最多被支持的人被支持了多少次,分别是哪几个人 

题解:有向边先建图(这题边要反向建图,比如A支持B,那就要B->A这样建图,原因是等会找多少人支持了B,这要方便搜索)

然后就是tarjan缩点,如今我的tarjan模版已经写的比较熟练比较6了,可以直接套了,每次找强连通分量的时候需要记录每个强连通分量中有多少点

同一个SCC中的点肯定都是互相支持的,如果B所在的SCC有10个点,那么就有9个人支持他

然后就是缩点之后建新图,并且记录每个SCC的入度

然后从入度为0的SCC开始dfs,记得要做标记(开头没做标记WA了,找了好久才发现错误),dfs一直搜到底,记录最大值,最后最大值-1就是支持最多的次数,然后就看每个人所属的SCC的搜到的值等于最大值,然后记录

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         5005
#define   MAXN        1000005
#define   maxnode     110
#define   sigma_size  26
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem(x,v)    memset(x,v,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const LL     mod   = (1<<64);

/**************¶ÁÈëÍâ¹Ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct Edge{
    int v,next;
}edge[30005];
int head[MAX];
int dfn[MAX];
int low[MAX];
int instack[MAX];
int sstack[MAX];
int belong[MAX];
int num[MAX];
int tmp[MAX];
int in[MAX];
int vis[MAX];
int tot,Index,cnt,top;
vector<int> v[MAX];

void add_edge(int a,int b){
    edge[tot]=(Edge){b,head[a]};
    head[a]=tot++;
}

void init(){
    mem(head,-1);
    mem(dfn,0);
    mem(low,0);
    mem(instack,0);
    mem(belong,0);
    mem(num,0);
    mem(tmp,0);
    mem(in,0);
    mem(vis,0);
    top=0;
    Index=0;
    cnt=0;
    tot=0;
}

void tarjan(int u){
    dfn[u]=low[u]=++Index;
    sstack[++top]=u;
    instack[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[v],low[u]);
        }
        else if(instack[v]) low[u]=min(dfn[v],low[u]);
    }
    if(dfn[u]==low[u]){
        cnt++;
        while(1){
            int k=sstack[top--];
            instack[k]=0;
            belong[k]=cnt;
            num[cnt]++;
            if(k==u) break;
        }
    }
}

int dfs(int u){
    vis[u]=1;
    int ans=num[u];
    for(int i=0;i<v[u].size();i++){
        int k=v[u][i];
        if(!vis[k]){
            ans+=dfs(k);
        }
    }
    return ans;
}

int main(){
    int t,kase=0;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++){
            int a=read_int();
            int b=read_int();
            add_edge(b,a);
        }
        for(int i=0;i<n;i++){
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=cnt;i++) v[i].clear();
        for(int i=0;i<n;i++){
            for(int j=head[i];j!=-1;j=edge[j].next){
                int vv=edge[j].v;
                if(belong[vv]!=belong[i]){
                    in[belong[vv]]++;
                    v[belong[i]].push_back(belong[vv]);
                }
            }
        }
        int maxn=0;
        for(int i=1;i<=cnt;i++){
            if(!in[i]){
                mem(vis,0);
                tmp[i]=dfs(i)-1;
                maxn=max(maxn,tmp[i]);
            }
        }
        vector<int> vans;
        for(int i=0;i<n;i++){
            if(tmp[belong[i]]==maxn) vans.push_back(i);
        }
        kase++;
        printf("Case %d: %d\n",kase,maxn);
        for(int i=0;i<vans.size();i++){
            if(i==0) printf("%d",vans[i]);
            else printf(" %d",vans[i]);
        }
        printf("\n");
    }
    return 0;
}

poj 2762

题意:给你一些有向边,给的应该是连通图,然后问你随便选两个点,能否从A走到B,或者从B走到A(看清楚是或者 or)

题解:这题是弱连通,就是连通图里任意两点,只要有一点能到另一点即可

首先用tarjan缩点去环,因为环中的点必然满足条件,然后考虑这是一棵树,边是有向边,A是根,B,C是他的儿子,那么B必然不能到达C,C也不能到达B,所以这个图如果要是弱连通,那么必须是一个链

那么考虑下入度和出度即可,只有一个入度为0的点,一个出度为0的点,还有都是入度=出度的点‘

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         5005
#define   MAXN        1000005
#define   maxnode     110
#define   sigma_size  26
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem(x,v)    memset(x,v,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const LL     mod   = (1<<64);

/**************¶ÁÈëÍâ¹Ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct Edge{
    int v,next;
}edge[30005];
int head[MAX];
int dfn[MAX];
int low[MAX];
int instack[MAX];
int sstack[MAX];
int belong[MAX];
int in[MAX];
int out[MAX];
int tot,Index,cnt,top;

void add_edge(int a,int b){
    edge[tot]=(Edge){b,head[a]};
    head[a]=tot++;
}

void init(){
    mem(head,-1);
    mem(dfn,0);
    mem(low,0);
    mem(instack,0);
    mem(belong,0);
    mem(in,0);
    mem(out,0);
    top=0;
    Index=0;
    cnt=0;
    tot=0;
}

void tarjan(int u){
    dfn[u]=low[u]=++Index;
    sstack[++top]=u;
    instack[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[v],low[u]);
        }
        else if(instack[v]) low[u]=min(dfn[v],low[u]);
    }
    if(dfn[u]==low[u]){
        cnt++;
        while(1){
            int k=sstack[top--];
            instack[k]=0;
            belong[k]=cnt;
            if(k==u) break;
        }
    }
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++){
            int a=read_int();
            int b=read_int();
            add_edge(a,b);
        }
        for(int i=1;i<=n;i++){
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=n;i++){
            for(int j=head[i];j!=-1;j=edge[j].next){
                int v=edge[j].v;
                if(belong[v]!=belong[i]){
                    in[belong[v]]++;
                    out[belong[i]]++;
                }
            }
        }
        int ans=0;
        int flag=0;
        for(int i=1;i<=cnt;i++){
            if(!in[i]) ans++;
            if(out[i]>1) flag=1;
            if(ans>1) flag=1;
        }
        if(flag) printf("No\n");
        else printf("Yes\n");
    }
    return 0;
}


hdu 3394

题意:给你一个连通图,没有重边(好啊),然后就是每个旅游路线是一个环,如果不在路线中的边就是可以去掉,如果在多个环中的边就是会冲突

让你求有多少边可以去掉,多少边冲突

题解:可以去掉的边就是无向图的桥,冲突的边就是在双连通分量中

在一个会起冲突的双连通分量中,必然是没有割点的,然而边-双连通分量有可能会有割点,而点-双连通分量就是没有割点的

然而会起冲突,说明这个点-双连通分量中的边比点多,想一下就是了,然后就是怎么求的问题了

套用了点-双连通分量的模版,然而这个并不能求边的数量,所以在stack里面存的是这个点-双连通分量的边,并且tarjan要做一点修改,如果是向已经走过的点更新,那么只要和比自己时间戳dfn小的点比较就行了,因为这是无向图,你在考虑比你时间戳大的点的时候,如果这个点已经走过了,那么在考察这个点的时候,他已经和前面的点更新过了,所以这会就不要考虑了,因为在每次要更新的地方把双连通分量中的边放入stack中

做个点双连通分量的模版把

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX           10005
#define   MAXN          200005
#define   maxnode       1005
#define   sigma_size    4
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   lrt           rt<<1
#define   rrt           rt<<1|1
#define   mid           int m=(r+l)>>1
#define   LL            long long
#define   ull           unsigned long long
#define   mem(x,v)      memset(x,v,sizeof(x))
#define   lowbit(x)     (x&-x)
#define   pii           pair<int,int>


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const int    mod   = 9937;

/**************¶áèëía1ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*****************************************************/

struct Edge{
    int u,v,next;
} edge[MAXN];
int head[MAX];
int dfn[MAX];
int low[MAX];
int vis[MAXN];
int num[MAX];//记录点双连通分量中有多少边
int belong[MAX];
int tot,Index,cnt,bridge,ans;
vector<int> ddc[MAX];
stack<Edge> q;

void add_edge(int a,int b){
    edge[tot]=(Edge){a,b,head[a]};
    head[a]=tot++;
}

void init(){
    mem(head,-1);
    mem(dfn,0);
    mem(low,0);
    mem(num,0);
    mem(vis,0);
    mem(belong,0);
    tot=Index=cnt=bridge=ans=0;
}

void tarjan(int u,int fa){
    dfn[u]=low[u]=++Index;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            q.push(edge[i]);
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v]){
                bridge++;
                q.pop();//这条边是桥,直接弹出
            }
            else if(dfn[u]<=low[v]){
                cnt++;
                ddc[cnt].clear();
                while(1){
                    Edge k=q.top(); q.pop();
                    num[cnt]++;
                    if(belong[k.u]!=cnt){
                        ddc[cnt].push_back(k.u);
                        belong[k.u]=cnt;
                    }
                    if(belong[k.v]!=cnt){
                        ddc[cnt].push_back(k.v);
                        belong[k.v]=cnt;
                    }
                    if(k.u==u&&k.v==v) break;
                }
            }
        }
        else if(dfn[u]>dfn[v]&&v!=fa) {
            q.push(edge[i]);//不能重复
            low[u]=min(low[u],dfn[v]);
        }
    }
}

int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)&&n){
        init();
        for(int i=0; i<m; i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        for(int i=0; i<n; i++){
            if(!dfn[i]) tarjan(i,-1);
        }
        for(int i=1;i<=cnt;i++){
            if(num[i]>ddc[i].size()) ans+=num[i];
        }
        printf("%d %d\n",bridge,ans);
    }
    return 0;
}

hdu 2242

题意:这么显然,就是把一棵树切成两半,然后两边最小差值

题解:tarjan缩点(如今理解了点和边双连通的不同,这个缩点是只要成环的都在一起就行,不考虑有没有割点,所以是边-双连通分量)

并且记录每个连通分量中有多少点

然后就是dfs搜索啦,当成一棵树,从根节点进去,考虑每个儿子的中的节点数量,就是搜树的重心,唉我不会用树形dp写啊,太搓了

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         10005
#define   MAXN        100005
#define   maxnode     1005
#define   sigma_size  4
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem(x,v)    memset(x,v,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const LL     mod   = (1<<64);

/**************¶áèëía1ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct Edge{
    int v,next;
}edge[MAXN];
int head[MAX];
int dfn[MAX];
int low[MAX];
int instack[MAX];
int sstack[MAX];
int belong[MAX];
int in[MAX];
int vis[MAX];
int num[MAX];
int tmp[MAX];
int tot,Index,top,cnt,sum,ans;
vector<int> v[MAX];

void init(){
    mem(head,-1);
    mem(dfn,0);
    mem(low,0);
    mem(instack,0);
    mem(belong,0);
    mem(num,0);
    mem(tmp,0);
    mem(vis,0);
    tot=0;
    Index=0;
    top=0;
    cnt=0;
    sum=0;
    ans=INF;
}

void add_edge(int a,int b){
    edge[tot]=(Edge){b,head[a]};
    head[a]=tot++;
}

void tarjan(int u,int fa){
    dfn[u]=low[u]=++Index;
    instack[u]=1;
    sstack[++top]=u;
    int flag=0;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(v==fa&&!flag){
            flag=1;
            continue;
        }
        if(!dfn[v]){
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v]) low[u]=min(dfn[v],low[u]);
    }
    if(low[u]==dfn[u]){
        cnt++;
        while(1){
            int k=sstack[top--];
            belong[k]=cnt;
            instack[k]=0;
            tmp[cnt]+=num[k];
            if(k==u) break;
        }
    }
}

int dfs(int u,int fa){
    int ret=tmp[u];
    for(int i=0;i<v[u].size();i++){
        int k=v[u][i];
        if(k==fa) continue;
        ret+=dfs(k,u);
    }
    ans=min(ans,abs(sum-2*ret));
    return ret;
}

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=0;i<n;i++){
            scanf("%d",&num[i]);
            sum+=num[i];
        }
        for(int i=0;i<m;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        for(int i=0;i<n;i++){
            if(!dfn[i]) tarjan(i,-1);
        }
        if(cnt==1){
            printf("impossible\n");
            continue;
        }
        for(int i=1;i<=cnt;i++) v[i].clear();
        for(int i=0;i<n;i++){
            for(int j=head[i];j!=-1;j=edge[j].next){
                int vv=edge[j].v;
                if(belong[i]!=belong[vv]){
                    v[belong[i]].push_back(belong[vv]);
                }
            }
        }
        dfs(1,-1);
        printf("%d\n",ans);
    }
    return 0;
}


hdu 3861

题意:国王要把国家分成几个州,满足一些条件,如果两个点属于同一个强连通,那么他们必须在同一个州,在同一个州里的点,必须满足弱连通

题解:tarjan缩点,变成一棵树,然后从入度为0的点放去队列中,bfs+dfs,走一条链,走过一个点就标记,并且把其他与他相连的点入度-1,看最后有多少个链(弱连通)

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         5005
#define   MAXN        100005
#define   maxnode     1005
#define   sigma_size  4
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem(x,v)    memset(x,v,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const LL     mod   = (1<<64);

/**************¶áèëía1ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct Edge{
    int v,next;
}edge[MAXN];
int head[MAX];
int dfn[MAX];
int low[MAX];
int instack[MAX];
int sstack[MAX];
int belong[MAX];
int in[MAX];
int vis[MAX];
int num[MAX];
int tot,Index,top,cnt;
vector<int> v[MAX];
queue<int> q;

void init(){
    mem(head,-1);
    mem(dfn,0);
    mem(low,0);
    mem(instack,0);
    mem(belong,0);
    mem(num,0);
    mem(in,0);
    mem(vis,0);
    tot=0;
    Index=0;
    top=0;
    cnt=0;
}

void add_edge(int a,int b){
    edge[tot]=(Edge){b,head[a]};
    head[a]=tot++;
}

void tarjan(int u){
    dfn[u]=low[u]=++Index;
    instack[u]=1;
    sstack[++top]=u;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v]) low[u]=min(dfn[v],low[u]);
    }
    if(low[u]==dfn[u]){
        cnt++;
        while(1){
            int k=sstack[top--];
            belong[k]=cnt;
            instack[k]=0;
            num[cnt]++;
            if(k==u) break;
        }
    }
}

void dfs(int u){
    int flag=0;
    for(int i=0;i<v[u].size();i++){
        int k=v[u][i];
        if(vis[k]) continue;
        if(!flag&&!vis[k]){
            vis[k]=1;
            dfs(k);
            flag=1;
        }
        else{
            in[k]--;
            if(in[k]==0){
                q.push(k);
                vis[k]=1;
            }
        }
    }
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
        }
        for(int i=1;i<=n;i++){
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=cnt;i++) v[i].clear();
        for(int i=1;i<=n;i++){
            for(int j=head[i];j!=-1;j=edge[j].next){
                int vv=edge[j].v;
                if(belong[i]!=belong[vv]){
                    in[belong[vv]]++;
                    v[belong[i]].push_back(belong[vv]);
                }
            }
        }
        while(!q.empty()) q.pop();
        for(int i=1;i<=cnt;i++){
            if(!in[i]){
                vis[i]=1;
                q.push(i);
            }
        }
        int ans=0;
        while(!q.empty()){
            int u=q.front();
            q.pop();
            ans++;
            dfs(u);
        }
        printf("%d\n",ans);
    }
    return 0;
}


这题其实还有另外种想法,就是二分图最小路径覆盖(路径是一条链啊,然后覆盖所有点,需要多少条路径)

由于给的图不是二分图,所以要把一个点拆成x部和y部,然后建边,在用hk算法匹配的时候只要写y部的i点匹配x部的j点就行,然后就不需要实际把两个点拆开

我二分图理解还不够深刻,这个也是想了好久,然后我的HK算法代码就是x部和y部都match了,比较搓,还没好好学




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值