zoukankan      html  css  js  c++  java
  • 【NOIP2017】逛公园 拆点最短路+拓扑(记忆化搜索

    题目描述

    策策同学特别喜欢逛公园。公园可以看成一张N个点M条边构成的有向图,且没有 自环和重边。其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。

    策策每天都会去逛公园,他总是从1号点进去,从N号点出来。

    策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果1号点 到N号点的最短路长为d,那么策策只会喜欢长度不超过d+K的路线。

    策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?

    为避免输出过大,答案对P取模。

    如果有无穷多条合法的路线,请输出1。

    输入输出格式

    输入格式: 

    第一行包含一个整数 T, 代表数据组数。

    接下来TT组数据,对于每组数据: 第一行包含四个整数 N,M,K,P,每两个整数之间用一个空格隔开。

    接下来M行,每行三个整数ai,bi,ci​,代表编号为ai,bi的点之间有一条权值为 ci的有向边,每两个整数之间用一个空格隔开。

    输出格式:

    输出文件包含 T 行,每行一个整数代表答案。

    输入输出样例

    输入样例#1:
    2
    5 7 2 10
    1 2 1
    2 4 0
    4 5 2
    2 3 2
    3 4 1
    3 5 2
    1 5 3
    2 2 0 10
    1 2 0
    2 1 0
    输出样例#1:
    3
    -1
    
    

    说明

    【样例解释1】

    对于第一组数据,最短路为 33。 $1 – 5, 1 – 2 – 4 – 5, 1 – 2 – 3 – 5$ 为 33 条合法路径。

    【测试数据与约定】

    对于不同的测试点,我们约定各种参数的规模不会超过如下

    测试点编号  TT   NN   MM   KK   是否有0边
    1 5 5 10 0
    2 5 1000 2000 0
    3 5 1000 2000 50
    4 5 1000 2000 50
    5 5 1000 2000 50
    6 5 1000 2000 50
    7 5 100000 200000 0
    8 3 100000 200000 50
    9 3 100000 200000 50
    10 3 100000 200000 50

    对于 100%的数据, 1 le P le 10^9,1 le a_i,b_i le N ,0 le c_i le 10001P109,1ai,biN,0ci1000。

    数据保证:至少存在一条合法的路线。

    ------------------------------------------------------

    在此衷心感谢rockdu~

    还是本着避开DP的原则,这一次我们继续用骚操作的避开一波浓浓的DP味~~~~

    以下摘录rockdu的原话加上我的一点点理解:

    首先这道题关键的一步是找出T到所有点的最短路是多少,

    虽然起点很多但是终点只有一个,我们从终点反着跑最短路,就可以求出所有的最优距离。

    然后我们想,如果从S出发到达点u,距离误差已经超过了K,这时候会怎么样呢?

    我们无力回天,因为即使用最优策略也只能让误差不增加

    所以对于每一个点,我们只关心误差在K以内的方案数

    这时候统计方案数需要用到一个技巧,叫拆点最短路

    对于每一个原图中的点,我们把它都拆成K个点,第x个点表示到了点u时误差为x的状态,也就是说强行把原来的走到u这个状态细化了,现在每一个小点能更精确的表示需要的信息

    再进一步思考,这个图一定是一个dag:因为边权不为0,误差一定增大

    那么想象一下,如果把小点从平面的图中拉出来

    形成一层一层的结构,每一层都是一个x,那么一定是只能从x小的层向x大的层流动

    也就是不可能一个点误差是k走一圈回来误差仍然是k,整个层次图构成了dag

    因此,统计dag上可以到达T的路径数就可以了,写一个记忆化搜索做到O(n)  (我最后用的topsort+f[i]来解决这一个问题 topsort顺便用来判了-1的情况

    如果有边权为0的,并且可以反向到达S正向到达T的环,那么有无限组方案

    我觉得整个过程真的是妙妙啊....

    同时要注意:

    1.memset(first,0,sizeof(first)); 

    2.根据新图的性质要把数组开到足够大

    3.仔细品读建新图的过程

    4.记得手写队列

    以下代码:

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 #include<cmath>
      5 #include<algorithm>
      6 #include<queue>
      7 #define N 200100
      8 #define ll long long
      9 using namespace std;
     10 int n,m,k,p;
     11 ll ans;
     12 struct node
     13 {
     14     int u,v,w,nxt;
     15 }e[N*2],g[N*53];
     16 int first[N],cnt;
     17 void ade(int u,int v,int w)
     18 {
     19     e[++cnt].nxt=first[u]; first[u]=cnt;
     20     e[cnt].u=u; e[cnt].v=v; e[cnt].w=w;
     21 }
     22 int fir[N*53],cnnt;
     23 void adde(int u,int v,int w)
     24 {
     25     g[++cnnt].nxt=fir[u]; fir[u]=cnnt;
     26     g[cnnt].u=u; g[cnnt].v=v; g[cnnt].w=w;
     27 }
     28 void adeg(int u,int v)
     29 {
     30     g[++cnnt].nxt=fir[u]; fir[u]=cnnt;
     31     g[cnnt].u=u; g[cnnt].v=v;
     32 }
     33 ll dis[N];
     34 bool vis[N];
     35 
     36 void spfa(int x)
     37 {
     38     queue<int>q;
     39     memset(dis,0x3f,sizeof(dis));
     40     memset(vis,true,sizeof(vis));
     41     q.push(x);
     42     dis[x]=0;
     43     while(!q.empty())
     44     {
     45         int u=q.front(); q.pop();
     46         vis[u]=true;
     47         for(int i=first[u];i;i=e[i].nxt)
     48         {
     49             int v=e[i].v;
     50             if(dis[v]>dis[u]+e[i].w)
     51             {
     52                 dis[v]=dis[u]+e[i].w;
     53                 if(vis[v]==true)
     54                 {
     55                     q.push(v);
     56                     vis[v]=false;
     57                 }
     58             }
     59         }
     60     }
     61 }
     62 ll dis2[N];
     63 void spfa2(int x)
     64 {
     65     queue<int>q;
     66     memset(dis2,0x3f,sizeof(dis));
     67     memset(vis,true,sizeof(vis));
     68     q.push(x);
     69     dis2[x]=0;
     70     while(!q.empty())
     71     {
     72         int u=q.front(); q.pop();
     73         vis[u]=true;
     74         for(int i=fir[u];i;i=g[i].nxt)
     75         {
     76             int v=g[i].v;
     77             if(dis2[v]>dis2[u]+g[i].w)
     78             {
     79                 dis2[v]=dis2[u]+g[i].w;
     80                 if(vis[v]==true)
     81                 {
     82                     q.push(v);
     83                     vis[v]=false;
     84                 }
     85             }
     86         }
     87     }
     88 }
     89 int get(int x,int y)
     90 {
     91     return (x-1)*(k+1)+y+1;
     92 }
     93 int ru[N*53];
     94 void build_graph()
     95 {
     96     for(int i=1;i<=m;i++)
     97     {
     98         int u=e[i].u,v=e[i].v,w=e[i].w;
     99         int x=get(u,0);
    100         int y=get(v,dis[u]+w-dis[v]);
    101         for(int j=dis[u];j+w+dis2[v]<=dis[n]+k;j++,x++,y++)
    102         { adeg(x,y); ru[y]++; }
    103     }
    104 }
    105 ll sum,f[N*53];
    106 int q[N<<6];
    107 void topsort2()
    108 {
    109     int l = 0,r=0;
    110     for(int i = 1;i<=n*(k+1);i++)
    111         if(!ru[i]) q[++r]=i;
    112     f[1]=1;
    113     while(l<r)
    114     {
    115         int x=q[++l];
    116         sum++;
    117         for(int i=fir[x];i;i=g[i].nxt)
    118         {
    119             int v=g[i].v;
    120             ru[v]--;
    121             if(!ru[v]) q[++r]=v;
    122             f[v]+=f[x];
    123             f[v] = f[v]>p ? f[v]-p : f[v];
    124         }
    125     }
    126 }
    127 void pre()
    128 {
    129     memset(first,0,sizeof(first));
    130     memset(fir,0,sizeof(fir));
    131     memset(f,0,sizeof(f));
    132     memset(ru,0,sizeof(ru));
    133     sum=ans=cnnt=cnt=0;
    134 }
    135 int main()
    136 {
    137     int t;
    138     scanf("%d",&t);
    139     while(t--)
    140     {
    141         pre();
    142         scanf("%d%d%d%d",&n,&m,&k,&p);
    143         for(int i=1,x,y,z;i<=m;i++)
    144         {
    145             scanf("%d%d%d",&x,&y,&z);
    146             ade(x,y,z); adde(y,x,z);
    147         }
    148         spfa(1);
    149         spfa2(n);
    150         memset(fir,0,sizeof(fir));
    151         cnnt=0;
    152         build_graph();
    153         topsort2();
    154         int num=(k+1)*n;
    155         if(sum<num) printf("-1
    ");
    156         else
    157         {
    158             for(int i=0;i<=k;i++)
    159                 ans=(ans+f[get(n,i)])%p;
    160             printf("%lld
    ",ans);
    161         }
    162     }
    163     return 0;
    164 }
    165 /*
    166 1
    167 5 7 2 10
    168 1 2 1
    169 2 4 0
    170 4 5 2
    171 2 3 2
    172 3 4 1
    173 3 5 2
    174 1 5 3
    175 */

    我爱rockdu

  • 相关阅读:
    案例(用封装的ajax加载数据库的数据到页面)
    案例(用封装的ajax函数检查用户名)
    Verilog中的UDP
    FPGA中的“门”
    反馈的基础概述
    集成运放四种组态
    阻抗匹配处理方式
    关于阻抗匹配的理解
    关于输入阻抗和输出阻抗的理解
    电压跟随器的一点理解
  • 原文地址:https://www.cnblogs.com/kylara/p/9748884.html
Copyright © 2011-2022 走看看