洛谷3953 NOIP2017 逛公园 最短路图+拓扑排序+dp

44 篇文章 0 订阅
12 篇文章 0 订阅

题目链接
题意:
给你一个n个点m条边的有向带权图,设1号点到n号点的最短路是dis,给你一个k(k<=50),求所有1到n的路径中长度不超过dis+k的数量。

题解:
显然我们要先处理出最短路,以后再也不会写SPFA了,因为NOI2018卡了SPFA,并且很多人也说SPFA的复杂度确实是错的,于是就只好迪杰了。处理出最短路之后,如果k=0,就是最短路计数了。要做计数,我们不难想到要在图上dp。我们发现只要有一个边权全部是0的环,那么我们的满足题意的路径就会有无数条,因为可以在环里转任意多圈之后再出来。那么我们要判断是否有无穷多解就是去找有没有全部是0的环。我们可以先处理出一个最短路图,最短路图的含义是由所有dis[x]+dis[x][y]=dis[y]的边连成的图,一个性质就是如果边权都是正数,那么最短路图是一个DAG。DAG是可以拓扑排序的,如果最后每个点入度都是0,就不存在权值全是0的环,否则就是有权值全是0的环,因为权值是0的边一定不会让两点之间的最短路边长,就一定会在最短路图上,而形成环的话是没法有其中某一个点的入度是0,因此判断拓扑排序后的入度即可。对图拓扑排序后根据图的拓扑序在DAG上dp也是一个经典套路,这里我们就会采用这个套路。因为k很小,所以很可能会出现在我们的dp状态中,当前点肯定与dp值有关,于是我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示点i比dis[i]大j的路径数,那么对于路径 x − > y x->y x>y d p [ x ] [ j ] dp[x][j] dp[x][j] d p [ y ] [ j + l e n [ x − > y ] − ( d i s [ y ] − d i s [ x ] ) ] dp[y][j+len[x->y]-(dis[y]-dis[x])] dp[y][j+len[x>y](dis[y]dis[x])]有贡献。
那么我们的做法是枚举j,然后按照拓扑序枚举x,再枚举从x出发的所有边,进行dp。注意外层是枚举j,因为在dp的过程中如果外层枚举x的话DAG上是没有环的,但是这里的边是枚举原图的边,所以可能之后会有环再回到x,得到答案就是错误的了。还有就是这个dp看似枚举的层数很多,但是其实复杂度并不高,因为所有边都只会被枚举到一次,所有点都只会被枚举k+1(0到k)次,所以总的复杂度是 O ( n ∗ k ) O(n*k) O(nk)的。最后要说的就是,这道题在NOIP的时候是卡常数的,无数神犇都被卡了,我看了看我在洛谷上的运行速度,放在NOIP的测评机上肯定也是会超时的。
代码:

#include <bits/stdc++.h>
using namespace std;

int T,n,m,k,p,hed[100010],cnt,dis[100010],vis[100010],bk[100010];
struct node
{
    int from,to,dis,next;
}a[200010],e[200010];
int dp[1000010][51],hed2[100010],cnt2,ru[100010],xu[100010],shu;
priority_queue<pair<int,int> > q;
int que[100010],h,t,pd;
long long ans;
vector<int> v[100010];
inline void add(int from,int to,int dis)
{
    a[++cnt].to=to;
    a[cnt].from=from;
    a[cnt].dis=dis;
    a[cnt].next=hed[from];
    hed[from]=cnt;
}
inline void add2(int from,int to,int dis)
{
    e[++cnt2].to=to;
    e[cnt2].dis=dis;
    e[cnt2].next=hed2[from];
    hed2[from]=cnt2;
}
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
int main()
{
    T=read();
    while(T--)
    {
    	n=read();
    	m=read();
    	k=read();
    	p=read();
        memset(hed,0,sizeof(hed));
        memset(bk,0,sizeof(bk));
        memset(dis,0x3f,sizeof(dis));
        memset(vis,0,sizeof(vis)); 
        for(int i=1;i<=cnt;++i)
        {
            a[i].to=0;
            a[i].dis=0;
            a[i].next=0;
        }
        cnt=0;
        for(int i=1;i<=m;++i)
        {
            int x,y,z;
            x=read();
            y=read();
            z=read();
            add(x,y,z);
        }
        dis[1]=0;
        q.push(make_pair(0,1));
        while(!q.empty())
        {
            int x=q.top().second;
            q.pop();
            if(vis[x])
            continue;
            vis[x]=1;
            for(int i=hed[x];i;i=a[i].next)
            {
                int y=a[i].to;
                if(dis[y]>dis[x]+a[i].dis)
                {
                    dis[y]=dis[x]+a[i].dis;
                    q.push(make_pair(-dis[y],y));
                }
            }
        }
        memset(hed2,0,sizeof(hed2));
        memset(ru,0,sizeof(ru));
        for(int i=1;i<=cnt2;++i)
        {
            e[i].to=0;
            e[i].dis=0;
            e[i].next=0;
        }
        cnt2=0;
        for(int i=1;i<=cnt;++i)
        {
            if(dis[a[i].from]+a[i].dis==dis[a[i].to])           
            {
                add2(a[i].from,a[i].to,a[i].dis);
                ++ru[a[i].to];
            }
        }
        h=1;
        t=0;
        shu=0;
        memset(xu,0,sizeof(xu));
        for(int i=1;i<=n;++i)
        {
            if(!ru[i])
            que[++t]=i; 
        }
        while(h<=t)
        {
            int x=que[h];
            xu[++shu]=x;
            for(int i=hed2[x];i;i=e[i].next)
            {
                int y=e[i].to;
                --ru[y];
                if(!ru[y])
                que[++t]=y;
            }
            ++h;
        }
        pd=0;
        for(int i=1;i<=n;++i)
        {
            if(ru[i])
            bk[i]=1;
        }
        memset(dp,0,sizeof(dp));
        dp[1][0]=1;
        for(int j=0;j<=k;++j)
        {
            for(int i=1;i<=n;++i)
            {
                int x=xu[i];
                for(int l=hed[x];l;l=a[l].next)
                {
                    int y=a[l].to;
                    if(j+a[l].dis-(dis[y]-dis[x])<=k)
                    {
                    	dp[y][j+a[l].dis-(dis[y]-dis[x])]=dp[y][j+a[l].dis-(dis[y]-dis[x])]+dp[x][j];
                    	if(dp[y][j+a[l].dis-(dis[y]-dis[x])]>=p)
                    	dp[y][j+a[l].dis-(dis[y]-dis[x])]-=p;
                    	v[y].push_back(i);
					}
                    
                }
            }
        }
        h=0;
        t=1;
        que[1]=n;
        memset(vis,0,sizeof(vis));
        while(h<t)
        {
        	++h;
        	int x=que[h];
        	if(bk[x])
        	{
        		pd=1;
        		break;
			}
        	if(v[x].empty())
        	continue;
        	int sz=v[x].size();
			for(int i=0;i<sz;++i)
			{
				int y=v[x][i];
				if(vis[y])
				continue;
				que[++t]=y;
				vis[y]=1;
			}
		}
        if(pd==1)
        {
            printf("-1\n");
            for(int i=1;i<=n;++i)
       		{
        		if(!v[i].empty())
        		v[i].clear();
			}
            continue;
        }
        ans=0;
        for(int i=0;i<=k;++i)
        ans=ans+dp[n][i];
        printf("%lld\n",ans%p);
        for(int i=1;i<=n;++i)
        {
        	if(!v[i].empty())
        	v[i].clear();
		}
    }
    return 0;
}

PS:时隔大半年,再做NOIP题目,真是一点长进都没有啊。这是我NOIP唯一当场爆零了的题,但是现在听了别人讲之后还是交了好几次才过掉,虽然错的都是小地方,但是还是看出我似乎进步并不是那么大啊。

update:
时隔两年多,把bug修了,现在正确性应该没有问题了,但是常数太爆炸了,在洛谷上开02后8、9、10三个测试点会随机TLE,是可以都过的。不想卡常了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值