Analysis
首先看到这道题,暴力30分很好打
针对
的情况,我们直接最短路计数就可以了
然后还是从k入手,发现k最多只有50,是个突破口
我们可以试着每次枚举k,限制路径长度然后计数,最后相加
如果令
表示当前在u这个点,与 u 到 1 的最短路相差 j 的路径条数
那么怎么更新这个状态呢?
令 v 能够到 u 那么可以得到:
是不是有点懵?
没关系,我们来推导一下
假设现在到
的路径比dis[v]大k,并且到
的路径比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提出了解决方案
这个程序出现的问题,就是针对样例那种情况,由于我们先将
赋了初值,就判不了包含1的0环了
既然这样,我们就做两遍dfs,第一遍的时候只是判0环,不计数(也就是不给
赋初值,第二遍的时候再赋值计数
代码:
#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的方法