0819 T3 城堡

原题链接

题目分析

首先求出从1号点出发的最短路.
我们构造一个最短路图如下.
对原图中的边(u, v, w),如果d[u] = d[v]+w,那么在最短路图中加入v->w的有向边;
如果d[v] = d[u]+w,那么在最短路图中加入u->w的有向边。
由于所有边权为正,因此有一个性质是,按上面得到的的最短路图一定是一个DAG(有向无环图)。反证法易证。
而要满足题目中的条件,我们只能从最短路图中选边,而且必须满足在得到的有向树上存在从1到每个点的路径。
那么必要条件是:对于除了1之外的每个节点,都必须选择一条它的入边,这样选出N-1条边。
可以证明这也是充分条件:这样一定能得到一棵满足要求的树。
因此答案很简单,即为除1号点外,每个点入度的乘积。
复杂度为O(M log N)(最短路复杂度)

代码

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000;
const int M = 499500;
const int inf = 12345678;
const ll mod = (1LL<<31) - 1LL;

struct edge{//边表 
    int e, d;//该边重点,边权 
    edge* next;//下一条边 
} ed[M<<1|1], *v[N+1];//边的集合ed,v[i]表示i点发出的第一条边的地址 
ll c[N+1], ans;
int n, m, x, y, w, en, dist[N+1], q[N+1];
bool inq[N+1];
void add_edge(int s, int e, int d){//边表加边 
    ed[++en].next = v[s];
    v[s] = ed+en;
    ed[en].e = e;
    ed[en].d = d;
}
int inc(int &x) {//循环队列 
    if(x+1 == n) x = 0;
    else x++;
    return x;
}
void spfa(int now) {//单源最短路 
    int front = 0, tail = 0;
    for(int i = 1; i <= n; ++i) dist[i] = inf;//初始化 
    dist[now] = 0;
    inq[now] = true;//是否在队中 
    q[inc(tail)] = now;
    while(front != tail) {
        int now = q[inc(front)];
        inq[now] = false;
        for(edge* e = v[now]; e; e = e->next) {//遍历从now出发的所有边 
            if(dist[e->e] > dist[now]+e->d) {
                dist[e->e] = dist[now]+e->d;//松弛操作 
                if(!inq[e->e]) {
                    inq[e->e] = true;
                    q[inc(tail)] = e->e;//新点入队 
                }
            }
        }
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; ++i) {
        scanf("%d%d%d", &x, &y, &w);
        add_edge(x, y, w);
        add_edge(y, x, w);
    }
    spfa(1);//初始化 

    int front = 0, tail = 0;
    q[inc(tail)] = 1;//1号点开始遍历 
    inq[1] = true;
    c[1] = 1LL;//点i的入度为c[i] 
    while(front != tail) {
        int now = q[inc(front)];
        for(edge* e = v[now]; e; e = e->next) {//遍历该点能到的所有点 
            if(dist[e->e] == dist[now]+e->d) {//到该点的距离正好为最短路 
                ++c[e->e];//该点入度更新 
                if(c[e->e] >= mod) c[e->e] -= mod;//入度取模 
                if(!inq[e->e]) {//下一点进队 
                    q[inc(tail)] = e->e;
                    inq[e->e] = true;
                }
            }
        }
    }
    ans = 1LL;
    for(int i = 1; i <= n; ++i) {
        ans *= c[i];//所有入度乘积 
        if(ans >= mod) ans %= mod;
    }
    printf("%d\n", (int)ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值