NOIP2017 逛公园

题目

首先需要一个 Dijkstra 求最短路。设 \(d_u\) 表示从 \(1\)\(u\) 的最短路。

\(g_{u,j}\) 表示从 \(1\) 点到 \(u\) 点,长度恰好等于 \(j\) 的路径种数。那么可以列出式子

\[ g_{u,j}= \begin{cases} 1(u=1,j=0)\\ \sum g_{v,j-c(v,u)}(otherwise) \end{cases} \]

。然后我们就可以 DP 了。

慢着……为什么这题可以 DP 呢?

DP 的一个必要条件是,不存在一个状态使得它向自己转移。假设本题中的某个状态能向自己转移,那么很明显原图中存在一个边权全为 \(0\) 的环。因此,判掉 \(0\) 环后,本题的状态转移就没有环了(实际上,一切 DP 的过程都可以看作一个以状态为点,以转移为边的有向图,且它一定是一个拓扑图)。

现在有一个严重的问题:这个 DP 有 \(N\cdot \max d_i\) 种状态,好像只能拿 \(10\) 分。

我们发现, \(K\) 不是很大,想到设 \(f_{u,j}\) 表示从 \(1\) 点到 \(u\) 点,长度恰好等于 \(d_u+j\) 的路径种数。方程

\[ f_{u,j}= \begin{cases} 1(u=1,j=0)\\ \sum f_{v,d_u+j-c(v,u)-d_v}(otherwise) \end{cases} \]

。答案就是\(\sum_{j=0}^K f_{n,j}\)

显然,状态一共只有 \(O(NK)\) 种。转移顺序不明显,可以用记忆化搜索实现;关于 \(0\) 环,只要在记忆化搜索的过程中顺便判判就好了。总时间复杂度\(O(M\log M+MK)\),可以通过本题。

#include
#include
#include
inline int read(){
    int a=0;char c=getchar();
    for(;c<48||c>57;c=getchar());for(;c>47&&c<58;a=a*10+c-48,c=getchar());
    return a;
}
using namespace std;
const int N=1e5+1,K=51;
struct edge{int v,c,nxt;}g[400001];
int n,m,l,M,s,f[N][K],used[N][K],d[N],head[N+N],k;
inline int Push(int u,int v,int c){g[++k]=(edge){v,c,head[u]};head[u]=k;} 
typedef pairP;priority_queue,greater

>p; inline int Sp(){ int u,v; memset(d,127,sizeof d); p.push(P(d[1]=0,1)); for(;!p.empty();){ u=p.top().second; if(p.top().first>d[u]){p.pop();continue;} p.pop(); for(int i=head[u];i;i=g[i].nxt)if(g[i].c+d[u]=M?a-M:a;} int dfs(int u,int k){ if(used[u][k])return-1; if(f[u][k]=0&&t<=l) if(~dfs(v,t))f[u][k]=Mod(f[v][t]+f[u][k]); else return f[u][k]=-1; }used[u][k]=0; return f[u][k]; } int main() { int u,v,c,fl; int T=read();for(;T--;){ k=0;memset(head,0,sizeof head); n=read();m=read();l=read();M=read(); for(;m--;)u=read(),v=read(),c=read(),Push(u,v,c),Push(v+n,u,c); Sp(); for(int i=1;i<=n;i++) for(int j=0;j<=l;j++) f[i][j]=M; s=0;fl=1;memset(used,0,sizeof used); for(int j=0;j<=l;j++) if(dfs(n,j)<0){fl=0;break;} else s=Mod(s+f[n][j]); printf("%d\n",fl?s:-1); }return 0; }

你可能感兴趣的:(NOIP2017 逛公园)