NOIP2017 Day1_T3 逛公园

题目大意:

让你求从1到n的路径中,长度和最短路径不超过k有多少条?

Warning:CCF老爷机严重卡常。

题解:

考场时想到了缩环,然并卵,就是没想到拆点按Turpo序dp。

70分的做法,先跑SPFA求最短路,然后再求最短路的条数,如果此时有0环,那么队列会无限大,可以超过10^6就退出,之后再强行拆点连边SPFA跑方案数。

发现如果原图中没有0环的话,拆点后的图是没有环的,所以强行Turpo一发,按顺序统计方案数即可。

这也启发出了判0环的另一种方法,即直接拆点Turpo,如果终点不能Turpo出来,则有0环在路径上。

千万不要以为这样就能过了,这个得跑6s。

卡常1:
正反两遍SPFA求出每个点到起点终点的距离,如果有拆点后的f[i][j]表示第i个点,现在比最短路的多了j的长度,若 diss>i+jdiss>t+disi>t>k ,那么f[i][j]这个点就肯定无用了。

卡常2:
直接在最短路图上做Turpo,之后枚举层数,再按Turpo序枚举点。
这样做Turpo时快了许多,且之后算答案时如果一个点得方案数为0,可以直接跳过它。

这两个优化加上后估计老爷机跑2s。

Code:

#include<cstdio>
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 1e5 + 5, M = 2e5 + 5;

int T, n, m, k, mo, x, y, z, bzans;
int next[M], to[M], cost[M], final[N], tot;
int bz[N], bx[N], d[M * 100];
int r[N], d2[N], f[N][51], dis[N], dis2[N];
int next2[M], to2[M], final2[N], tot2;
int b[M][3];

void read(int &x) {
    char c = ' '; for(; c < '0' || c > '9'; c = getchar());
    x = 0; for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - 48;
}

void SPFA2(int *dis) {
    fo(i, 1, n) dis[i] = 2e9;
    d[1] = n; bz[n] = 1; dis[n] = 0;
    for(int st = 1, en = 1; st <= en; st ++) {
        int x = d[st];
        for(int i = final[x]; i; i = next[i]) {
            int y = to[i], z = cost[i];
            if(dis[x] + z < dis[y]) {
                dis[y] = dis[x] + z;
                if(!bz[y]) bz[y] = 1, d[++ en] = y;
            }
        }
        bz[x] = 0;
    }
}

void SPFA() {
    memset(dis, 127, sizeof dis);
    d[1] = 1; bz[1] = 1; dis[1] = 0;
    for(int st = 1, en = 1; st <= en; st ++) {
        int x = d[st];
        for(int i = final[x]; i; i = next[i]) {
            int y = to[i], z = cost[i];
            if(dis[x] + z < dis[y]) {
                dis[y] = dis[x] + z;
                if(!bz[y]) bz[y] = 1, d[++ en] = y;
            }
        }
        bz[x] = 0;
    }
}

void Link() {
    fo(i, 1, tot2) next2[i] = 0;
    fo(i, 1, n) final2[i] = 0;
    tot2 = 0;
    fo(x, 1, n) for(int i = final[x]; i; i = next[i]) {
        int y = to[i];
        if(dis[x] + cost[i] == dis[y])
            next2[++ tot2] = final2[x], to2[tot2] = y, final2[x] = tot2;
    }
}

int d0;

void Turpo() {
    fo(i, 1, n) r[i] = 0;
    fo(i, 1, n) for(int j = final2[i]; j; j = next2[j]) r[to2[j]] ++;
    int st = 1, en = 0;
    fo(i, 1, n) if(!r[i]) d[++ en] = i;
    for(; st <= en; st ++) {
        int x = d[st];
        bz[x] = 1;
        for(int i = final2[x]; i; i = next2[i]) 
            if(!(-- r[to2[i]])) d[++ en] = to2[i];
    }
    bzans = 1;
    fo(i, 1, n) if(dis[i] < 1e9 && dis2[i] < 1e9 && !bz[i])
        bzans = 0;
    memset(bz, 0, sizeof bz);
    d0 = en;
}

int main() {
    freopen("park.in", "r", stdin);
    freopen("park.out", "w", stdout);
    for(scanf("%d", &T); T; T --) {
        fo(i, 1, n) final[i] = 0;
        fo(i, 1, tot) next[i] = 0;
        tot = 0;
        scanf("%d %d %d %d", &n, &m, &k, &mo);
        fo(i, 1, m) {
            read(x); read(y); read(z);
            b[i][0] = x; b[i][1] = y; b[i][2] = z;
            next[++ tot] = final[y], to[tot] = x, cost[tot] = z, final[y] = tot;
        }
        SPFA2(dis2);
        fo(i, 1, n) final[i] = 0;
        fo(i, 1, tot) next[i] = 0;
        tot = 0;
        fo(i, 1, m) {
            x = b[i][0], y = b[i][1], z = b[i][2];
            next[++ tot] = final[x], to[tot] = y, cost[tot] = z, final[x] = tot;
        }
        SPFA();
        Link();
        Turpo();
        if(!bzans) {
            printf("-1\n");
            continue;
        }
        memset(f, 0, sizeof f);
        f[1][0] = 1;
        fo(j, 0, k) fo(i, 1, d0) {
            int x = d[i];
            if(!f[x][j]) continue;
            if(j + dis2[x] + dis[x] - dis[n] > k) continue;
            for(int p = final[x]; p; p = next[p]) {
                int y = to[p], k2 = j + dis[x] + cost[p] - dis[y];
                if(k2 > k) continue;
                f[y][k2] = (f[y][k2] + f[x][j]) % mo;
            }
        }
        int ans = 0;
        fo(j, 0, k) ans = (ans + f[n][j]) % mo;
        printf("%d\n", ans);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值