• 方格取数(多线程dp,深搜)


    https://www.luogu.org/problem/P1004

    题目描述

    设有N×N的方格图(N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。如下图所示(见样例):

    某人从图的左上角的A点出发,可以向下行走,也可以向右走,直到到达右下角的B点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
    此人从A点到B点共走两次,试找出2条这样的路径,使得取得的数之和为最大。

    输入格式

    输入的第一行为一个整数N(表示N×N的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的0表示输入结束。

    输出格式

    只需输出一个整数,表示2条路径上取得的最大的和。

    输入输出样例

    输入

    8
    2 3 13
    2 6  6
    3 5  7
    4 4 14
    5 2 21
    5 6  4
    6 3 15
    7 2 14
    0 0  0

    输出

    67

    第一种方法(深搜):

    这道题深搜的最优方法就是两种方案同时从起点出发。因为如果记录完第一种方案,再计算第二种方案,不可控的因素太多了,大多都不是最优解→_→,

    但两种方案同时执行就行,因为这可以根据当前的情况来判断最优。

    总的来说,每走一步都会有四个分支(你理解成选择或者情况也可以):

    1、两种都向下走

    2、第一种向下走,第二种向右走

    3、第一种向右走,第二种向下走

    4、两种都向右走

    每走一步走枚举一下这四种情况,因为在每个点的方案具有唯一性(也就是在某个点走到终点的取数方案只有一个最优解,自己理解一下),所以我们可以开一个数组来记录每一种情况,当重复枚举到一种情况时就直接返回(对,就是剪枝),大大节省了时间(不然会超时哦~)。深搜和DP的时间复杂度时一样的!

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <iostream>
     4 #include <string>
     5 #include <math.h>
     6 #include <algorithm>
     7 #include <queue>
     8 #include <set>
     9 #include <math.h>
    10 const int INF=0x3f3f3f3f;
    11 typedef long long LL;
    12 const int mod=1e9+7;
    13 const double PI=acos(-1);
    14 const int maxn=1e5+10;
    15 using namespace std;
    16 
    17 int n;
    18 int A[15][15];
    19 int dp[11][11][11][11];
    20 
    21 int DFS(int x1,int y1,int x2,int y2)//两种方案同时执行,表示当第一种方案走到x1,y1,第二种方案走到x2,y2时到终点取得的最大数 
    22 {
    23     if(dp[x1][y1][x2][y2]!=-1)//如果这种情况已经被记录过了,直接返回,节省时间
    24         return dp[x1][y1][x2][y2];
    25     if(x1==n&&y1==n&&x2==n&&y2==n)//如果两种方案都走到了终点,返回结束 
    26         return 0;
    27     int MAX=0;
    28     //如果两种方案都不在最后一行,就都往下走,统计取得的数,如果有重复,就减去一部分
    29     if(x1<n&&x2<n)
    30         MAX=max(MAX,DFS(x1+1,y1,x2+1,y2)+A[x1+1][y1]+A[x2+1][y2]-A[x1+1][y1]*(x1+1==x2+1&&y1==y2));
    31     
    32     //如果第一种方案不在最后一行,第二种方案不在最后一列,第一种就向下走,第二种就向右走, 统计取得的数,如果有重复,就减去一部分
    33     if(x1<n&&y2<n)
    34         MAX=max(MAX,DFS(x1+1,y1,x2,y2+1)+A[x1+1][y1]+A[x2][y2+1]-A[x1+1][y1]*(x1+1==x2&&y1==y2+1));
    35     
    36     //如果第一种方案不在最后一列,第二种方案不在最后一行,第一种就向右走,第二种就向下走, 统计取得的数,如果有重复,就减去一部分
    37     if(x2<n&&y1<n)
    38         MAX=max(MAX,DFS(x1,y1+1,x2+1,y2)+A[x1][y1+1]+A[x2+1][y2]-A[x1][y1+1]*(x1==x2+1&&y1+1==y2));
    39     
    40     //如果第一种方案和第二种方案都不在最后一列,就都向右走,统计取得的数,如果有重复,就减去一部分
    41     if(y1<n&&y2<n)
    42         MAX=max(MAX,DFS(x1,y1+1,x2,y2+1)+A[x1][y1+1]+A[x2][y2+1]-A[x1][y1+1]*(x1==x2&&y1+1==y2+1));
    43     //对最后那个 A[x1][y1+1]*(x1==x2&&y1+1==y2+1))的解释:这个是用来判断两种方案是不是走到了同一格的
    44     //如果是真,就返回1,否则返回0,如果是1的话,理所当然的可以减去A[x1][y+1]*1,否则减去A[x1][y1+1]*0相当于不减,写得有点精简,省了4个if 
    45     dp[x1][y1][x2][y2]=MAX;//记录这种情况 
    46     return MAX;//返回最大值
    47 }
    48 
    49 int main()
    50 {
    51     scanf("%d",&n);
    52     //将记录数组初始化成-1,因为可能出现取的数为0的情况,如果直接判断f[x1][y1][x2][y2]!=0(见DFS第一行)
    53     //可能出现死循环而导致超时,细节问题 
    54     memset(dp,-1,sizeof(dp));
    55     int a,b,c;
    56     while(~scanf("%d %d %d",&a,&b,&c)&&(a||b||c))//读入
    57     {
    58         A[a][b]=c;
    59     }
    60     int ans=0;
    61     ans=DFS(1,1,1,1)+A[1][1];//因为DFS中没有考虑第一格,即A[1][1],所以最后要加一下
    62     printf("%d
    ",ans);
    63     return 0;
    64 }

     第二种方法(DP):

    我们发现题目的数据规模不大,n<=9,所以我们可以考虑用DFS或者DP都可以

    这题,是四维动规的模板题,和P1006传纸条基本相似。

    我们记f[i][j][k][l]表示第1条路线的i,j走法和第2条路线的k,l走法

    显然我们可以两个人一起走,复杂度最多就是9*9*9*9=6561

    我们考虑两个人同时走,就相当于数字三角形。

    第1条路线只可能是从i-1,j或者i,j-1转移,第2条路线也只可能从k-1,l或者k,l-1转移

    而且因为是2个人走,如果走到一点我们的那个点就要打标记说那点上面的值为0

    所以我们得到了我们的动归方程(注意:万一i,j与k,l相同这是要小心的!)

    状态转移方程为:

    dp[i][j][k][l]=max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1],dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])+A[i][j]+A[k][l];

    不过要判断i=k&&j=l的情况。

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <iostream>
     4 #include <string>
     5 #include <math.h>
     6 #include <algorithm>
     7 #include <queue>
     8 #include <set>
     9 #include <math.h>
    10 const int INF=0x3f3f3f3f;
    11 typedef long long LL;
    12 const int mod=1e9+7;
    13 const double PI=acos(-1);
    14 const int maxn=1e5+10;
    15 using namespace std;
    16 
    17 int n;
    18 int A[15][15];
    19 int dp[11][11][11][11];//dp[i][j][k][l]表示两个人同时走,一个走i,j 一个走k,l
    20 
    21 int main()
    22 {
    23     scanf("%d",&n);
    24     int a,b,c;
    25     while(~scanf("%d %d %d",&a,&b,&c)&&(a||b||c))//读入
    26     {
    27         A[a][b]=c;
    28     }
    29     for(int i=1;i<=n;i++){
    30         for(int j=1;j<=n;j++){
    31             for(int k=1;k<=n;k++){
    32                 for(int l=1;l<=n;l++){
    33                     dp[i][j][k][l]=max(dp[i-1][j][k-1][l],max(dp[i][j-1][k-1][l],max(dp[i-1][j][k][l-1],dp[i][j-1][k][l-1])))+A[i][j]+A[k][l];
    34                     if(i==k&&j==l)
    35                         dp[i][j][k][l]-=A[i][j];
    36                 }
    37             }
    38         }
    39     }
    40     printf("%d
    ",dp[n][n][n][n]);
    41     return 0;
    42 }

    思路:

    相当于两个人同时走,求最大值,

    dp[i][j][x][y]:=第1个人走到坐标(i,j)  第2个人走到坐标(x,y) 时,最大的值

    但无论如何,同一时间,1和2走的步数是一样多的,设为p,且i+j-2=p;x+y-2=p

    那么dp[i][j][x][y]可以降维为:dp[i][x][p]

    那么对于dp[i][x][p],就有4种情况:dp[i][x][p-1],dp[i-1][x-1][p-1],dp[i-1][x][p-1],dp[i][x-1][p-1]

    求出最大值再加上a[i][j],a[x][y]

    当i==x的时候,显然多加了一次

    最后,注意一定要初始化:dp[1][1][0]=a[1][1],不然会出错

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<string>
     5 #include<cstring>
     6 #include<queue>
     7 #include<stack>
     8 #include<cmath>
     9 #include<set>
    10 #include<map>
    11 #include<functional>
    12 using namespace std;
    13 #define ll long long
    14  
    15 typedef pair<ll,int>P;
    16 const ll INF=1e17+10;
    17 const int N=15,mod=1e9+7;
    18  
    19 int a[N][N];
    20 int dp[N][N][2*N];
    21  
    22 int main(){
    23     int n;
    24     scanf("%d",&n);
    25     int x,y,k;
    26     while(scanf("%d%d%d",&x,&y,&k)&&x+y+k!=0){
    27         a[x][y]=k;
    28     }
    29     dp[1][1][0]=a[1][1];
    30     for(int p=1;p<=2*n-2;p++){//步数
    31         for(int i=1;i<=n;i++){
    32             for(int x=1;x<=n;x++){
    33                 int j=p+2-i,y=p+2-x;
    34                 int tmp=max(dp[i][x][p-1],dp[i-1][x-1][p-1]);
    35                 tmp=max(tmp,dp[i-1][x][p-1]);
    36                 tmp=max(tmp,dp[i][x-1][p-1]);
    37                 dp[i][x][p]=tmp+a[i][j]+a[x][y];
    38                 if(i==x)dp[i][x][p]-=a[x][y];
    39             }
    40         }
    41     }
    42     printf("%d
    ",dp[n][n][2*n-2]);
    43 }

    下面粘出洛谷大佬 以墨 的题解:

    这题和传纸条很像

    一级

    显然,用f[i][j][k][l]表示第一个人走到(i,j),第二个人走到(k,l)的最优解,因为楼下讲了太多,我就不再赘述了。发个代码。

     1 #include<cstdio>
     2 #include<algorithm>
     3 using namespace std;
     4 struct point
     5 {
     6     int x,y,data;
     7 }p[100];
     8 int n,m,map[11][11],f[11][11][11][11];
     9 int main()
    10 {
    11     int i,j,k,l;
    12     scanf("%d",&n);
    13     while(1)
    14     {
    15         int a,b,c;
    16         scanf("%d%d%d",&a,&b,&c);
    17         if(!a&&!b&&!c)
    18             break;
    19         p[++m].x=a;
    20         p[m].y=b;
    21         p[m].data=c;
    22     }
    23     for(i=1;i<=m;i++)
    24         map[p[i].x][p[i].y]=p[i].data;
    25     for(i=1;i<=n;i++)
    26         for(j=1;j<=n;j++)
    27             for(k=1;k<=n;k++)
    28             {
    29                 l=i+j-k;
    30                 if(l<=0)
    31                     break;
    32                 f[i][j][k][l]=max(f[i-1][j][k-1][l],max(f[i-1][j][k][l-1],max(f[i][j-1][k-1][l],f[i][j-1][k][l-1])));
    33                 if(i==k&&j==l)
    34                     f[i][j][k][l]+=map[i][j];
    35                 else
    36                     f[i][j][k][l]+=map[i][j]+map[k][l];
    37             }
    38     printf("%d
    ",f[n][n][n][n]);
    39     return 0;
    40 }

    二级

    然而这题数据太弱,范围这么小偶也没想到,但是钻研的精神很可贵,还是要多想想。

    像楼下说的,用三维数组,优化空间。可以用路径长度代表阶段

    f(l,i,ii)表示当前路径长度为l(也就是说当前是第l个状态),第一个人走到(i,l-i)的位置,第二个人走到(ii,l-ii)的位置。

    具体原因楼下同样讲得太多了,因为后面还有料,现在就不多说了。


    三级

    看过楼下题解的同学应该能够发现,每一次的状态转移都只和上一个阶段有关,即第l-1个阶段。

    所以说可以用滚动数组,只记录当前阶段和2的余数的阶段即可。

    若l%2==0,则上一个状态l%2==1。反之亦然。

    这样空间复杂度从O(n^4)->O(n^3)->O(2n^2)


    四级

    其实这样已经差不多了,但是还可以继续优化。

    如果对01背包的以为写法非常熟悉的话......

    可以模仿,枚举i和ii的时候,如果倒着做,那么当前的值就是上一个阶段的值,连滚动数组都不用。

    空间复杂度再降到O(n^2)

    贴上四级的代码~~~

     1 #include<cstdio>
     2 #include<algorithm>
     3 using namespace std;
     4 struct point
     5 {
     6     int x,y,data;//记录每个点的位置和数值
     7 }p[100];
     8 int n,m,map[11][11],f[11][11];
     9 int main()
    10 {
    11     int i,ii,j,jj,l;
    12     scanf("%d",&n);
    13     while(1)
    14     {
    15         int a,b,c;
    16         scanf("%d%d%d",&a,&b,&c);
    17         if(!a&&!b&&!c)
    18             break;
    19         p[++m].x=a;
    20         p[m].y=b;
    21         p[m].data=c;
    22     }
    23     for(i=1;i<=m;i++)
    24         map[p[i].x][p[i].y]=p[i].data;
    25     for(l=2;l<=n*2;l++)//每个点最少横着竖着都走一格,最多都走n格就到终点
    26         for(i=l-1;i>=1;i--)//和前面说的一样,倒着做
    27             for(ii=l-1;ii>=1;ii--)
    28             {
    29                 j=l-i;jj=l-ii;//i+j=ii+jj=l
    30                 f[i][ii]=max(max(f[i][ii],f[i-1][ii-1]),max(f[i-1][ii],f[i][ii-1]))+map[i][j];
    31 //重点说明一下吧,这里省略了很多。如果i不减1,意思就是j-1,因为上一个阶段就是l-1嘛。如果ii-1,意思就是说jj不减1。
    32                 f[i][ii]+=map[ii][jj]*(i!=ii);
    33 //如果i==ii,其实就是(i==ii&&j==jj),因为和都是l嘛。如果走过一遍,第二遍走得到的值就是0(题目上说的)。
    34             }
    35     printf("%d
    ",f[n][n]);
    36 //输出意思是在路径长度为2*n的阶段,两遍都走到(n,n)的最优值。因为在这里(j=2*n-i=n,jj=2*n-ii=n),所以走到的就是(n,n)的位置
    37     return 0;
    38 }

    OVER ! 一个小小的建议,如果你想对这种两边一起做的DP有更深入的了解,可以做:传纸条和回文的路径

    后者很难!!!


     第三种方法(费用流,SPFA):

     (蒟蒻表示暂时不会,先粘大佬的代码,以后再看)

    SPFA

    简要介绍一下如何构图

    1. 拆点:因为每个方格只取一次,但要走两遍,因此我们考虑对于矩阵中每个方格拆为两个节点,一个为流入点,记为i;一个为流出点,记为i'。连两条边从i->i’,两条容量都为1,费用为-g[i][j]和0。

    2. 编号:这个大家有各自的习惯。我的题解中具体看我程序中的hashin和hashout函数和注释,hashin用于编号我前文所提到的i,hashout用于编号我前文所提到的i'。

    3. 连接节点:每个节点的out连接它的右边和它下边节点的流入点,对于边界特殊处理一下,s连(0,0)的入点,(n-1,n-1)连t点。

    这样构图跑一遍spfa的最小费用最大流就OK了。

      1 #include <cstdio>
      2 #include <cstring>
      3 #include <queue>
      4 #define INF 0x7f7f7f7f
      5 using namespace std;
      6 
      7 struct Edge{
      8     int u;//大多数算法在邻接表中并不需要这个,但费用流比较例外
      9     int v;
     10     int f;//残量 
     11     int c;//费用 
     12     int next;
     13 }e[850];//网络流的题目都要记得边数开两倍,因为还有反向弧
     14 int head[170];
     15 int n,m,s,t;
     16 int ecnt = 0;
     17 inline void AddEdge(int _u,int _v,int _f,int _c) {
     18     e[ecnt].next = head[_u];
     19     head[_u] = ecnt;
     20     e[ecnt].u = _u;
     21     e[ecnt].v = _v;
     22     e[ecnt].f = _f;
     23     e[ecnt].c = _c;
     24     ecnt++;
     25 }
     26 inline void Add(int _u,int _v,int _f,int _c) {
     27     AddEdge(_u,_v,_f,_c);
     28     AddEdge(_v,_u,0,-_c);
     29 }
     30 
     31 int dis[170];
     32 bool inq[170];
     33 int pre[170];
     34 bool SPFA() {
     35     queue <int> q;
     36     q.push(s);
     37     memset(dis,0x7f,sizeof(dis));
     38     memset(inq,0,sizeof(inq));
     39     memset(pre,-1,sizeof(pre));
     40     inq[s] = true;
     41     dis[s] = 0;
     42     while (!q.empty()) {
     43         int cur = q.front();
     44         q.pop();
     45         inq[cur] = false;
     46         for (int i = head[cur];i != -1;i = e[i].next) {
     47             if (e[i].f != 0 && dis[e[i].v] > dis[cur] + e[i].c) {
     48                 dis[e[i].v] = dis[cur] + e[i].c;
     49                 pre[e[i].v] = i;
     50                 if (!inq[e[i].v]) {
     51                     inq[e[i].v] = true;
     52                     q.push(e[i].v);
     53                 }
     54             }
     55         }
     56     }
     57     return dis[t] != INF;
     58 }
     59 
     60 void MICMAF(int &flow,int &cost) {
     61     flow = 0;
     62     cost = 0;
     63     while (SPFA()) {
     64         int minF = INF;
     65         for (int i=pre[t];i != -1;i=pre[e[i].u]) minF = min(minF,e[i].f);
     66         flow += minF;
     67         for (int i=pre[t];i != -1;i=pre[e[i].u]) {
     68             e[i].f -= minF;
     69             e[i^1].f += minF;
     70         }
     71         cost += dis[t] * minF;
     72     }
     73 }
     74 /*
     75 节点编号规则:
     76 源点:0
     77 矩阵节点(入):n*x+y+1
     78 矩阵节点(出):n*n+n*x+y+1
     79 汇点:2*n*n+1
     80 */
     81 int g[10][10];
     82 inline int hashin(int x,int y) {
     83     return n*x+y+1;
     84 }
     85 inline int hashout(int x,int y) {
     86     return n*n + n * x + y + 1;
     87 }
     88 int main() {
     89     memset(head,-1,sizeof(head));
     90     scanf("%d",&n);
     91     int x,y,v;
     92     while (scanf("%d%d%d",&x,&y,&v) == 3) {
     93         if (x == 0 && y == 0 && v == 0) break;
     94         x --;
     95         y --;
     96         g[x][y] = v;
     97     }
     98     s = 0;
     99     t = 2 * n * n + 1;
    100     Add(s,1,2,0);
    101     Add(2*n*n,t,2,0);
    102     for (int i=0;i<n;i++)
    103         for (int j=0;j<n;j++) {
    104             int in = hashin(i,j);
    105             int out = hashout(i,j);
    106             Add(in,out,1,0);//邻接表中后插入的先遍历,卡常,f=1是因为只可能再经过一次
    107             Add(in,out,1,-g[i][j]);
    108             if (i != n - 1) Add(out,hashin(i+1,j),2,0);
    109             if (j != n - 1) Add(out,hashin(i,j+1),2,0);
    110         }
    111     int f,c;
    112     MICMAF(f,c);
    113     printf("%d
    ",-c);
    114     return 0;
    115 }
  • 相关阅读:
    dsoframer设计笔记
    pb 使用ole控制进行WORD操作失败-9
    【学习笔记】Fragment
    Suggest:the suffix for classes name
    ArrayList和LinkedList
    Android Studio入门
    asp.net ToString() 输出格式详细
    Uploadify 3.2 参数属性、事件、方法函数详解
    基础解析正则表达式
    10个优秀的 Web UI库/框架
  • 原文地址:https://www.cnblogs.com/jiamian/p/11334909.html
Copyright © 2020-2023  润新知