NOIP 2017 Senior 3

来源:互联网 发布:主力如何拉升股价 知乎 编辑:程序博客网 时间:2024/05/21 03:17

传送门(JZOJ)

这个题,很明显要用动态规划求解。但是这个图不是 DAG,还有零环,怎么做呢?我们逐一突破。

1.对于 10% 的数据
首先你得先会最短路吧。由于是稀疏图,使用 SPFA 即可。这样我们就知道了从 1 到 n 的最短距离。猜想,对于 10% 的这么小的数据,爆搜应该没有问题吧?由于可能走环,因此我们只能把已走过的路程作为搜索的出口。理论上能拿 10 分,由于官方数据相当水,这么做能得到 20 分。

2.对于 30% 的数据(k = 0,不存在零边)
当 k = 0 时,答案会是 1 吗?不一定,可能最短路有多条,但是最短路一定不会存在环。这样,我们就可以套用一个经典做法:构造最短路图,即所有边都是最短路上的一条边。方法是:若 disi+ei,j=disj,则该边保留。然后就是一个 DAG 上的 DP 了。拓扑排序后,从 1 开始递推。设 f[i] 为走到 i 的路径条数,则边界条件为 f[1] = 1。
注意,由于是从起点到终点方向的递推,因此一定要拓扑排序才不会有后效性!(考试时我居然忘了)

3.对于 70% 的数据(不存在零边)
很自然会想到设 f[i][j],表示走到点 j 且当前路径总长度比从 1 到 j 的最短路的长度大 i 的方案数。但是怎么递推呢?边界条件显然是 f[0][1] = 1。然后考虑最外层循环到底是谁。如果最外层循环是结点,k 的循环放内层的话,是有毛病的。因为可能会从一个点出发,走到一个已经计算过的点。由于最外层循环是结点,那个已经计算过的点显然不会再被访问,就造成了后效性。若最外层循环为 k,里面放结点就没有毛病了。可以知道,若要从 f[a][b] -> f[c][d],那么 c 一定是大于 a 的(不存在零边!),从而说明 f[c][d] 是没有作为起点更新过其它答案的,这样就不会有后效性了。
以上递推要遍历每一条边。
最终答案为 ki=0f[i][n]

4.对于 100% 的数据
如果有零边,我们考虑如何找到零环。首先我们仍然构造出一个最短路径图。注意如果有满足要求的零环存在(要输出 -1 时),则零环上的任意一点都应该满足 dis[i] + disT[i] <= dis[n] + k(disT 表示从 n 到任意一点的最短路)。这样,若拓扑排序后还有点没有排到,且有任意一点满足以上条件,就输出 -1,否则对已经排到的点进行 3 中的DP。可以知道,这个 DP 一定不会从零环上的点出发(因为没有把它们加入拓扑序),所以即使存在一个零环但是正确答案不是 -1 时也能得到正确答案。(然而官方数据并不卡这一点,如果你直接判断是否有点没有被拓扑排序到就输出 -1 也是可以过官方数据的,但事实上这是错的)

参考数据

17 8 1 1000001 2 12 3 13 4 04 3 01 5 11 6 15 7 06 7 0
2

而不是 -1。

参考代码(渣,真的仅供参考)

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>#include <bitset>using std::cin;using std::cout;using std::endl;typedef int INT;INT readIn(){    INT a = 0;    bool minus = false;    char ch = getchar();    while (!(ch == '-' || (ch >= '0' && ch <= '9'))) ch = getchar();    if (ch == '-')    {        minus = true;        ch = getchar();    }    while (ch >= '0' && ch <= '9')    {        a = a * 10 + (ch - '0');        ch = getchar();    }    if (minus) a = -a;    return a;}void printOut(INT x){    char buffer[20];    INT length = 0;    bool minus = x < 0;    if (minus) x = -x;    do    {        buffer[length++] = x % 10 + '0';        x /= 10;    } while (x);    if (minus) buffer[length++] = '-';    do    {        putchar(buffer[--length]);    } while (length);    putchar('\n');}const INT INF = (~(INT(1) << (sizeof(INT) * 8 - 1)));const INT maxk = 55;const INT maxn = 100005;const INT maxm = 200005;INT n, m, K, mod;struct Edge{    INT to;    INT cost;    INT next;} edges[maxm], edgesT[maxm];INT head[maxn], headT[maxm]; //G and GTINT count_;void addEdge(INT from, INT to, INT cost){    count_++;    edges[count_].to = to;    edges[count_].cost = cost;    edges[count_].next = head[from];    head[from] = count_;    edgesT[count_].to = from;    edgesT[count_].cost = cost;    edgesT[count_].next = headT[to];    headT[to] = count_;}INT d; //no useINT minC; //for cheat//a mess, so comment is needed//shortest path, dis means 1 to others, disT means n to othersnamespace SP{    INT dis[maxn];    INT disT[maxn];    bool vis[maxn];    struct Queue    {        INT c[maxn];        INT head, tail;        Queue() : head(), tail() {}        void clear()        {            head = tail = 0;        }        bool empty()        {            return head == tail;        }        void push(INT x)        {            c[tail] = x;            tail = (tail + 1) % maxn;        }        INT front()        {            return c[head];        }        void pop()        {            head = (head + 1) % maxn;        }    } q;    INT s;    //bad code    void SPFA()    {        memset(vis, 0, sizeof(vis));        q.clear();        q.push(1);        dis[1] = 0;        vis[1] = true;        while (!q.empty())        {            INT from = q.front();            q.pop();            vis[from] = false;            for (int i = head[from]; i; i = edges[i].next)            {                Edge& e = edges[i];                INT to = e.to;                INT c = e.cost;                if (dis[from] + c < dis[to])                {                    dis[to] = dis[from] + c;                    if (!vis[to])                    {                        q.push(to);                        vis[to] = true;                    }                }            }        }        memset(vis, 0, sizeof(vis));        q.clear();        q.push(n);        disT[n] = 0;        vis[n] = true;        while (!q.empty())        {            INT from = q.front();            q.pop();            vis[from] = false;            for (int i = headT[from]; i; i = edgesT[i].next)            {                Edge& e = edgesT[i];                INT to = e.to;                INT c = e.cost;                if (disT[from] + c < disT[to])                {                    disT[to] = disT[from] + c;                    if (!vis[to])                    {                        q.push(to);                        vis[to] = true;                    }                }            }        }    }    INT goSP()    {        memset(dis, 0x3f, sizeof(dis));        memset(disT, 0x3f, sizeof(disT));        SPFA();        return dis[n];    }};#define RunInstance(x) delete new xstruct work{    INT topo[maxn];    INT inDegree[maxn];    bool vis[maxn];    INT found;    //if a vertex on a "0 ring" is valid (dis[i] + disT[i] <= dis[1] + K), print -1    //use topo sort to find vertexes on "0 ring"    void BFS() //topo sort    {        using namespace SP;        for (int i = 1; i <= n; i++)        {            for (int j = head[i]; j; j = edges[j].next)            {                Edge& e = edges[j];                INT to = e.to;                INT cost = e.cost;                if (dis[i] + cost == dis[to])                    inDegree[to]++;            }        }        q.clear();        for (int i = n; i >= 1; i--)            if (!inDegree[i])                q.push(i);        while (!q.empty())        {            INT from = q.front();            vis[from] = true;            topo[++topo[0]] = from;            found++;            q.pop();            for (int j = head[from]; j; j = edges[j].next)            {                Edge& e = edges[j];                INT to = e.to;                INT cost = e.cost;                if (dis[from] + cost == dis[to])                {                    inDegree[to]--;                    if (!inDegree[to])                        q.push(to);                }            }        }        if (found != n)            for (int i = 1; i <= n; i++)                if (!vis[i] && dis[i] + disT[i] <= dis[n] + K)                {                    found = -1;                    return;                }    }    INT f[maxk][maxn];    work() : f(), vis(), found(), inDegree()    {        using namespace SP;        topo[0] = 0;        BFS();        if (found == -1)        {            printOut(-1);            return;        }        //otherwise, this is a DAG        f[0][1] = 1;        for (int k = 0; k <= K; k++)        {            for (int i = 1; i <= topo[0]; i++)            {                INT node = topo[i];                for (int j = head[node]; j; j = edges[j].next)                {                    Edge& e = edges[j];                    INT to = e.to;                    INT c = e.cost;                    INT s2 = k + dis[node] + c - dis[to];                    if (s2 <= K)                    {                        f[s2][to] = (f[s2][to] + f[k][node]) % mod;                    }                }            }        }        INT ans = 0;        for (int i = 0; i <= K; i++)            ans = (ans + f[i][n]) % mod;        printOut(ans);    }};void run(){    INT T = readIn();    while (T--)    {        count_ = 0;        memset(head, 0, sizeof(head));        memset(headT, 0, sizeof(headT));        n = readIn();        m = readIn();        K = readIn();        mod = readIn();        minC = INF;        for (int i = 1; i <= m; i++)        {            INT u = readIn();            INT v = readIn();            INT c = readIn();            minC = std::min(minC, c);            addEdge(u, v, c);        }        d = SP::goSP();        RunInstance(work);    }}int main(){#ifndef LOCAL    freopen("park.in", "r", stdin);    freopen("park.out", "w", stdout);#endif    run();    return 0;}
原创粉丝点击