Hdu 4738【tanjan求无向图的桥】割边判定定理 dfn[x] < low[y]

题目:

  曹操在长江上建立了一些点,点之间有一些边连着。如果这些点构成的无向图变成了连通图,那么曹操就无敌了。刘备为了防止曹操变得无敌,就打算去摧毁连接曹操的点的桥。但是诸葛亮把所有炸弹都带走了,只留下一枚给刘备。所以刘备只能炸一条桥。

  题目给出n,m。表示有n个点,m条桥。

  接下来的m行每行给出a,b,c,表示a点和b点之间有一条桥,而且曹操派了c个人去守卫这条桥。

 

  现在问刘备最少派多少人去炸桥。

  如果无法使曹操的点成为多个连通图,则输出-1.

 

思路:

  就是用tarjan算法算出桥的数量,再比较哪一个的值最小。

 

Tips:

  注意三点:

  ①. 有重边,所以tarjan算法要处理重边。有两种处理方法,一种是先把所有的边存下,发现两点有重边的时候就只给这两个点连一条权值为无穷大的边。或者是在tarjan算法里处理重边,即使之求u或u的子树能够追溯到的最早的栈中节点的次序号时可访问父节点的次序号。

  ②. 如果无向图图本身已经有两个连通图了,就无需派人去炸桥,这时候输出0。

  ③. 如果求出来的最小权值桥的守卫人数为0时,也需要派出一个人去炸桥。

 

Code:

// tarjan算法求无向图的桥、边双连通分量并缩点
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include <bits/stdc++.h>
#define cls(s,h) memset(s,h,sizeof s)
using namespace std;
const int N = 1010;
int n, m ;
struct edge
{
    int to;
    int pre;
    int id;
    int w;
};
edge E[N*N];
int head[N],tot;
//int soldier[N][N];//第一种方法所需的的邻接矩阵
int low[N],dfn[N],ts,top,st[N],ins[N];
int minn;

void init()
{
    cls(head,-1);
    tot=0;
    //CLR(soldier,INF);
    cls(low,0);
    cls(dfn,0);
    ts=top=0;
    cls(ins,0);
    minn=1e8;
}
inline void add(int s,int t,int w,int id)
{
    E[tot].to=t;
    E[tot].id=id;
    E[tot].w=w;
    E[tot].pre=head[s];
    head[s]=tot++;
}
void tarjan(int u,int id)
{
    low[u]=dfn[u]=++ts;
    ins[u]=1;
    st[top++]=u;
    int v;
    for (int i=head[u]; ~i; i=E[i].pre)
    {
        v=E[i].to;
        if(id==E[i].id)
            continue;
        if(!dfn[v])
        {
            tarjan(v,E[i].id);
            low[u]=min<int>(low[v],low[u]);
            if(low[v]>dfn[u])
            {
                int need=E[i].w;
                if(need<minn)
                    minn=need;
            }
        }
        else if(ins[v])
            low[u]=min<int>(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        do
        {
            v=st[--top];
            ins[v]=0;
        }
        while (u!=v);
    }
}


int main()
{
    while(scanf("%d%d",&n,&m)&&n+m)
    {
        init();

        //tot = 1;
        for (int i = 1; i <= m; i++)
        {
            int x, y,z;
            scanf("%d%d%d", &x, &y,&z);
            add(x, y,z,i), add(y,x,z,i);
        }
        int k = 0;
        minn = 1e8;
        for (int i = 1; i <= n; i++)
            if (!dfn[i]) tarjan(i, -1),k++;
        //for (int i = 2; i < tot; i += 2)
        //    if (bridge[i])
        //    printf("%d %d\n", ver[i ^ 1], ver[i]);

//    for (int i = 1; i <= n; i++)
        //    if (!c[i]) {
        //        ++dcc;
        //        dfs(i);
        //    }
        //printf("There are %d e-DCCs.\n", dcc);
        //for (int i = 1; i <= n; i++)
        //    printf("%d belongs to DCC %d.\n", i, c[i]);


        //for (int i = 2; i <= tot; i++) {
        //    int x = ver[i ^ 1], y = ver[i];
        //    if (c[x] == c[y]) continue;
        //    add_c(c[x], c[y]);
//    }
        //printf("缩点之后的森林,点数 %d,边数 %d\n", dcc, tc / 2);

        //for (int i = 2; i < tc; i += 2)
        //printf("%d %d %d\n", vc[i ^ 1], vc[i],edge[i]);
        //    minn = min(minn,edge[i]);
        if(k > 1)
            minn = 0;
        else if(minn == 0)
            minn = 1;
        else if(minn == 1e8)
            minn = -1;
        printf("%d\n",minn);

    }
    return 0;
}
View Code

 

// tarjan算法求无向图的桥、边双连通分量并缩点
// 割边判定定理 dfn[x] < low[y] 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include <bits/stdc++.h>
#define cls(s,h) memset(s,h,sizeof s)
using namespace std;
const int N = 1010;
int n, m ;
struct edge
{
    int to;
    int pre;
    int id;
    int w;
};
edge E[N*N];
int head[N],tot;
//int soldier[N][N];//第一种方法所需的的邻接矩阵
int low[N],dfn[N],ts,top,st[N],ins[N];
int minn;

void init()
{
    cls(head,-1);
    tot=0;
    //CLR(soldier,INF);
    cls(low,0);
    cls(dfn,0);
    ts=top=0;
    cls(ins,0);
    minn=1e8;
}
inline void add(int s,int t,int w,int id)
{
    E[tot].to=t;
    E[tot].id=id;
    E[tot].w=w;
    E[tot].pre=head[s];
    head[s]=tot++;
}
void tarjan(int u,int id)
{
    low[u]=dfn[u]=++ts;
    ins[u]=1;
    st[top++]=u;
    int v;
    for (int i=head[u]; ~i; i=E[i].pre)
    {
        v=E[i].to;
        if(id==E[i].id)
            continue;
        if(!dfn[v])
        {
            tarjan(v,E[i].id);
            low[u]=min<int>(low[v],low[u]);
            if(low[v]>dfn[u])
            {
                //枚举每一个桥,找到最小的桥
                int need=E[i].w;
                if(need<minn)
                    minn=need;

            }
        }
        else if(ins[v])
            low[u]=min<int>(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        do
        {
            v=st[--top];
            ins[v]=0;
        }
        while (u!=v);
    }
}


int main()
{
    while(scanf("%d%d",&n,&m)&&n+m)
    {
        init();

        //tot = 1;
        for (int i = 1; i <= m; i++)
        {
            int x, y,z;
            scanf("%d%d%d", &x, &y,&z);
            add(x, y,z,i), add(y,x,z,i);
        }
        int k = 0;
        minn = 1e8;
        for (int i = 1; i <= n; i++)
            if (!dfn[i]) tarjan(i, -1),k++;//连通块数量
        if(k > 1)
            minn = 0;
        else if(minn == 0)
            minn = 1;
        else if(minn == 1e8)
            minn = -1;
        printf("%d\n",minn);

    }
    return 0;
}
更新代码

 

转载于:https://www.cnblogs.com/DWVictor/p/11332100.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于 HDU4992 所有原根的问题,我可以给出以下解答: 1. 首先,我们需要知道什么是原根。原根是指模 n 意义下,所有与 n 互质的数 a 的最小正整数 k 次幂对 n 取模的值覆盖了所有与 n 互质的数。简单来说,就是如果一个数 a 是模 n 意义下的原根,那么 a 的任何正整数次幂对 n 取模的值都不相同,且能覆盖所有与 n 互质的数。 2. 为了模 n 意义下的所有原根,我们需要先出与 n 互质的数的个数 phi(n)。phi(n) 可以使用欧拉函数出。 3. 然后,我们需要枚举模 n 意义下的所有数,判断它是否是原根。具体来说,对于每个 a,我们需要判断 a 的每个小于 phi(n) 的正整数次幂对 n 取模的值是否都不相同,且能覆盖所有与 n 互质的数。如果是,那么 a 就是模 n 意义下的原根。 4. 代码实现可以参考以下 Java 代码: ``` import java.util.*; public class Main { static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } static int phi(int n) { int res = n; for (int i = 2; i * i <= n; i++) { if (n % i == 0) { res = res / i * (i - 1); while (n % i == 0) { n /= i; } } } if (n > 1) { res = res / n * (n - 1); } return res; } static int pow(int a, int b, int mod) { int res = 1; while (b > 0) { if ((b & 1) != 0) { res = res * a % mod; } a = a * a % mod; b >>= 1; } return res; } static boolean check(int a, int n, int phi) { for (int i = 1, j = pow(a, i, n); i <= phi; i++, j = j * a % n) { if (j == 1) { return false; } } return true; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { int n = scanner.nextInt(); int phi = phi(n); List<Integer> ans = new ArrayList<>(); for (int i = 1; i < n; i++) { if (gcd(i, n) == 1 && check(i, n, phi)) { ans.add(i); } } Collections.sort(ans); for (int x : ans) { System.out.print(x + " "); } System.out.println(); } } } ``` 其中,gcd 函数用于最大公约数,phi 函数用于欧拉函数,pow 函数用于快速幂模,check 函数用于判断一个数是否是原根。在主函数中,我们依次读入每个 n,出 phi(n),然后枚举模 n 意义下的所有数,判断它是否是原根,将所有原根存入一个 List 中,最后排序输出即可。 希望我的回答能够帮到你,如果你有任何问题,欢迎随时提出。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值