How many ways?
-
描述
-
给一个 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 110
long 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 10007
int 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)%m
dp[v]%=mod;
indeg[v]-=g[u][v];//减少入度表示删除此点的出边
}
}
printf("%d\n",dp[n]);
}
return 0;
}