$E=mc^{2}$
很多问题都可以转化为DAG上的最长(短)路路径,最多(少)路径数(路径的权值为1)
对于状态d[i]的设置可以有两种:
1.d[i]表示从i出发的最长路
一般这种时候会考虑打印路劲,在出发之后会同时用一个数组来记录路径,而且这种方式一般通过回溯找到最大值。这种方法多数情况下不被推荐使用(偶尔用来取巧)(没有固定起点终点的情况下找到从任意点出发的总长并且可以记录从这个点出发到底端一步一步的路径),因为一般情况下都会有一个终止状态,用这种方法很难找到终止状态。
2.d[i]表示以i结束的最长路
缺点有时候很容易枚举从某个结点i出发到达的所有边(i,j),却不容易枚举(j,i),即所有到达j的结点(因为是有向图),对应的过程不可逆。传统递推是对于每个i,所有边。而解决该问题的办法是,对于每个结点i,枚举(i,j),但是操作对象确实对j,对j重复操作更新d[i]=max(d[j],d[i]+1)。这个式子会在满足状态转移的情况下,重复更新。
下面可以考虑两个问题:
(1)不固定起点终点的DAG最长路径
(2)固定终点的最长路径
(1)这是可以用1完成的问题
1 void DP(int a){ 2 if(visited[a]) 3 return d[a]; 4 visited[a] = true; 5 for(int i=1;i<=n;i++){ 6 if(edge[a][i]){//表示有向联通 7 //d[a] = max(d[a],d[i]+edge[a][i]); //不用加入路径就这样 8 //假如要加入路径 9 if(d[a]<D(i)+edge[a][i]){ 10 d[a] = D(i)+edge[a][i]; 11 next[a] = i; //每次都会更新,直到最小,可以用于打印路径 12 } 13 } 14 } 15 return d[a]; 16 }
这个dp只能从这个点有向方向出去进行DFS然后回溯,而不能搜到它的上级,所以很多题目要对所有点搜一次,当重复搜索的时候,只用return一个值不会占用很多时间。而且这个可以保证得到的最长路径的情况下(假如有多个相同的最大值),序号和是最小的。因为每次都是从较小序号开始回溯,而且只有更大的情况下才会更新,所以能保证最小。
for(int i=1;i<=n;i++) DP(i);
如果要用2完成,可以确定某一条最大路径,但是比较难得到相长度同路径中的最小(大)序列。原因见(2)中代码。
(2)因为要固定了终点,所以用1处理可能会比较麻烦(大多数情况下都是用2),因为不知道是否最大值是到这个终点。所以设置d[i]为到达i时的最大值。
如果是要固定起点固定终点,初始化起点位置为0就可以了,然后把无关的边全部去掉,清理掉图来变成只固定终点。
1 void DP(int a){ 2 if(visited[a]) 3 return d[a]; 4 visited[a] = true; 5 for(int i=1;i<=n;i++){ 6 if(edge[i][a]){ 7 //d[a] = max(d[a],DP(i)+edge[i][a]); 8 //感觉也可以加入路径 9 if(d[a]<DP(i)+edge[i][a]){ 10 d[a] = DP(i)+edge[i][a]; 11 before[a] = i; //也会有状态更新 //能否通过这个判断最小序列?不行,它同样是从最后一位开始取最优的 12 } 13 } 14 } 15 return d[a]; 16 }
可以打印路径但是不能打印序列最小
1 void print_ans(int i){ //逆序打印,可以多加一个数组来变成正序 2 printf("%d",i); 3 for(int j=1;j<=n;j++){ 4 if(d[i]==d[j]+edge[i][j]){ 5 printf_ans(j); 6 break; //防止多打东西 7 } 8 } 9 }
这种方法无法打印最小序列,(最小序列:比如出现顺序是1 2 3 5,则为 1235,出现顺序为2 1 3 4,则为2134),因为它是从后往前面判断的,无法(判断)每次取最前面位的最小情况,因为每一位的权重不一样,越前面权重越大,但是反而在后面取到最优情况可能前面就取不到了。