[洛谷P3953] 逛公园

洛谷题目链接:逛公园

题目复制过来有点炸格式就不贴了,看题目请戳上面的链接

题意: 给出一张\(n\)个点,\(m\)条边的有向图,设\(1\)\(n\)的最短路径长度为\(d\),问\(1\)\(n\)路径长度小于等于\(d+k\)的路径有多少条.

题解: \(k=0\)的部分分就是一个最短路计数,如果不会的话可以先去看一下最短路计数.

我们先考虑不含\(0\)环的情况.首先我们可以求出从\(1\)为起点到其他点的最短路的长度,记为\(dist[i]\).最后对答案产生贡献的状态只有\(dist[n]+k\),所以我们可以记\(f[i][j]\)表示从\(1\)出发到\(i\)节点路径长度为\(dist[i]+j\)的方案数.

那么这样我们就可以写出状态转移方程\((x->to)\):$$f[to][dist[x]+e[i].w-dist[to]+j] += f[x][j]$$

然后需要特别注意的是枚举顺序,我们在枚举\(to\)的时候要先将\(x\)处理完,并且第二维的枚举状态要从小到大枚举.处理好这些,就可以获得\(70\)分了.

为什么说是\(70\)分呢?因为我们这样并不能判断出无穷多条路径的方案.但是我们可以发现,如果存在无穷多条路径,那么在\(1\)\(n\)的最短路上(或是在一条\(1->n\)的路径且长度小于等于\(dist[n]+k\)上)有一个\(0\)环,那么就存在无穷多条路径,因为可以在这个\(0\)环上不断绕.

所以要判断无穷多条路径,就要先判断是否存在一个\(0\)环在一条可以对答案产生贡献的路径上.这个可以用拓扑排序实现.

因为有可能存在\(0\)环,所以我们更新\(dp\)数组的时候不能再一边遍历图,一遍更新数组了,这时候我们就需要对要更新的状态排个序,也就是让\(dist\)小的排在前面更新,但是又由于\(0\)边的存在,我们需要让一条边的起点来更新终点,所以在多个点\(dist\)相同的时候,可以按照拓扑序来排.

细节比较多,可以自己多想想.

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
const int M = 2e5+5;
const int K = 50+5;

int T, n, m, k, yyj, ans = 0, dist[N][2], vis[N][2], point[N], in[N], ord[N];
int last[N][2], last0[N], ecnt[2], ecnt0 = 0;
int f[N][K];

struct edge{
    int to, nex, w;
}e[M][2], e0[M];

struct node{
    int id, dis;
    bool operator < (const node &a) const{
		return dis > a.dis;
    }
};

int gi(){
    int res = 0, f = 1; char i = getchar();
    while(i < '0' || i > '9'){ if(i == '-') f = -1; i = getchar(); }
    while(i >= '0' && i <= '9') res = res*10+i-'0', i = getchar();
    return res*f;
}

void clear(){
    memset(last, 0, sizeof(last)), ans = ecnt[0] = ecnt[1] = 0;
    memset(last0, 0, sizeof(last0)), ecnt0 = 0;
    memset(in, 0, sizeof(in)), memset(vis, 0, sizeof(vis));
    memset(dist, 127/3, sizeof(dist));
    memset(ord, 0, sizeof(ord));
}

void add(int x, int y, int z, int t){
    e[++ecnt[t]][t].to = y, e[ecnt[t]][t].w = z, e[ecnt[t]][t].nex = last[x][t], last[x][t] = ecnt[t];
}

void add0(int x, int y){
    e0[++ecnt0].to = y, e0[ecnt0].nex = last0[x], last0[x] = ecnt0;
}

void dijkstra(int st, int t){
    priority_queue <node> q; q.push((node){ st, 0 });
    dist[st][t] = 0;
    while(!q.empty()){
		node x = q.top(); q.pop();
		if(vis[x.id][t]) continue; vis[x.id][t] = 1;
		for(int to, i = last[x.id][t]; i; i = e[i][t].nex){
			to = e[i][t].to;
			if(dist[to][t] > x.dis+e[i][t].w)
				dist[to][t] = x.dis+e[i][t].w, q.push((node){ to, dist[to][t] });
		}
    }
}

bool cmpdis(int a, int b){
    if(dist[a][0] == dist[b][0]) return ord[a] < ord[b];
    return dist[a][0] < dist[b][0];
}

bool topsort(){
    queue <int> q; int cnt = 0;
    for(int i = 1; i <= n; i++)
		if(in[i] == 0) q.push(i), ord[i] = ++cnt;
    while(!q.empty()){
		int x = q.front(); q.pop();
		for(int to, i = last0[x]; i; i = e0[i].nex){
			to = e0[i].to, in[to]--;
			if(in[to] == 0) q.push(to), ord[to] = ++cnt;
		}
    }
    for(int i = 1; i <= n; i++)
		if(in[i] && dist[i][0]+dist[i][1] <= dist[n][0]+k) return false;
    return true;
}

void solve(int st){
    memset(f, 0, sizeof(f)), f[st][0] = 1;
    for(int i = 1; i <= n; i++) point[i] = i;
    sort(point+1, point+n+1, cmpdis);
    for(int l = 0; l <= k; l++){
		for(int i = 1; i <= n; i++){
			int x = point[i];
			for(int j = last[x][0]; j; j = e[j][0].nex){
				int to = e[j][0].to;
				if(dist[x][0]+e[j][0].w+l <= dist[to][0]+k)
					(f[to][dist[x][0]+e[j][0].w+l-dist[to][0]] += f[x][l]) %= yyj;
			}
		}
    }
}

int main(){
    ios::sync_with_stdio(false);
    int x, y, z; T = gi();
    while(T--){
		clear(); n = gi(), m = gi(), k = gi(), yyj = gi();
		for(int i = 1; i <= m; i++){
			x = gi(), y = gi(), z = gi(), add(x, y, z, 0), add(y, x, z, 1);
			if(z == 0) add0(x, y), in[y]++;
		}
		dijkstra(1, 0), dijkstra(n, 1);
		if(!topsort()){ cout << -1 << endl; continue; }
		solve(1);
		for(int i = 0; i <= k; i++) (ans += f[n][i]) %= yyj;
		cout << ans << endl;
    }
    return 0;
}
posted @ 2018-09-28 11:06  Brave_Cattle  阅读(179)  评论(0编辑  收藏  举报