noip2017D1T3逛公园(拓扑图上dp,记忆化搜索)

QWQ前几天才刚刚把这个D1T3写完

看着题解理解了很久,果然我还是太菜了QAQ


题目大意就是

给你一个n个点,m条边的图,保证1能到达n,求从1到n的 (设1到n的最短路长度是d)路径长度在[d,d+k]之间的路径有多少条,答案要对p取膜

下面附上数据范围的大表哥!



首先对于30%的数据,我们可以直接跑最短路计数来实现QWQ

这里最短路计数就不作详细解释了!

一定注意的是 当更新dis[to[i]]时,要记得把ans[to[i]]赋值成ans[x] 千万不要手残写成1!!!


上代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define pa pair<int,int>
using namespace std;

const int maxn = 100010;
const int maxm = 400010;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){ if (ch=='-') f=-1;ch=getchar();
    }while (isdigit(ch)){ x=(x << 3)+(x << 1) + ch-'0';ch=getchar();
    }return x*f; 
}

struct Node{
    int num;
    int id;
};

priority_queue <pa,vector<pa>,greater<pa> > q;
int point[maxn],nxt[maxm],to[maxm],val[maxm];
int dis[maxn];
Node qq[1000100];
int tt[maxn],vis[maxn];
int aa[maxn];
int cnt,ans;
int n,m;

void addedge(int x,int y,int w)
{
    nxt[++cnt]=point[x];
    to[cnt]=y;
    val[cnt]=w;
    point[x]=cnt;
}

void dijkstra(int s,int pp)
{
    for (int i=1;i<=n;i++)
      dis[i]=2e9;
    memset(aa,0,sizeof(aa));
    aa[s]=1;
    dis[s]=0;
    memset(vis,0,sizeof(vis));
    q.push(make_pair(0,s));
    while (!q.empty())
    {
        int  x = q.top().second;
        q.pop();
        if (vis[x]) continue;
        vis[x]=1;
        for (int i=point[x];i;i=nxt[i])
        {
            int p=to[i];
            if (dis[p]>dis[x]+val[i])
            {
                dis[p]=dis[x]+val[i];
                aa[p]=aa[x]%pp;
                q.push(make_pair(dis[p],p));
            }
            else
              if (dis[p]==dis[x]+val[i])
              {
              	aa[p]=(aa[p]+aa[x])%pp;
              }
        }
    }
}

int t,k,p;

void bfs(int d,int pp)
{
    int head=0,tail=1;
    qq[tail].id=1;
    while (head<=tail)
    {
        head++;
        int x=qq[head].id;
        for (int i=point[x];i;i=nxt[i])
        {
            int p=to[i];
            if (tt[p]>n) continue;
            qq[++tail].id=p;
            qq[tail].num=qq[head].num+val[i];
            tt[p]++;
            if (p==n&&qq[tail].num<=d+k)
            {
                ans=(ans+1)%pp;
            }
        }
    }
}


int main()
{
    t=read();
    while (t--)
    {
        scanf("%d%d%d%d",&n,&m,&k,&p);
        cnt=0;
        ans=0;
        memset(point,0,sizeof(point));
        memset(qq,0,sizeof(qq));
        memset(tt,0,sizeof(tt));
        for (int i=1;i<=m;i++)
        {
             int u,v,w;
             u=read();v=read();w=read();
             addedge(u,v,w);
        }
        dijkstra(1,p);
        printf("%d\n",aa[n]);
    }
}


这是(修改后的)考场源代码QWQ可能有点丑陋


而对于其他数据QAQ emmmmmm

这个嘛~


我们就需要考虑dp


我这里用的dp状态是

f[i][j]表示从1到i这个点,比最短路长了j的方案数

对于一条边 u - > v QAQ我们不难发现

f[v][dis[u]+k+val[i]-dis[v]]+=f[u][k]; (0<=dis[u]+k+val[i]-dis[v]<=k)


好啦!这不就可以转移了嘛?


别急QWQ 貌似还有0环的问题。


这里就需要思考一下0环的性质

如果有0环的话.....这些边应该一定会出现在最短路图上吧,那么我们只需要在最短路图上跑拓扑排序~如果到最后发现无法构成DAG 那么应该就是有0环

同时拓扑排序也是为了在最短路上的点在后面的dp中,制定一个顺序

例如x->y->z 更新顺序一定是x y z


而对于0环上的点,如果dis[i]+disn[i](到n的最短路)<=dis[n]+k 那么它就可以无限制的更新下去(可以理解为一直在0环上,从而使方案数变为无限)

如果遇到这种情况 就直接输出-1了


下面dp的部分也没什么好说的了

枚举这个偏移量(就是比最短路长多少)

就是分成两部分,先更新最短路的点,然后再用当前的偏移量的u,去更新更大偏移量的v


上代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define pa pair < int , int >
using namespace std;

inline int read()
{
   int x=0,f=1;char ch=getchar();
   while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
   while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
   return x*f;
}

//f[to[i]][dis[u]+k+w-dis[v]]+=f[u][k]

const int maxn =  1e5+1e2;
const int maxm = 5e5+1e2;
const int inf = 1e9;

int f[maxn][60];
int nxt[maxm],to[maxm],point[maxn],val[maxm];
queue<int> que;
int x[maxm],y[maxm],w[maxm];
int vis[maxn],dis[maxn],disn[maxn];
int in[maxn];
int n,m,k,p;
bool flag;
int cnt=0;
priority_queue< pa , vector<pa>,greater<pa> > q;
int mod;


void addedge(int x,int y,int w)
{
    nxt[++cnt]=point[x];
    to[cnt]=y;
    val[cnt]=w;
    point[x]=cnt;
}

void init()
{
   cnt=0;
   memset(point,0,sizeof(point));
   memset(f,0,sizeof(f));
   memset(in,0,sizeof(in));
   flag=true;
}

int dijkstra(int s)
{
   memset(vis,0,sizeof(vis));
   for (register int i=1;i<=n;i++) dis[i]=inf;
   q.push(make_pair(0,s));
   dis[s]=0;
   while (!q.empty())
   {
   	  int x = q.top().second;
   	  q.pop();
   	  if (vis[x]) continue;
   	  vis[x]=1;
   	  for (register int i=point[x];i;i=nxt[i])
   	  {
   	     int p = to[i];
   	     if (dis[p]>dis[x]+val[i])
   	     {
   	     	dis[p]=dis[x]+val[i];
   	     	q.push(make_pair(dis[p],p));
   	     }
   	  }
   }
}

int dijkstran(int s)
{
  
   memset(vis,0,sizeof(vis));
   for (register int i=1;i<=n;i++) disn[i]=inf; 
   disn[s]=0;
   q.push(make_pair(0,s));
   while (!q.empty())
   {
   	  int x = q.top().second;
   	  q.pop();
   	  if (vis[x]) continue;
   	  vis[x]=1;
   	  for (register int i=point[x];i;i=nxt[i])
   	  {
   	     int p = to[i];
   	     if (disn[p]>disn[x]+val[i])
   	     {
   	     	disn[p]=disn[x]+val[i];
   	     	q.push(make_pair(disn[p],p));
   	     }
   	  }
   }
}

int t;
int top[maxn];
int tmp;

void tpsort()
{
    tmp=0;
    for (register int x=1;x<=n;++x)
      for (register int i=point[x];i;i=nxt[i])
        {
        	int p = to[i];
        	if (dis[p]==dis[x]+val[i]) in[p]++;
        }
    for (register int i=1;i<=n;++i)
    if (in[i]==0) que.push(i),top[++tmp]=i;
    while (!que.empty())
    {
        int x = que.front();
        que.pop();
        for (register int i=point[x];i;i=nxt[i])
        {
            int p = to[i];
            if (dis[p]==dis[x]+val[i])
            {
            in[p]--;
            if (in[p]==0)
            {
                que.push(p);
                top[++tmp]=p; 
            }
            }
        }
    }
}

void dp() //之所以要拓扑排序,是因为在更新最短路上的点的时候,有一个先后顺序 就好比是u->v 必须先算u,再算v 
{
    f[1][0]=1;
    for (register int i=0;i<=k;i++)
    {
          //更新最短路上的点 
         // cout<<1<<endl;
         for (register int j=1;j<=tmp;++j)
         {
         	int x=top[j];
         	for (register int ii=point[x];ii;ii=nxt[ii])
         	  {
         	  	int p= to[ii];
         	  	if (dis[p]==dis[x]+val[ii]) f[p][i]=(f[x][i]+f[p][i])%mod;
         	  }
         }
         for (register int x=1;x<=n;++x)
           for (register int ii=point[x];ii;ii=nxt[ii])
         	  {
         	  	int p= to[ii];
         	  	int now = dis[x]+val[ii]+i-dis[p];
         	  	if (dis[p]!=dis[x]+val[ii] && now<=k) f[p][now]=(f[x][i]+f[p][now])%mod; 
         	  }
    }
//	cout<<2<<endl;
}

int main()
{
  cin>>t;
  while (t--)
  {
  	 init();
  	 n=read();m=read();k=read();mod=read();
  	 for (register int i=1;i<=m;++i)
  	 {
  	 	x[i]=read();
  	 	y[i]=read();
  	 	w[i]=read();  	 
     }
     for (register int i=1;i<=m;++i)
     addedge(y[i],x[i],w[i]);
     dijkstran(n);
     init();
     for (register int i=1;i<=m;++i)
     addedge(x[i],y[i],w[i]);
     dijkstra(1);
     //for (int i=1;i<=n;i++)
      // cout<<dis[i]<<" ";
     //cout<<endl;
     //for (int i=1;i<=n;i++)
     ////  cout<<disn[i]<<" ";
     //cout<<endl;
     tpsort();
     for (register int i=1;i<=n;++i)
       if (in[i]>0 && dis[i]+disn[i]<=dis[n]+k) 
       {
       	 printf("-1\n");
       	 flag=false;
       	 break;
       }
     if (!flag) continue;
     dp();
     int ans=0;
     for (register int i=0;i<=k;++i)
       ans=(ans+f[n][i])%mod;
     printf("%d\n",ans);
  }
  return 0;
}

QAQ这种写法,代码常数特别大,需要卡常,才能A掉

QWQ


这个题的另一个做法,记忆化搜索


正着建图求好dis后,

然后反向建图,将f[1][0]=1

从n开始做记忆化搜索


f[x][k]=f[x][k]+f[to[i]][k-(dis[to[i]]+val[i]-dis[x])]


一定一定一定一定注意!!!!!!!

这种方法要将f数组初始化成-1

在dfs 的时候

用一个中间变量保存f的值

最后再赋值,详情看代码吧QWQ被这个点坑了很久

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>

using namespace std;

inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}


const int maxn = 2e5+1e2;
const int maxm = 1e6+1e2;

int point[maxn],nxt[maxm],to[maxm],val[maxm];
int dis[maxn],vis[maxn];
int f[maxn][61];
int n,m,k,mod;
int x[maxm],y[maxm],w[maxm];
queue<int> q;
int g[maxn][61];
bool flag;
int kk;
int cnt;

void addedge(int x,int y,int w)
{
	nxt[++cnt]=point[x];
	to[cnt]=y;
	val[cnt]=w;
	point[x]=cnt;
} 

void init()
{
	cnt=0;
	flag=true;
	memset(point,0,sizeof(point));
	memset(f,-1,sizeof(f));
	memset(g,0,sizeof(g));
}

int spfa(int s)
{
	memset(vis,0,sizeof(vis));
	memset(dis,127/3,sizeof(dis));
	vis[s]=1;
	dis[s]=0;
	q.push(s);
	while (!q.empty())
	{
		int x = q.front();
	    q.pop();
		vis[x]=0;
		for (int i=point[x];i;i=nxt[i])
		{
			int p = to[i];
			if (dis[p]>dis[x]+val[i])
			{
				dis[p]=dis[x]+val[i];
				if (!vis[p])
				{
					vis[p]=1;
					q.push(p);
				}
			}
		}
	}

}

int dfs(int x,int k)
{
	//cout<<1<<endl;
	int ret=0;
	if (g[x][k]) {
		flag=false;
		return 0;
	}
	if (f[x][k]!=-1) return f[x][k]; //这里如果写成if (f[x][k]) 会re 因为f[x][k]==0的状态有很多 
	g[x][k]=1;
	if (!flag) return 0;
	for (int i=point[x];i;i=nxt[i])
	{
		int p = to[i];
		int cnt=k-(dis[p]+val[i]-dis[x]);
		if (cnt<0 || cnt>kk) continue;
		  ret=(ret+dfs(p,cnt))%mod;
		if (!flag) return 0;
	}
	if (!flag) return 0;
	g[x][k]=0;
	if (x==1 && k==0) 
	{
	  f[x][k]=1;
    }
    else
     f[x][k]=ret;
	return f[x][k];
}

int t;

int main()
{
  cin>>t;
  while (t--)
  {
     init();
     n=read(),m=read(),k=read(),mod=read();
     for (int i=1;i<=m;i++)
     {
     	x[i]=read();
     	y[i]=read();
     	w[i]=read();
     	addedge(x[i],y[i],w[i]);
	 }
	 spfa(1);
	 //for (int i=1;i<=n;i++) cout<<dis[i]<<" ";
	// cout<<endl;
	 init();
	 for (int i=1;i<=m;i++) addedge(y[i],x[i],w[i]);
	 int ans=0;
	 kk=k;
	 for (int i=0;i<=k;i++)
	 {
	 	//memset(g,0,sizeof(g));
	 	int tmp = dfs(n,i)%mod;
	 	if (!flag) break;
	 	ans=(ans+tmp)%mod;
	 }
	 
	 if (!flag) {
	 	cout<<-1<<endl;
	 	continue;
	 }
	 else
	 {
	 	printf("%d\n",ans);
	 }
  }  
  return 0;
}

猜你喜欢

转载自blog.csdn.net/y752742355/article/details/80298096