nyoj-895How many ways【图上dp+拓扑序】

来源:互联网 发布:淘宝如何买报销假车票 编辑:程序博客网 时间:2024/04/29 18:40

How many ways?

时间限制:1000 ms  |  内存限制:65535 KB
难度:4
描述

给一个 n 个点 m 条边的有向无环图,问从点 1 到点 n 一共有多少条路径?(结果对 10007 取模)

输入
多组测试数据。
第一行两个数,n 和 m(2<=n<=100,2<=m<=10000)。
输出
每组测试数据输出一行,表示从点 1 到点 n 的方案数对 10007 取模后的值。

首先,我分析这个问题是在图上的dp。

用dp[i][j]表示 从i点->j点的最大路径数

dp[0][j]记录到j点总的最大路径数

g[i][j]表示i点到j点的的边数

状态转移方程为:

dp[u][v]=Max(dp[0][u]*g[u][v]);

dp[0][v]=∑(Max(dp[u][v]));u为所有能连接到v的点

总体意思就是一个点的最大路径数为 sum v= ∑(  sum u *  count_e ); v的最大路径数 = 所有的 (v的最大路径数 * v到u的边数)之和

接下来就是枚举所有边,更新dp值。一个点的dp值被改变了,那么它连接的点及其以后的dp值都是可变的。所以不断的枚举所有边更新dp值,直到再没有点更新为止。

#include <stdio.h>#include <stdlib.h>#include <string.h>#define Max_V 110long long int dp[Max_V][Max_V];long long int g[Max_V][Max_V];int n,m;void init(){memset(dp,0,sizeof(dp));memset(g,0,sizeof(g));}int main(){int i;while(~scanf("%d%d",&n,&m)){init();int u,v;for(i=0;i<m;i++){scanf("%d%d",&u,&v);g[u][v]++;}dp[0][1]=1;//从1点出发bool flag=true;while(flag){flag=false;//枚举所有边for(u=1;u<=n;u++)for(v=1;v<=n;v++){if(dp[u][v]<g[u][v]*dp[0][u]){flag=true;//dp[0][v]记录最大路径数,//更新前的dp[u][v]无效后就得减去//再加上更新后的dp[u][v]dp[0][v]-=dp[u][v];dp[u][v]=g[u][v]*dp[0][u];dp[0][v]+=dp[u][v];}}}printf("%lld\n",dp[0][n]%10007);}return 0;}

但是,问题就来了。数据范围十分的大,远超过long long int。因为求模之后比较大小是无效的。那么怎么解决这个问题呢?


关键字:有向无环图

联想:DAG(拓扑排序)

由AOV网构造拓扑序列的拓扑排序算法主要是循环执行以下两步,直到不存在入度为0的顶点为止。

重复执行
(1) 选择一个入度为0的顶点;
(2) 从网中删除此顶点及所有出边。

这样就不断列举出入度为0的点。

承接上面的思路。一个点的入度为0,是不是就说明这个点就不会再被其它点更新了?是不是就说明这个点已经更新到底了?

那么 dp[0][u]就不会再改变了。dp[u][v]也随之固定。这样问题就变成了一个单纯的递推式,并且dp[]数组可以从二维优化到一维:dp[v]+=dp[u]*g[u][v];因为dp[u]固定(u入度为0,所以dp[u]不会再被更改,所以dp[u]当前为最大路径数),g[u][v]固定。

就不需要再判断大小了。就可以利用取模运算了。

所以在此点更新了其它点之后,此点及其出边就可以删除了,这样又会创造出入度为0的点。

#include <stdio.h>#include <string.h>#include <stdlib.h>#define Max_V 110#define mod 10007int n,m;int indeg[Max_V];int g[Max_V][Max_V];int dp[Max_V];bool vis[Max_V];void init(){memset(indeg,0,sizeof(indeg));memset(g,0,sizeof(g));memset(dp,0,sizeof(dp));memset(vis,0,sizeof(vis));}int main(){int i;while(~scanf("%d%d",&n,&m)){init();int u,v;for(i=0;i<m;i++){scanf("%d%d",&u,&v);g[u][v]++;indeg[v]++;}dp[1]=1;while(true){for(u=1;u<=n;u++)//寻找未删除的0度顶点if(!indeg[u]&&!vis[u])break;if(u>=n)break;vis[u]=1;//标记数组表示删除该点for(v=1;v<=n;v++)if(g[u][v]){dp[v]+=dp[u]*g[u][v];//(a+b)%m=(a%m+b%m)%mdp[v]%=mod;indeg[v]-=g[u][v];//减少入度表示删除此点的出边}}printf("%d\n",dp[n]);}return 0;}


0 0