【icpc网络赛大连赛区】Sparse Graph

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 2487    Accepted Submission(s): 386


Problem Description
In graph theory, the complement of a graph G is a graph H on the same vertices such that two distinct vertices of H are adjacent if and only if they are notadjacent in G

Now you are given an undirected graph G of N nodes and M bidirectional edges of unit length. Consider the complement of G, i.e., H. For a given vertex S on H, you are required to compute the shortest distances from S to all N1 other vertices.
 

Input
There are multiple test cases. The first line of input is an integer T(1T<35) denoting the number of test cases. For each test case, the first line contains two integers N(2N200000) and M(0M20000). The following M lines each contains two distinct integers u,v(1u,vN) denoting an edge. And S (1SN) is given on the last line.
 

Output
For each of T test cases, print a single line consisting of N1 space separated integers, denoting shortest distances of the remaining N1 vertices from S (if a vertex cannot be reached from S, output ``-1" (without quotes) instead) in ascending order of vertex number.
 

Sample Input
1 2 0 1
 

Sample Output
1
 

【题解】

补图:

在完全图的基础上,把我们在这题中输入的边给去掉。剩余的图就是输入的图的补图。然后可以理解为,原来出现过的边在补图中都不会出现了。


在补图上做最短路。(边权为单位长度)

这题需要分两种情况来做。

1.

m<n-1;

则我们输入的图肯定是不连通的图。因为少于n-1条边你没办法使一张图连通.

这个时候节点的数量可能达到20万。你不可能写34组的dijkstra..而且每组都20万。

然后我们就有特殊的技巧了。


这是一种情况。

可以看到边的数目小于n-1条。

然后我们考虑一下它的补图,因为有一个点肯定是和所有的其他点都没有联通的。在这里就是4号节点。那么我把它和其他的点都连上,如下图


但是注意我还没有把其他边连上。这还不是它的补图。

但我们却由此得到一个思路。那就是所有的点都可以经过4号节点再多走一步到达其他任意一个节点。也就是说花费最多是2.

那有哪一些点到其他点的花费是1呢?

就是那些一开始在原图上没有和起点直接相连的点


假设起点是3.

那么完整的补图会是这样的


可以看到,原图上未和3号节点直接相连的节点与3的距离都是1.

这是因为。如果3号节点一开始没有和任何节点相连。那么补图上。

3号节点是会和其他所有节点都连上一条边的,也就是距离为1

但是如果有节点和3号节点相连。那么在补图上就不能存在一条边由3号节点直接指向那个节点了(即不存在距离为1的代价直接从3号节点到达那个点)

而我们刚才的分析已经得到最大的代价为2,即把那个一开始谁都没连过的节点作为中间节点走两步到达目标。

而m < n-1保证了有那样一个一开始谁都没连过的节点。

2.

m>=n-1;

这下你可说不准图有没有联通了。那该怎么办呢?

注意到n<=m+1.而m最大为20000;实际上我们已经可以用dijkstra来解决这个问题了。

这是它的能力范围之内的事情了。

从起点开始进行dijkstra算法就可以了。

详细的过程看代码注释。

题目的数据显示在n-1<=m的时候n最大值没有超过6000;

【代码】

#include <cstdio>
#include <cstring>

int T,n,m,bian[25000][2],s,dis[5500+100],w[6000][6000];
bool linjin[200001+100],haved[5500+100];

const int MAX_N = 2100000000/3;//这是给dis数组一开始赋值用的玩意,就是一个很大的值。

void input_data()
{
    scanf("%d%d",&n,&m);//输入n个点m条边。
    for (int i = 1;i <= m;i++)//输入m条边。
        scanf("%d%d",&bian[i][0],&bian[i][1]);
    scanf("%d",&s);//输入起点。
}

void get_ans()
{
    if (m < n-1) //如果这张原图是不可能联通的。
    {
        for (int i = 1;i <= m;i++)//看下有没有和起点相连的点。
        {
            if (bian[i][0] == s)
                linjin[bian[i][1]] = true;//如果和起点相连那么从起点到它的最短距离就只能是2
            if (bian[i][1] == s)
                linjin[bian[i][0]] = true;
        }
        bool flag = false;//这个flag用来判断要不要输出空格。
        for (int i = 1;i <= n;i++)
            if (i!=s)//如果不是起点,就要输出一些东西了
            {
                if (flag)
                    printf(" ");
                if (linjin[i])
                    printf("2");//如果这个点和起点相连。则最短的距离只能是2
                    else
                        printf("1");//如果不和起点相连。那么就可以存在一条边直接从起点指向该点。
                flag = true;//判断已经输出过一个答案。下次要输出空格了。
            }
        printf("
");//输出一个空行
    }
    else //n <= m-1的情况
    {//在这个情况下n的最大值不超过5500;(数据显示)
        for (int i = 1;i <= n;i++)
            for (int j = 1;j <= n;j++)//一开始先构造出一个完全图。
                if (i!=j)
                    w[i][j] = 1;//任意两点之间的距离除了两个相同的点的距离外都为1.
                        else
                            w[i][j] = 0;
        for (int i = 1;i <= m;i++) //然后输入的边是原图的边,它在补图里面就是不存在的了。
        {
            w[bian[i][0]][bian[i][1]] = MAX_N;//置为一个很大的数就可以了。
            w[bian[i][1]][bian[i][0]] = MAX_N;
        }
        for (int i = 1;i <= n;i++)//先置起点到所有的点的距离为起点到该点的距离。
        {
            dis[i] = w[s][i];
            haved[i] = false;//置该点还不是最短距离。
        }
        haved[s] = true;//起点到起点已经是最短距离了。
        for (int i = 1;i <= n;i++)
        {
            int k = -1,min = MAX_N;
            for (int j = 1;j <= n;j++)//找到离起点最近的点。(且要求它之前没有别用来更新过。)
                if (!haved[j] && dis[j] < min)
                {
                    min = dis[j];
                    k = j;
                }
            if (k == -1)//如果没找到就说明全部找完直接结束
                break;
            haved[k] = true;//否则就标记这个节点已经被用来更新过了。下次就不会再用到它了。
            for (int j = 1;j <= n;j++) //如果之前没有用过这个点更新,且需要更新
                if (!haved[j] && (dis[j] > dis[k] + w[k][j]))
                    dis[j] = dis[k] + w[k][j];//则更新
        }
        bool flag = false;//flag依旧是用来控制输出的。
        for (int i = 1;i <= n;i++)
            if (i!=s)
            {
                if (flag) printf(" ");//如果之前已经输出过一个了,则需要输出一个空格。
                if (dis[i] >= MAX_N) //如果是一个“无穷大”的值就直接输出-1,否则输出最短路。
                    printf("-1");
                        else
                            printf("%d",dis[i]);
                flag = true;//标记已经输出过答案。
            }
        printf("
");
    }
}

int main()
{
//freopen("F:\rush.txt","r",stdin);
//freopen("F:\rush_out.txt","w",stdout);
    scanf("%d",&T);//输入T组数据。
    while (T--)
    {
        memset(linjin,0,sizeof(linjin));
        input_data();
        get_ans();
    }
    return 0;
}



原文地址:https://www.cnblogs.com/AWCXV/p/7632268.html