一早起来发现自己没有写昨天的三道DP(Flag被收系列)

水题1:
最大子矩阵和:
给出一个矩阵,求一个子矩阵使得其和最大。
我们先来想一维的:
给出一个序列,求一个连续子序列使得和最大。
f[i] = max(f[i - 1] + seq[i],seq[i]);
考虑子矩阵的话,那么我们可以这样考虑:
我们考虑:
以(i,j)为左上角,(p,q)为右下角的矩阵,我们发现它的矩阵和是:pre_sum[p][j~q] - pre_sum[i,j~q],如果我们考虑暴力:
选择一个点,找出剩下的点,算出矩阵和然后求最大值:
O(n^4)。
而如果我们维护一个”前缀矩阵和”:
我们枚举这个矩阵的最上端和最下端,复杂度n^2,再枚举这个矩阵的宽,从而把复杂度弄成n^3。而暴力枚举的时候,我们的复杂度高在了计算上,而这次我们每次能用一个前缀的思想,使得每次计算矩阵和时间为O(1),复杂度为n^3、、、

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define fl edge[i].f
#define vfl edge[i^1].f
#define v edge[i].to
using namespace std;
const int inf = 1 << 30;
typedef long long ll;
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
int sig[105][105];
int maxn = - inf;
int sum = 0;
int main()
{
    int n = read();
    Rep(i,n)
        Rep(j,n){
            int a;
            scanf("%d",&a);
            sig[i][j] = sig[i - 1][j] + a;
        }
    Rep_0(i,n)
        RD(j,i + 1,n){
            sum = 0;
            Rep(k,n)
            {
                sum >= 0 ? sum += sig[j][k] - sig[i][k] : sum = sig[j][k] - sig[i][k];
                maxn = max(sum,maxn);
            }
        }
    printf("%d\n",maxn);
    return 0;
}

总感觉这复杂度好高 ……觉得标解应该不是这样……
水题2:
旅行商简化版(多进程DP):
有个人,他从西边出发,到达最东边,然后返回,但是返回的时候不能经过原来经过的点,所有点都需要走一遍。求最短距离。
没错这就是两次方格取数问题。
那么我们考虑:
一次方格取数的话:
f[i][j] = max(f[i - 1][j] ,f[i][j - 1] ) + a[i][j];
大概就是这样。而两次的话,我们是不能让它跑两遍最优解的。
我们可以这样来考虑:
考虑两个人一起走,f[i][j]代表:第1个人来到了i,第2个人来到了j的最短距离。
显然,我们对于w <= min(i,j)都已经决策过且不会影响之后的决策。设k = max(i,j) + 1,则有:
f[i][k] = min(f[i][k],f[i][j] + dis[j,k]);
f[k][j] = min(f[k][j],f[i][j] + dis[i,k]);
不难发现,f[j][i] = f[i][j],所以我们不妨:
我们考虑i + 1 = k,那么f[i][k] 的意思就是让走到j的那个人直接走向k,并且加上j到k的距离,看起来很对,但是仍然要质疑一下:j~i之间似乎这些点没有走过?
我们考虑一下:
f[i][k]这个点来讲,那么我们显然在前面已经推出过:
f[1 ~ i - 1][n]的状态,以及f[i][1~j]的所有状态,显然当j >i 时,f[i][j]能转移出f[i][j + 1]的状态,相当于第二个人直接走了一步,而如果对于j < i那么转移是:f[i][k] = f[i][j] + dis[j,k];
当i > j + 1时,那么i一定是在j + 1之后就一直是第一个人在走,而到了i = j + 1,那么i要么是从一个地方l走过来,要么是从j走过来,如果从l走过来,那么也就意味着从l–j这一段都是j一个人在走、、、也就意味着又能按照前面的思路……所以分析下来,f[i][j]一定是走了之前的所有点过来的。
排版好像出了些问题。

我们继续来说第三题:
    第三题:
    搭建双塔:
    我觉得这是我做的最好的分类讨论了……
    题意:给你N个石柱,每个石柱有高度,搭出最高的高度相同的塔。

思路:
考虑到无后效性,可以DP。
我们设f[i][j]代表在考虑第i个水晶时高度差为j时矮塔的高度。
则有:
f[i][j] = max{f[i - 1][j - h[i]],f[i - 1][j + h[i]] + h[i] ,f[i - 1][h[i] - j] + h[i] - j,f[i - 1][j]};
表示:1.放到高的塔上。
2.放到矮的塔上。
3.之前高度差不是很大,放上这个之后矮塔会变成高塔。
4.不要这个石柱。

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define fl edge[i].f
#define vfl edge[i^1].f
#define v edge[i].to
using namespace std;
const int inf = 1 << 30;
typedef long long ll;
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
int f[105][2005],sum[20005],h[2005];
int main()
{   
    memset(f,-0x7f,sizeof(f));
    int n = read();
    Rep(i,n)
        h[i] = read();
    sort(h + 1,h + 1 + n);  
    Rep(i,n)
        sum[i] = sum[i - 1] + h[i];
    f[1][0] = 0;f[1][h[1]] = 0;
    RD(i,2,n)   
        for(int j = 0 ; j <= sum[i]; j ++)
        {
            int h1 = j - h[i];
            if(h1 >= 0 && h1 <= sum[i - 1])
                f[i][j] = max(f[i][j],f[i - 1][h1]);

            int h2 = j + h[i];
            if(h2 <= sum[i - 1])
                f[i][j] = max(f[i][j] , f[i - 1][h2] + h[i]);

            int h3 = h[i] - j;
            if(h3 >= 0 && h3 <= sum[i - 1])
                f[i][j] = max(f[i - 1][h3] + h3,f[i][j]);

            int h4 = j;
            if(h4 <= sum[i - 1])
                f[i][j] = max(f[i - 1][j],f[i][j]);//Rig

        }
    if(f[n][0] > 0)printf("%d\n",f[n][0]);
    else printf("Impossible\n");
    return 0;
}

ps:这个代码是我照着标准程序调的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值