1. 程式人生 > >noip2017逛公園

noip2017逛公園

https://www.zybuluo.com/ysner/note/1330542

題面

無人不知,無人不曉。

解析

這絕對是\(noip2017\)得分最難的題目。

\(30pts\)演算法

最短路計數?跑\(Dijstra\)的同時,順便\(DP\)算下方案數就好。
於是設\(f[i]\)表示由\(1\)\(i\)點的方案數。

#define pi pair<int,int>
#define mk make_pair
#define fi first
#define se second
priority_queue<pi,vector<pi>,greater<pi> >Q;
il void Dijstra()
{
  fp(i,1,n) dis[i]=1e9,vis[i]=0;
  dis[1]=0;Q.push(mk(0,1));
  while(!Q.empty())
    {
      re int u=Q.top().se;Q.pop();
      vis[u]=1;
      for(re int i=h[u];i+1;i=e[i].nxt)
      {
        re int v=e[i].to;
        if(dis[v]>dis[u]+e[i].w)
    {
      dis[v]=dis[u]+e[i].w;f[v]=f[u];
      Q.push(mk(dis[v],v));
    }
    else if(dis[v]==dis[u]+e[i].w) (f[v]+=f[u])%=mod;
      }
      while(!Q.empty()&&vis[Q.top().se]) Q.pop();
    }
}

\(70pts\)演算法

考慮到\(k\)很小,可以把\(k\)加入狀態。
於是設\(dp[j][i]\)表示到達\(i\)點,路徑長度比最短路長\(j\)的方案數。
看這個狀態就知道我們要預處理最短路。
狀態都會設,正確轉移估計沒多少人會了

顯然轉移式為
\[dp[j][u]=\sum dp[dis_u+j-e[i].w-dis_v][v]\]

但是題目背景是張圖,\(DP\)時是要考慮轉移順序的。

由於在轉移過程中,\(j\)這一維一定是單調不降的,那麼肯定是由\(j\)小的狀態往\(j\)大的狀態轉移。
但是這樣還不止,你會發現只這麼寫會過不了某個樣例。。。而且這個樣例的\(k=0\)

。。。

注意到\(j\)相等的轉移(在最短路上的轉移)是有鍋的。
其實這一型別的轉移,我們都是由\(dis\)值小的轉移到\(dis\)值大的,這一點看看\(30pts\)最短路計數的轉移就應該明白。
那麼這裡也一樣,給所有點依\(dis\)排個序,這就是轉移的拓撲序
然後像拓撲排序那樣轉移就行,由一個節點向與其相連的多個結點轉移。

於是就有\(70pts\)了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define ll long long
#define re register
#define il inline
#define pi pair<int,int>
#define mk make_pair
#define fi first
#define se second
#define fp(i,a,b) for(re int i=a;i<=b;++i)
#define fq(i,a,b) for(re int i=a;i>=b;--i)
using namespace std;
const int N=1e5+100;
int f[55][N],n,m,k,mod,h[N],cnt,dis[N],ans,sta[N];
bool vis[N];
struct Edge{int to,nxt,w;}e[N<<1];
il void add(re int u,re int v,re int w){e[++cnt]=(Edge){v,h[u],w};h[u]=cnt;}
il int gi()
{
  re int x=0,t=1;
  re char ch=getchar();
  while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
  if(ch=='-') t=-1,ch=getchar();
  while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  return x*t;
}
il void Dijstra()
{
  priority_queue<pi,vector<pi>,greater<pi> >Q;
  fp(i,1,n) dis[i]=1e9,vis[i]=0;
  dis[1]=0;Q.push(mk(0,1));
  while(!Q.empty())
    {
      re int u=Q.top().se;Q.pop();
      vis[u]=1;
      for(re int i=h[u];i+1;i=e[i].nxt)
      {
        re int v=e[i].to;
        if(dis[v]>dis[u]+e[i].w)
    {
      dis[v]=dis[u]+e[i].w;
      Q.push(mk(dis[v],v));
    }
      }
      while(!Q.empty()&&vis[Q.top().se]) Q.pop();
    }
}
il void Dp()
{
  fp(j,0,k)
  fp(o,1,n)
    {
      re int u=sta[o];
    for(re int i=h[u];i+1;i=e[i].nxt)
      {
    re int v=e[i].to;
        if(dis[u]+j-dis[v]+e[i].w>=0&&dis[u]+j-dis[v]+e[i].w<=k)
    (f[dis[u]+j-dis[v]+e[i].w][v]+=f[j][u])%=mod;
      }
    }
}
il bool cmp(re int x,re int y){return dis[x]<dis[y];}
int main()
{
  re int T=gi();
  while(T--)
  {
    memset(h,-1,sizeof(h));memset(f,0,sizeof(f));cnt=0;ans=0;
    n=gi();m=gi();k=gi();mod=gi();
    fp(i,1,m)
      {
    re int u=gi(),v=gi(),w=gi();
    add(u,v,w);
      }
    Dijstra();
    fp(i,1,n) sta[i]=i;sort(sta+1,sta+1+n,cmp);
    f[0][1]=1;
    Dp();
    fp(i,0,k) (ans+=f[i][n])%=mod;
    printf("%d\n",ans);
  }
  return 0;
}

\(100pts\)演算法

\(0\)邊時轉移又鍋了。
因為會有\(dis\)\(j\)同時相等的點,它們誰先轉移,誰後轉移需要進一步確定。
仔細想想,發現按拓撲序轉移就行了,畢竟只有這樣才滿足無後效性。

那就進行一遍拓撲排序(只有\(0\)邊提供入度,因為針對\(dis\)\(j\)同時相等的點)。
如果跑完後還有點沒進拓撲序,說明有\(0\)環。
如果有合法路線經過\(0\)環,就可以\(puts("-1")\)了(但實際上GGF的資料中,只要有零環就可以puts)。
然後再兩重標準給點排序,再照搬\(70pts\)轉移即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define ll long long
#define re register
#define il inline
#define pi pair<int,int>
#define mk make_pair
#define fi first
#define se second
#define fp(i,a,b) for(re int i=a;i<=b;++i)
#define fq(i,a,b) for(re int i=a;i>=b;--i)
using namespace std;
const int N=1e5+100;
int f[55][N],n,m,k,mod,h[N],cnt,dis[N],ans,sta[N],d[N],seq[N],top;
bool vis[N],lab[N];
struct Edge{int to,nxt,w;}e[N<<1];
il void add(re int u,re int v,re int w){e[++cnt]=(Edge){v,h[u],w};h[u]=cnt;}
il int gi()
{
  re int x=0,t=1;
  re char ch=getchar();
  while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
  if(ch=='-') t=-1,ch=getchar();
  while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  return x*t;
}
il void Dijstra()
{
  priority_queue<pi,vector<pi>,greater<pi> >Q;
  fp(i,1,n) dis[i]=1e9,vis[i]=0;
  dis[1]=0;Q.push(mk(0,1));
  while(!Q.empty())
    {
      re int u=Q.top().se;Q.pop();
      vis[u]=1;
      for(re int i=h[u];i+1;i=e[i].nxt)
      {
        re int v=e[i].to;
        if(dis[v]>dis[u]+e[i].w)
    {
      dis[v]=dis[u]+e[i].w;
      Q.push(mk(dis[v],v));
    }
      }
      while(!Q.empty()&&vis[Q.top().se]) Q.pop();
    }
}
il void Dp()
{
  fp(j,0,k)
  fp(o,1,n)
    {
      re int u=sta[o];
    for(re int i=h[u];i+1;i=e[i].nxt)
      {
    re int v=e[i].to;
        if(dis[u]+j-dis[v]+e[i].w>=0&&dis[u]+j-dis[v]+e[i].w<=k)
    (f[dis[u]+j-dis[v]+e[i].w][v]+=f[j][u])%=mod;
      }
    }
}
il int Toposort()
{
  queue<int>Q;
  fp(i,1,n) if(!d[i]) Q.push(i);
  while(!Q.empty())
    {
      re int u=Q.front();Q.pop();seq[u]=++top;
      for(re int i=h[u];i+1;i=e[i].nxt)
    if(!e[i].w)
      {
        re int v=e[i].to;
        if(!--d[v]) Q.push(v);
      }
    }
  fp(i,1,n) if(d[i]) return 0;
  return 1;
}
il bool cmp(re int x,re int y){return dis[x]<dis[y]||(dis[x]==dis[y]&&seq[x]<seq[y]);}
int main()
{
  re int T=gi();
  while(T--)
  {
    memset(h,-1,sizeof(h));memset(f,0,sizeof(f));memset(d,0,sizeof(d));
    cnt=0;ans=0;top=0;
    n=gi();m=gi();k=gi();mod=gi();
    fp(i,1,m)
      {
    re int u=gi(),v=gi(),w=gi();
    add(u,v,w);if(!w) ++d[v];
      }
    if(!Toposort()) {puts("-1");continue;}
    Dijstra();
    fp(i,1,n) sta[i]=i;sort(sta+1,sta+1+n,cmp);
    f[0][1]=1;
    Dp();
    fp(i,0,k) (ans+=f[i][n])%=mod;
    printf("%d\n",ans);
  }
  return 0;
}