zoukankan      html  css  js  c++  java
  • 方格取数(多线程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 }
  • 相关阅读:
    UOJ.26.[IOI2014]Game(交互 思路)
    Good Bye 2016 F.New Year and Finding Roots(交互)
    Codeforces.835E.The penguin's game(交互 按位统计 二分)
    Codeforces.744B.Hongcow's Game(交互 按位统计)
    Codeforces.862D.Mahmoud and Ehab and the binary string(交互 二分)
    正睿OI 提高 Day1T3 ZYB玩字符串(DP)
    划分vlan
    2三层交换机实现vlan间的路由
    交换机基础-交换机远程telnet
    自动化运维环境的搭建问题处理
  • 原文地址:https://www.cnblogs.com/jiamian/p/11334909.html
Copyright © 2011-2022 走看看