两条不重复路径DP

Author Avatar
Axell 1月 27, 2019
  • 在其它设备中阅读本文章

问题

给定一个平面,以及一些点之间的边,求出一条最长的环形路径

解决方案

一般可以使用DP解决,但是为了避免重复路径,需要控制状态转移方程的条件和边界

一般问题

给定n个点,以及两点之间的边,求出起点到终点的两条不重复路径最多经过多少点
DP方程: Fi=max{Fi+1,Fk+1}
注意:此时DP顺着推会更加清晰,边界控制也相对简单

#include <bits/stdc++.h>
using namespace std;

string a[105],x,y;
int head[105],f[105][105],Next[20005],en[20005],cnt,n,v;

void add(int x,int y){
    cnt++;
    Next[cnt]=head[x];
    head[x]=cnt;
    en[cnt]=y;
}

int main(){
    cin>>n>>v;
    for (int i=1;i<=n;++i) cin>>a[i];
    memset(f,0xcf,sizeof f);
    for (int i=1;i<=v;++i){
        cin>>x>>y;
        int l,r;
        for (int i=1;i<=n;++i) if (a[i]==x) l=i;
        for (int i=1;i<=n;++i) if (a[i]==y) r=i;
        add(l,r);
        add(r,l);
    }
    f[1][1]=0;
    for (int i=1;i<=n;++i){
        for (int j=1;j<=n;++j){
            if (j==i && i!=1) continue;  //因为不能重复,但是i=j=1时也能转移
            for (int k=head[i];k;k=Next[k]){
                if (en[k]>i && en[k]>j ||en[k]==n) f[en[k]][j]=max(f[en[k]][j],f[i][j]+1);
            } //找到接在i后的一个点,注意K点必须在ij的后面,不然情况会重复,不要忘记ij其中一个到达N时,也要继续转移,不然最终答案为-INF
            for (int k=head[j];k;k=Next[k]){
                if (en[k]>i && en[k]>j ||en[k]==n) f[i][en[k]]=max(f[i][en[k]],f[i][j]+1);
            }
        }
    }
    cout<<f[n][n]<<endl;
    return 0;
}

有条件的转移问题

HERE
设DPi为上面路径达到I,下面的路径达到J时的最短路径长度之和,其它与上面相同,但当I,J为特殊点时,只有一种转移方案

#include <bits/stdc++.h>
using namespace std;

struct node{
    int x,y;
}a[1005];
int n,tb,eb;
double f[1005][1005],ans;

double get(int st,int ed){
    int t=a[st].x-a[ed].x;
    int tt=a[st].y-a[ed].y;
    return sqrt(t*t+tt*tt);
}

int main(){
    cin>>n>>tb>>eb;
    for (int i=1;i<=n;++i) scanf("%d %d",&a[i].x,&a[i].y);
    tb++;eb++;
    for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) f[i][j]=1e7;
    f[1][1]=0;
    for (int i=1;i<=n;++i){
        for (int j=1;j<=n;++j){
            if (i!=j || i==1){ //注意转移方程的条件 I!=J
                int k=max(i,j)+1;
                if (k==n+1) k=n;
                if (k!=tb){
                    f[i][k]=min(f[i][k],f[i][j]+get(j,k)); //判断能否转移过来
                }
                if (k!=eb){
                    f[k][j]=min(f[k][j],f[i][j]+get(i,k));
                }
            }
        }
    }
    printf("%.2lf",f[n][n]);
    return 0;
}

二维平面内的转移

例: 传纸条
DPik表示两条路径分别到达(i,j),(k,l)的最大值,转移基本相同

#include <bits/stdc++.h>
using namespace std;

int n,m,a[55][55],f[55][55][55][55];

int main(){
    cin>>m>>n;
    swap(m,n);
    for (int i=1;i<=n;++i){
        for (int j=1;j<=m;++j){
            scanf("%d",&a[i][j]);
        }
    }
    for (int i=1;i<=n;++i){
        for (int j=1;j<=m;++j){
            for (int k=1;k<=n;++k){
                for (int l=j+1;l<=m;++l){
                    if (i!=j || k!=m){
                        f[i][j][k][l]=max( max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]) , max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]) ) +a[i][j]+a[k][l];
                    }
                }
            }
        }
    }
    cout<<f[n][m-1][n-1][m]<<endl;
    return 0;
}

由于循环变量中,只要知道了其中3个,便可求出第4个,因此可以降一维复杂度

#include <bits/stdc++.h>
using namespace std;

int n,m,a[55][55],f[105][55][55];

int main(){
    cin>>m>>n;
    swap(m,n);
    for (int i=1;i<=n;++i){
        for (int j=1;j<=m;++j){
            scanf("%d",&a[i][j]);
        }
    }
    for (int k=1;k<=n+m-1;++k)
        for (int i=1;i<=n;++i){
            for (int j=1;j<=n;++j){
                if (k-i+1<1 || k-j+1<1) continue;
                if (i!=j) f[k][i][j]=max( max(f[k-1][i][j],f[k-1][i-1][j]) , max(f[k-1][i][j-1],f[k-1][i-1][j-1]))+a[i][k-i+1]+a[j][k-j+1];
            }
        }
    cout<<f[n+m-2][n-1][n]<<endl;
    return 0;
}

注意最后的输出状态

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。

本文链接:https://hs-blog.axell.top/archives/84/