图论专题 - 解题报告 - D

问题大意就是给出多次询问,问两点间的最短路,首先知道每次都跑一遍最短路肯定是不现实的,总共1e5次询问你来?但是我们如果能预处理出一些两点间的最短路径关系就可以完成这样的查找了对吧。
如果用上最小生成树呢?树覆盖了全部点,我们要最短路,要么在树上,要么过树外的一条边。
树外这些冗余的点边关系就派上了用场,我把那些冗余边的端点称为坏点(badnode),两点(u,v)间的树上距离(u→→ v)可以用树上倍增lca求,而过树外的边就必定过一个坏点(u →→badnode →→ v),因为坏点的数量最多不过60个,其实每条边就取一个点的话甚至不超过30个点,所以用dijkstra先处理所有坏点到全部点的距离是可取的。
最后的答案就是枚举badnode,分别求过这个点的的u点v点的距离,与树上距离比较,找到最短的一条路。
整体看下来复杂度绝对没问题,顶多是常数可能略大,不过出题人会留出一定的时间够你跑垃圾步骤。
上代码吧,写的略丑,但是好歹每个部分还是叫得上名字的板子,大概还算清晰(?):

#include
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define maxn 400005
#define maxm 55
#define hrdg 1000000007
#define zh 16711680
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
using namespace std;

int n, m, u, v, d, Q, query[maxn];
struct E {int to, nex, d;} e[maxn];				//全图的边
int h[maxn<<1], et;
struct node{int x, y, d;} a[maxn];
void  add_e (int u, int v, int d) {e[++et] = {v, h[u], d}; h[u] = et;}			
bool cmp (node x, node y) {return x.d < y.d;}
struct Edge {int to, nex, dis;} edge[maxn<<1];			//最小生成树的边
int head[maxn<<1], tot;
void add_edge(int u, int v, int d) {edge[++tot] = {v, head[u], d}; head[u] = tot;}
int root, depth[maxn], f[maxn][21];			//倍增所需
ll deep[maxn], dis[65][maxn];
int badcnt, badnode[65];
bool vis[maxn], badvis[maxn];
ll ans, sum = 0;

inline ll read(){				
    char c=getchar();long long x=0,f=1;
    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;
}

int fa[maxn];	
inline int setfind(int x){ return x == fa[x] ? x : fa[x] = setfind(fa[x]);}
inline void setunion(int x,int y){ x = setfind(fa[x]); y = setfind(fa[y]); fa[x] = y;}
inline bool setcheck(int x,int y){ x = setfind(x); y = setfind(y); return x == y;}

void kruskal()			//最小生成树
{
    for (int i = 1; i <= n; i++) fa[i] = i;
    for (int i = 1; i <= m; i++)
    {
        add_e(a[i].x, a[i].y, a[i].d);			//循环同步跑出全图的边
        add_e(a[i].y, a[i].x, a[i].d);
        if(setcheck(a[i].x, a[i].y))				//有冗余的边,就有badnode
        {
            if (!badvis[a[i].x] && !badvis[a[i].y])			//每条边标记一个点就够了
            {
                badnode[++badcnt] = a[i].x;
                badvis[a[i].x] = 1;
            }
            continue;
        }
        setunion(a[i].x, a[i].y);				
        add_edge(a[i].x, a[i].y, a[i].d);					//跑出最小生成树的边
        add_edge(a[i].y, a[i].x, a[i].d);		
    }
}

void dfs(int now)				//最小生成树递归建树,一路处理深度(depth)和到根节点的距离(deep)
{
    vis[now] = true;
    for (int i = head[now]; i; i = edge[i].nex)				//最小生成树上搜
    {
        int to = edge[i].to;
        if (vis[to])
            continue;
        f[to][0] = now;
        depth[to] = depth[now] + 1;
        deep[to] = deep[now] + 1LL * edge[i].dis;
        dfs(to);
    }
}

void dij(int start_index)			//start_index是起始点的序号
{
    priority_queue > q;
    FOR(i, 1, n) vis[i] = 0;
    int start = badnode[start_index];					//从start开始
    for (int i = 1; i <= n; i++)
        dis[start_index][i] = llinf;
    dis[start_index][start] = 0LL;				//以上都是初始化
    q.push(make_pair(0LL, start));		  	  //先推入优先队列
    while (!q.empty())
    {
        int now = q.top().second; q.pop();
        if (vis[now]) continue;
        vis[now] = 1;
        for (int i = h[now]; i; i = e[i].nex)				//全图跑最短路
        {
            int to = e[i].to;
            if (dis[start_index][to] > dis[start_index][now] + 1LL * e[i].d)
            {
                dis[start_index][to] = dis[start_index][now] + 1LL * e[i].d;
                q.push(make_pair(-dis[start_index][to], to));
            }
        }
    }
}

int lca(int x, int y)					//倍增求公共祖先
{
    if(depth[x] < depth[y])
        swap(x, y);
    for(int i=20; i>=0; i--)
        if(depth[f[x][i]] >= depth[y])
            x = f[x][i];
    if(x == y)
        return x;
    for(int i=20; i>=0; i--)
        if(f[x][i] != f[y][i])
        {
            x = f[x][i];
            y = f[y][i];
        }
    return f[x][0];
}

ll getdis(int x, int y) {return deep[x] + deep[y] - 2 * deep[lca(x, y)];}			//用公共祖先找到树上两点间距

int main()
{
    n = read(); m = read();
    for (int i = 1; i <= m; i++)
    {
        u = read(); v = read(); d = read();
        a[i] = {u, v, d};				//先初始边记录
    }
    sort(a + 1, a + 1 + m, cmp);		//排序,kruskal
    kruskal();
    root = a[1].x;				//让我们以边最短的边一个端点作为根,不过后来想想直接1就完事了
    depth[root] = 1;
    deep[root] = 0;
    f[root][0] = root;
    dfs(root);
    for (int i = 1; i <= 20; i++)
        for (int j = 1; j <= n; j++)
            f[j][i] = f[f[j][i - 1]][i - 1];					//倍增初始化
    for (int i = 1; i <= badcnt; i++)
        dij(i);
    Q = read(); query[0] = 1;					//开始询问
    for (int i = 1; i <= Q; i++)
    {
        query[i] = read();
        u = query[i - 1]; v = query[i];
        ans = getdis(u, v);				
        for (int j = 1; j <= badcnt; j++)
            ans = min (ans, dis[j][u] + dis[j][v]);				//循环比较
        sum += ans;				//统计
    }
    cout << sum;
    return 0;
}

你可能感兴趣的:(解题报告)