极大化思想 悬线法

1.悬线法
luoguP4147玉蟾宫
蓝书上有这种方法的介绍 维护left[i] right[i] up[i]表示当前行第i列能向左/右/上拓展的格子的编号 ,在第一次循环时从上到下,从左到右维护up和left 之后再逆序循环一次维护right
传送门
并不需要像蓝书那样写 递推状态只和上一行有关 直接和01背包一样证明 可以降成一维的

#include<cstdio>
using namespace std;
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
inline int read(){
    int s=0,f=1;char c=getchar();while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
    return s*f;
}
const int maxn = 1001;
int a[maxn][maxn], up[maxn], left[maxn], right[maxn];
int n, m, ans = 0;
/*
每个格子(i,j)对应着一个以第i行为下边界,高度为up(i,j),
左右边界为left(i,j),right(i,j)的矩形
*/
int main(){
    n = read(), m = read();
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++){
            char c = getchar();
            while(c != 'F' && c != 'R')
                c = getchar();
            a[i][j] = c == 'F' ? 0 : 1;
        }
    for (int i = 1; i <= n; i++){
        int lo = 0, ro = m + 1;
        for (int j = 1; j <= m; j++){   //维护left和up
            if(a[i][j] == 1){
                left[j] = up[j] = 0, lo = j;
            }else{
                left[j] = i == 1 ? lo + 1 : Max(lo + 1, left[j]);
                up[j] = i == 1 ? 1 : up[j] + 1;
            }
        }
        for (int j = m; j >= 1; j--) {
            if(a[i][j] == 1){
                right[j] = m, ro = j;
            }else{
                right[j] = i == 1 ? ro - 1 : Min(ro - 1, right[j]);
            }
            ans = Max(ans, (right[j] - left[j] + 1) * up[j]);
        }
    }
    printf("%d\n", ans * 3);
    return 0;
}

2.奶牛浴场
王之坤神犇有论文,但是有bug
洛谷题解
这位爷指出来错哪了,我就不再打一遍了。
论文
用上文的算法一的思路算
可以剪枝 在洛谷加了剪枝以后比没加快了一倍

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 5007;
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
/*
不能从最左边的那个点开始找 要从地图的左边界开始找
*/
int L, W, n;
struct node{
    int x, y;
} p[maxn];
struct cmp{
    bool operator()(const node &l,const node &r){
        return l.x < r.x || (l.x == r.x && l.y < r.y);
    }
};
struct cmp2{
    bool operator()(const node &l, const node &r){
        return l.y < r.y;
    }
};

int tot = 0, ans;
int read(){
    char c = getchar();
    int s = 0;
    while(c > '9' || c < '0')
        c = getchar();
    while(c >= '0' && c <= '9') {
        s = s * 10 + c - '0';
        c = getchar();
    }
    return s;
}

/*极大化思想:算法1:
先枚举极大子矩形的左边界 然后从左到右依次扫描每一个障碍点 并不断修改可行的上下边界
从而枚举出所有以这个定点为左边界的极大子矩形
*/
void solve(){
    sort(p + 1, p + tot + 1, cmp2());
    for (int i = 1; i <= tot; i++) {    //尝试以p[i].y 作为上边界更新答案
        for (int j = i + 1, left = 0, right = L, v = W - p[i].y; j <= tot; j++) {
            if(p[j].x > right || p[j].x < left)
                continue;
            if(v * (right - left) <= ans)
                break;//最优性剪枝
            ans = Max(ans, (p[j].y - p[i].y) * (right - left));
            if(p[j].x > p[i].x)
                right = Min(p[j].x, right);
            else
                left = Max(left, p[j].x);
            if(left == right)
                break;//这之后的矩阵面积均为0了
        }
        for (int j = i - 1, left = 0, right = L, v = p[i].y; j >= 1; j--) {
            if(p[j].x > right || p[j].x < left)
                continue;
            if(v * (right - left) <= ans)
                break;
            ans = Max(ans, (p[i].y - p[j].y) * (right - left));
            if(p[j].x > p[i].x)
                right = p[j].x;
            else
                left =  p[j].x;
            if(left == right)
                break;//这之后的矩阵面积均为0了
        }
    }
}
int main(){
    L = read(), W = read();
    n = read();
    p[++tot].x = 0, p[tot].y = 0;
    p[++tot].x = 0, p[tot].y = W;
    p[++tot].x = L, p[tot].y = 0;
    p[++tot].x = L, p[tot].y = W;
    for (int i = 1, x, y; i <= n; i++){
        x = read(), y = read();
        p[++tot].x = x, p[tot].y = y;
    }
    sort(p + 1, p + tot + 1, cmp());
    for (int i = 2; i <= tot; i++) {
        //枚举左边界(x)
        ans = Max(ans, (p[i].x - p[i - 1].x) * W);
    }
    solve();
    //solve2();
    printf("%d", ans);
    return 0;
}

3.ZJOI2007 棋盘制作
传送门
悬线法循环的时候就能维护最大正方形的边长了,输入的时候按行编号+列编号把格子里的01异或一下,就变成求全0或全1矩阵了

#include<cstdio>
#include<cstring>
using namespace std;
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
const int maxn = 2001;
int a[maxn][maxn], n, m, rua, ans, ans1, left[maxn], right[maxn], up[maxn];

inline int read(){
    int s=0,f=1;char c=getchar();while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
    return s*f;
}

int main(){
    n = read();
    m = read();
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            a[i][j] = read();
            if((i+j)&1)
                a[i][j] ^= 1;   //i+j为奇数的格子01取反 转化问题
        }
    }
    for (int i = 1; i <= n; i++){   //第一次维护的是全0矩阵和全0正方形
        int lo = 0, ro = m + 1;
        for (int j = 1; j <= m; j++){
            if(a[i][j]){
                up[j] = left[j] = 0, lo = j;
            }else{
                up[j] = i == 1 ? 1 : up[j] + 1;
                left[j] = i == 1 ? lo + 1 : Max(lo + 1, left[j]);
            }
        }
        for (int j = m; j >= 1; j--) {
            if(a[i][j])
                right[j] = m + 1, ro = j;
            else{
                right[j] = i == 1 ? ro - 1 : Min(ro - 1, right[j]);
                rua = Min(up[j], right[j] - left[j] + 1);
                ans = Max(ans, up[j] * (right[j] - left[j] + 1));
                ans1 = Max(ans1, rua * rua);
            }
        }
    }
    memset(up, 0, sizeof(up));
    memset(left, 0, sizeof(left));
    memset(right, 0, sizeof(right));
    for (int i = 1; i <= n; i++){   //第二次维护的是全1矩阵和全1正方形
        int lo = 0, ro = m + 1;
        for (int j = 1; j <= m; j++){
            if(a[i][j] == 0){
                up[j] = left[j] = 0, lo = j;
            }else{
                up[j] = i == 1 ? 1 : up[j] + 1;
                left[j] = i == 1 ? lo + 1 : Max(lo + 1, left[j]);
            }
        }
        for (int j = m; j >= 1; j--) {
            if(a[i][j] == 0)
                right[j] = m + 1, ro = j;
            else{
                right[j] = i == 1 ? ro - 1 : Min(ro - 1, right[j]);
                ans = Max(ans, up[j] * (right[j] - left[j] + 1));
                rua = Min(up[j], right[j] - left[j] + 1);
                ans1 = Max(ans1, rua * rua);
            }
        }
    }
    printf("%d\n%d\n", ans1, ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值