zoukankan      html  css  js  c++  java
  • 【题解】NOIP2017 逛公园

      题目链接

      这题的状态转移方程其实相对而言没有那么难,但判0环过程真的挺繁琐。

      

      这个题可以从数据范围的k <= 50 入手,由于我们最多走50及以下的无用路,所以可以考虑去利用不多的k来进行一些操作,这便可以大大减少枚举状态时要使用的复杂度。

      我们可以想到把k作为一维状态进行dp。状态设置也比较清晰,dp[j][i]一维表示当前在点i,另一维表示从出发点到该点经过的路程为最短路加上j。然后从一个点转到下一个点时的状态转移方程就可以列出来了。设我们要从u点到v点,他们之间连边的大小为w,当前状态为dp[x][u],规定dis[i]为从起点到i的最短路。那么到达v点时走的多余的路则为dis[u] + x + w - dis[v],所以我们就可以把状态像这样转移:

        dp[x][u] = ∑dp[dis[u] + x + w - dis[v]][v] ;

      也就是把所有可以到v点的u点的状态都转移到v点。

      至于判0环的部分,就较为复杂了,考虑将所有0边单独建一次图,然后在这张图里跑拓扑排序,所有不能进入队列里的点便是0环上或0环可以通过0边到达的点。

      如果0环可以通过0边到达某个点,该点若可以作为答案路径上的一点,该路径不一定会成为无限可走的路径,只要路径不通过0环即可。所以我们要排除0环可以通向的点,建反向0图,再次拓扑排序。而该点成为无限路径上一点的情况,则会被0环排除,所以我们不需要担心有情况缺失。

      除了0环之外,还要看环上的点是否属于可行路径,我们求出起点到它(负环上一点)的最短路径,加上终点到它的最短路径,若在可行范围内,则直接输出-1。

      丑陋的代码如下:

      

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #define maxn 200005
    
    int head[maxn],nex[maxn],to[maxn],edge[maxn],vis[maxn],n,m,k,p,T,tot = 0,last[maxn],in[maxn],cnt,tag[maxn],vis1[maxn],vis2[maxn],tot1 = 0,to1[maxn],nex1[maxn],head1[maxn];
    int from[maxn],go[maxn],ed[maxn],num[maxn];
    long long dp[60][100005],dis[maxn],dis1[maxn];
    std::queue <int > q;
    struct dot{
        int whi;
    }d[maxn];
    
    void add(int x,int y,int z) {
        to[++tot] = y; edge[tot] = z; nex[tot] = head[x]; head[x] = tot;
    }
    void add1(int x,int y) {
        to1[++tot1] = y; nex1[tot1] = head1[x]; head1[x] = tot1;
    }
    void turn() {
        tot = 0;
        memset(head,0,sizeof(head));
        for (int i = 1;i <= m;i++) {
            std::swap(from[i],go[i]);
            add(from[i],go[i],ed[i]);
        }
    }
    bool cmp(dot a1,dot a2) {
        if (dis[a1.whi] == dis[a2.whi]) return num[a1.whi] < num[a2.whi];
        else 
        return dis[a1.whi] < dis[a2.whi];
    }
    void spfa(int u) {
        for (int i = 1;i <= n;i++) dis[i] = 0x3f3f3f3f,vis[i] = 0,d[i].whi = i,num[i] = 0;
        dis[u] = 0;
        q.push(u); vis[u] = 1;
        while (!q.empty()) {
            int x = q.front();
            q.pop(); vis[x] = 0;
            for (int i = head[x];i;i = nex[i]) {
                int y = to[i];
                if (dis[y] > dis[x] + edge[i]) {
                    dis[y] = dis[x] + edge[i];
                    last[y] = x;
                    if (!vis[y]) q.push(y),vis[y] = 1;
                }
            }
        }
    }
    void top_sort1() {
        tot1 = 0;
        memset(in,0,sizeof(in));
        memset(head1,0,sizeof(head1));
        for (int i = 1;i <= n;i++)
            for (int j = head[i];j;j = nex[j])
                if (!edge[j]) tag[i] = 1,add1(i,to[j]),in[to[j]]++;
        for (int i = 1;i <= n;i++) {
            if (tag[i] && !in[i]) q.push(i),num[i] = ++cnt;
        }
        while (!q.empty()) {
            int x = q.front(); q.pop(); vis1[x] = 1;
            for (int i = head1[x];i;i = nex1[i]) {
                int y = to1[i];
                in[y]--;
                if (!in[y]) q.push(y),num[y] = ++cnt;
            }
        }
    }
    void top_sort2() {
        tot1 = 0;
        memset(head1,0,sizeof(head1));
        memset(in,0,sizeof(in));
        for (int i = 1;i <= n;i++)
            for (int j = head[i];j;j = nex[j]) {
                int y = to[j];
                if (!edge[j]) {
                    in[i]++;
                    add1(y,i);
                }
            }
        for (int i = 1;i <= n;i++) if (tag[i] && !in[i]) q.push(i);
        while (!q.empty()) {
            int x = q.front(); vis2[x] = 1; q.pop();
            for (int i = head1[x];i;i = nex1[i]) {
                int y = to1[i];
                in[y]--;
                if (!in[y]) q.push(y);
            }
        }
    }
    int main() {
        scanf("%d",&T);
        while (T--) {
            long long ans = 0,yes = 1;
            memset(head,0,sizeof(head));
            tot = 0; memset(dp,0,sizeof(dp));
            memset(vis1,0,sizeof(vis1));
            memset(vis2,0,sizeof(vis2));
            scanf("%d%d%d%d",&n,&m,&k,&p);
            for (int i = 1;i <= m;i++) {
                int x,y,z;
                scanf("%d%d%d",&x,&y,&z);
                from[i] = x; go[i] = y; ed[i] = z;
                add(x,y,z);
            }
            spfa(1);
            for (int i = 1;i <= n;i++) dis1[i] = dis[i];
            turn();
            spfa(n);//跑两次spfa得知起点与终点的最短路情况
            turn();
            for (int i = 1;i <= n;i++) std::swap(dis1[i],dis[i]);
            top_sort1();
            top_sort2();//两次拓扑,确定零环
            for (int i = 1;i <= n;i++) 
                if (tag[i] && !vis1[i] && !vis2[i] && dis[i] + dis1[i] <= dis[n] + k) {yes = 0;break;}
            if (!yes) {printf("-1
    ");continue;} //若一点在零环上且通过它的起点向终点的最短路径在可行范围内,则直接输出-1
            std::sort(d + 1,d + n + 1,cmp);
            dp[0][1] = 1;
            for (int i = 0;i <= k;i++) {
                for (int j = 1;j <= n;j++) {
                    int u = d[j].whi;
                    if (!dp[i][u]) continue;
                    for (int l = head[u];l;l = nex[l]) {
                        int v = to[l];
                        if (dis[u] + i + edge[l] - dis[v] > k || dis[v] == 0x3f3f3f3f) continue;
                        dp[dis[u] + i + edge[l] - dis[v]][v] += dp[i][u];
                        dp[dis[u] + i + edge[l] - dis[v]][v] %= p;
                    }//状态转移过程
                }
                ans += dp[i][n];
                ans %= p;
            }
            printf("%lld
    ",ans);
        }
    }
  • 相关阅读:
    git常用命令大全
    谷粒商城遇到的问题
    谷粒商城Seata(四十一)
    通过VMware Horizon Client访问虚拟机
    Qt 让窗口(或控件)居中
    QT 设置QDockWidget的初始大小
    Qt QDockWidget小结
    Qt QDockWidget停靠窗相关的信号
    Qt 基于Qt的词典开发系列--无边框窗口的缩放与拖动
    Qt 创建停靠悬浮窗口 QDockWidget
  • 原文地址:https://www.cnblogs.com/Illusions/p/13958777.html
Copyright © 2011-2022 走看看