NOIP2017提高组DAY1T3 - 逛公园(超详细&两种做法)

版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/83691413

传送门


Analysis

首先看到这道题,暴力30分很好打
针对 k = 0 k=0 的情况,我们直接最短路计数就可以了
然后还是从k入手,发现k最多只有50,是个突破口
我们可以试着每次枚举k,限制路径长度然后计数,最后相加
如果令 f [ u ] [ j ] f[u][j] 表示当前在u这个点,与 u 到 1 的最短路相差 j 的路径条数
那么怎么更新这个状态呢?
令 v 能够到 u 那么可以得到:
f [ u ] [ j ] = Σ f [ v ] [ d i s [ u ] d i s [ v ] w [ v ] [ u ] + j ] f[u][j]=\Sigma{f[v][dis[u]-dis[v]-w[v][u]+j]}

是不是有点懵?
没关系,我们来推导一下
假设现在到 v v 的路径比dis[v]大k,并且到 u u 的路径比dis[u]大 j
我们就可以得出:
d i s [ v ] + k + w [ v ] [ u ] d i s [ u ] = j dis[v]+k+w[v][u]-dis[u]=j
(用人话说就是:到v的最短路加上现在的差量k再加上v到u的距离,就是现在从1到u的路径长度,这个路径长度与dis[u]的差就是我们定义的 j 了)

现在方程我们弄出来了,还有无穷种走法的情况没有判断
要知道能够出现无穷种走法,当且仅当在1到 n 的最短路上存在0环
我们的问题就变成了判断0环是否存在于1到n的最短路上
下面有两种方法,仅供参考

方法一:记忆化搜索
这个没什么好讲的就简单的记忆化
注意一下细节,但思路还是很对的

Code

#include<bits/stdc++.h>
#define in read()
#define N 200009
#define M 400009
#define ll long long
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int T,n,m,k,p;
int nxt[M],to[M],head[N],w[M],ecnt=0;
int Nxt[M],To[M],Head[N],W[M],Ecnt=0;
ll f[N][55],d[N];
bool vis[N],flag,used[N][55];
inline void add(int x,int y,int z){	nxt[++ecnt]=head[x];head[x]=ecnt;to[ecnt]=y;w[ecnt]=z;}
inline void readd(int x,int y,int z){Nxt[++Ecnt]=Head[x];Head[x]=Ecnt;To[Ecnt]=y;W[Ecnt]=z;}
inline void init(){
	ecnt=0;flag=0;Ecnt=0;
	memset(Head,0,sizeof(Head));
	memset(head,0,sizeof(head));
	memset(f,-1,sizeof(f));
	memset(used,0,sizeof(used));
}
inline void dij(){
	priority_queue<pair<int,int> > q;
	memset(vis,0,sizeof(vis));memset(d,127/3,sizeof(d));
	q.push(make_pair(0,1));d[1]=0;
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(d[v]>d[u]+w[e]){
				d[v]=d[u]+w[e];
				q.push(make_pair(-d[v],v));
			}
		}
	}
}
ll dfs(int u,int lim){
	if(f[u][lim]!=-1) return f[u][lim];
	used[u][lim]=1;f[u][lim]=0;
	for(int e=Head[u];e;e=Nxt[e]){
		int v=To[e],num=lim-W[e]+d[u]-d[v];
		if(num<0) continue;
		if(used[v][num]) flag=1;
		f[u][lim]=(f[u][lim]+dfs(v,num))%p;
	}
	used[u][lim]=0;
	return f[u][lim];
}
int main(){
	T=in;
	while(T--){
		init();int i,j,a,b,c;
		n=in;m=in;k=in;p=in;
		for(i=1;i<=m;++i){
			a=in;b=in;c=in;
			add(a,b,c);readd(b,a,c);
		}
		dij();
		if(!d[n]) {cout<<"-1\n";continue;}
		//这条语句非常有意思……加上会错误判断,但不加会过不了样例
		//(可以用这组数据来卡一下:
		//1
		//3 2 0 10
		//1 2 0
		//2 3 0
		//应该输出1,但会错误判断输出-1
		f[1][0]=1;
		ll ans=0;
		for(i=0;i<=k;++i) ans=(ans+dfs(n,i))%p;
		if(flag) printf("-1\n");
		else cout<<ans<<'\n';
	}
	return 0;
}

(这个方法判0环存在Bug,样例过不了,但能水过测试数据,目前还没有想出更好的方法进行改进)
------------------------------20181104填坑---------------------
这个方法不存在bug了,神仙gsj提出了解决方案
这个程序出现的问题,就是针对样例那种情况,由于我们先将 f [ 1 ] [ 0 ] f[1][0] 赋了初值,就判不了包含1的0环了
既然这样,我们就做两遍dfs,第一遍的时候只是判0环,不计数(也就是不给 f f 赋初值,第二遍的时候再赋值计数

代码:

#include<bits/stdc++.h>
#define in read()
#define N 200009
#define M 400009
#define ll long long
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int T,n,m,k,p;
int nxt[M],to[M],head[N],w[M],ecnt=0;
int Nxt[M],To[M],Head[N],W[M],Ecnt=0;
ll f[N][55],d[N];
bool vis[N],flag,used[N][55];
inline void add(int x,int y,int z){	nxt[++ecnt]=head[x];head[x]=ecnt;to[ecnt]=y;w[ecnt]=z;}
inline void readd(int x,int y,int z){Nxt[++Ecnt]=Head[x];Head[x]=Ecnt;To[Ecnt]=y;W[Ecnt]=z;}
inline void init(){
	ecnt=0;flag=0;Ecnt=0;
	memset(Head,0,sizeof(Head));
	memset(head,0,sizeof(head));
	memset(f,-1,sizeof(f));
	memset(used,0,sizeof(used));
}
inline void dij(){
	priority_queue<pair<int,int> > q;
	memset(vis,0,sizeof(vis));memset(d,127/3,sizeof(d));
	q.push(make_pair(0,1));d[1]=0;
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(d[v]>d[u]+w[e]){
				d[v]=d[u]+w[e];
				q.push(make_pair(-d[v],v));
			}
		}
	}
}
ll dfs(int u,int lim){
	if(f[u][lim]!=-1) return f[u][lim];
	used[u][lim]=1;f[u][lim]=0;
	for(int e=Head[u];e;e=Nxt[e]){
		int v=To[e],num=lim-W[e]+d[u]-d[v];
		if(num<0) continue;
		if(used[v][num]) flag=1;
		f[u][lim]=(f[u][lim]+dfs(v,num))%p;
	}
	used[u][lim]=0;
	return f[u][lim];
}
int main(){
	T=in;
	while(T--){
		init();int i,j,a,b,c;
		n=in;m=in;k=in;p=in;
		for(i=1;i<=m;++i){
			a=in;b=in;c=in;
			add(a,b,c);readd(b,a,c);
		}
		dij();
		for(i=0;i<=k;++i) dfs(n,i);//
		if(flag) {cout<<"-1\n";continue;}//
		memset(f,-1,sizeof(f));//
		f[1][0]=1;
		ll ans=0;
		for(i=0;i<=k;++i) ans=(ans+dfs(n,i))%p;
		cout<<ans<<'\n';
	}
	return 0;
}

方法二:拓扑排序
这时候统计方案数需要用到一个技巧,叫拆点最短路
对于每一个原图中的点,我们把它都拆成K个点,第x个点表示到了点u时误差为x的状态,也就是说强行把原来的走到u这个状态细化了,现在每一个小点能更精确的表示需要的信息
统计dag上可以到达T的路径数就可以了,topsort顺便用来判了-1的情况

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define N 200100
#define ll long long
using namespace std;
int n,m,k,p;
ll ans;
struct node
{
    int u,v,w,nxt;
}e[N*2],g[N*53];
int first[N],cnt;
void ade(int u,int v,int w)
{
    e[++cnt].nxt=first[u]; first[u]=cnt;
    e[cnt].u=u; e[cnt].v=v; e[cnt].w=w;
}
int fir[N*53],cnnt;
void adde(int u,int v,int w)
{
    g[++cnnt].nxt=fir[u]; fir[u]=cnnt;
    g[cnnt].u=u; g[cnnt].v=v; g[cnnt].w=w;
}
void adeg(int u,int v)
{
    g[++cnnt].nxt=fir[u]; fir[u]=cnnt;
    g[cnnt].u=u; g[cnnt].v=v;
}
ll dis[N];
bool vis[N];

void spfa(int x)
{
    queue<int>q;
    memset(dis,0x3f,sizeof(dis));
    memset(vis,true,sizeof(vis));
    q.push(x);
    dis[x]=0;
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        vis[u]=true;
        for(int i=first[u];i;i=e[i].nxt)
        {
            int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w)
            {
                dis[v]=dis[u]+e[i].w;
                if(vis[v]==true)
                {
                    q.push(v);
                    vis[v]=false;
                }
            }
        }
    }
}
ll dis2[N];
void spfa2(int x)
{
    queue<int>q;
    memset(dis2,0x3f,sizeof(dis));
    memset(vis,true,sizeof(vis));
    q.push(x);
    dis2[x]=0;
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        vis[u]=true;
        for(int i=fir[u];i;i=g[i].nxt)
        {
            int v=g[i].v;
            if(dis2[v]>dis2[u]+g[i].w)
            {
                dis2[v]=dis2[u]+g[i].w;
                if(vis[v]==true)
                {
                    q.push(v);
                    vis[v]=false;
                }
            }
        }
    }
}
int get(int x,int y)
{
    return (x-1)*(k+1)+y+1;
}
int ru[N*53];
void build_graph()
{
    for(int i=1;i<=m;i++)
    {
        int u=e[i].u,v=e[i].v,w=e[i].w;
        int x=get(u,0);
        int y=get(v,dis[u]+w-dis[v]);
        for(int j=dis[u];j+w+dis2[v]<=dis[n]+k;j++,x++,y++)
        { adeg(x,y); ru[y]++; }
    }
}
ll sum,f[N*53];
int q[N<<6];
void topsort2()
{
    int l = 0,r=0;
    for(int i = 1;i<=n*(k+1);i++)
        if(!ru[i]) q[++r]=i;
    f[1]=1;
    while(l<r)
    {
        int x=q[++l];
        sum++;
        for(int i=fir[x];i;i=g[i].nxt)
        {
            int v=g[i].v;
            ru[v]--;
            if(!ru[v]) q[++r]=v;
            f[v]+=f[x];
            f[v] = f[v]>p ? f[v]-p : f[v];
        }
    }
}
void pre()
{
    memset(first,0,sizeof(first));
    memset(fir,0,sizeof(fir));
    memset(f,0,sizeof(f));
    memset(ru,0,sizeof(ru));
    sum=ans=cnnt=cnt=0;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        pre();
        scanf("%d%d%d%d",&n,&m,&k,&p);
        for(int i=1,x,y,z;i<=m;i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            ade(x,y,z); adde(y,x,z);
        }
        spfa(1);
        spfa2(n);
        memset(fir,0,sizeof(fir));
        cnnt=0;
        build_graph();
        topsort2();
        int num=(k+1)*n;
        if(sum<num) printf("-1\n");
        else
        {
            for(int i=0;i<=k;i++)
                ans=(ans+f[get(n,i)])%p;
            printf("%lld\n",ans);
        }
    }
    return 0;
}

这个代码不存在问题了

但不是我写的……
我写的代码,需要卡常加O(2)才能过
放一下我的代码,和上面网上标程不同就在于我没有反向再跑一遍最短路
所以加的边中会多一些无用的,就会拖慢速度

#include<bits/stdc++.h>
#define in read()
#define N 100009
#define M 400009
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+ch-'0';ch=getchar();}
	return f==1?res:-res;
}
priority_queue<pair<int,int> > q;
bool vis[N];
int T,n,m,k,p,cnt=0;
int nxt[M],to[M],head[N],w[M],ecnt=0;
int d[N],idx[M*52],du[N*52],l[N*52],r[N*52],f[N*52];
inline void add(int x,int y,int z){nxt[++ecnt]=head[x];head[x]=ecnt;to[ecnt]=y;w[ecnt]=z;}
inline void dijkstra(){
	memset(vis,0,sizeof(vis));
	memset(d,127/3,sizeof(d));
	q.push(make_pair(0,1));d[1]=0;
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int e=head[u];e;e=nxt[e]){
			int v=to[e];
			if(d[v]>d[u]+w[e]){
				d[v]=d[u]+w[e];
				q.push(make_pair(-d[v],v));
			}
		}
	}
}
inline void init(){
	ecnt=0;cnt=0;
	memset(head,0,sizeof(head));
	memset(du,0,sizeof(du));
	memset(f,0,sizeof(f));
}
inline int create(int i,int j){return i+j*n;}
int main(){
	T=in;
	while(T--){
		init();
		n=in;m=in;k=in;p=in;
		int i,j,a,b,c;
		for(i=1;i<=m;++i)	a=in,b=in,c=in,add(a,b,c);
		dijkstra();
		for(i=1;i<=n;++i){//枚举每一个点,拆点 
			for(j=0;j<=k;++j){//枚举每一种到 i 的误差大小 
				l[create(i,j)]=cnt+1;//新建的节点(i,j)连向的点 
				for(int e=head[i];e;e=nxt[e]){
					int v=to[e];
					if(j+d[i]+w[e]-d[v]<=k) 
						++du[idx[++cnt]=create(v,j+d[i]+w[e]-d[v])];
				}
				r[create(i,j)]=cnt;
			}
		}
		f[1]=1;
		queue<int > que;
		for(i=1;i<=create(n,k);++i)
			if(!du[i]) que.push(i);
		int sum=0;
		while(!que.empty()){
			int u=que.front();que.pop();
			++sum; 
			for(i=l[u];i<=r[u];++i){
				int v=idx[i];
				f[v]=(f[v]+f[u])%p;--du[v];
				if(!du[v]) que.push(v);
			}
		}
		if(sum<create(n,k)) {printf("-1\n");continue;}
		int ans=0;
		for(i=0;i<=k;++i){
			ans=(ans+f[create(n,i)])%p;
		}
		printf("%d\n",ans);
	}
	return 0;
}


Summary

┭┮﹏┭┮,T3果然不好做啊,我要恶补一下拓扑排序了
一开始学习zzh姐姐的,后来发现漏洞有点点多……
再后来在网上找拓扑排序版本的,又选了个超时的……
心态爆炸
不过还好,通过这道题知道了拆点+拓扑排序+拓扑序求解dp的方法

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/83691413