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; }