cf737d Ezzat and Grid

本文介绍了一种使用动态规划和线段树解决矩阵问题的方法。题目涉及处理10^9规模的数据,通过离散化和线段树优化,求解最少删除多少行使得每两行相邻。文章详细阐述了dp转移方程、线段树的构建与更新,并提供了完整的C++代码实现。
摘要由CSDN通过智能技术生成

题面

题意

给定 n n n 1 0 9 10^9 109 列的 01 矩阵。第 i i i 行和 i + 1 i+1 i+1 行是相邻的当且仅当至少存在一列,这两行这一列的数都是 1。问最少删掉多少行,才能使对于每个 1 ≤ i < m 1\le i<m 1i<m,第 i i i 行和 i + 1 i+1 i+1 行都相邻。 m m m 是删掉之后的总行数。输出方案。 1 ≤ n , m ≤ 3 × 1 0 5 1\le n,m\le 3\times10^5 1n,m3×105

思路

  1. 正难则反,考虑剩余的最长长度,类似最长上升子序列,本题的 dp 转移方程:
    f i = max ⁡ 1 ≤ j < i , g ( i , j ) f j + 1 f_i=\max_{1\le j < i,g(i,j)} f_j+1 fi=1j<i,g(i,j)maxfj+1
    其中 g ( i , j ) g_(i,j) g(i,j) 表示第 i i i 行与第 j j j 行有至少一列为 1

  2. 类似最长上升子序列,需要有一个数据结构来维护 [ 1 , i − 1 ] [1,i-1] [1,i1] 的最值,因为要输出方案,还要维护从 j j j 转移到 i i i j j j 的编号,考虑线段树

  3. 值域 1 0 9 10^9 109,考虑离散化,然后建树

  4. 处理的细节:

    1. 查询的时候要查两个值,用全局变量维护,注意每次循环的初始化。
    2. 更新完 dp 值后,要把当前行所有段都更新一遍
    3. 注意建树的大小,是离散化后的大小。注意数组要开 2*n,因为对区间两个端点离散化
    4. 老老实实用懒标记,pushdown 别忘了懒标记,query 不要 pushup

代码

const int N = 6e5 + 5; //注意是 2 * n

int n, m, cnt, f[N], pre[N];
int mx, pos;
struct Node {int id, x, y;} p[N];
struct node {int x, y;};
vector<node> a[N];
set<int> s, ans;
map<int, int> mp;

struct tree {int mx, id; bool tag;} t[N << 2];

void pushdown(int u) {
    if(!t[u].tag) return;
    t[ls].mx = t[rs].mx = t[u].mx;
    t[ls].id = t[rs].id = t[u].id;
    t[ls].tag = t[rs].tag = t[u].tag;
    t[u].tag = false;
}

void pushup(int u) {
    if(t[ls].mx >= t[rs].mx) {
        t[u].mx = t[ls].mx;
        t[u].id = t[ls].id;
    } else {
        t[u].mx = t[rs].mx;
        t[u].id = t[rs].id;
    }
}

void upd(int u, int l, int r, int x, int y, int val, int id) {
    if(x <= l && r <= y) {
        t[u].mx = val;
        t[u].id = id;
        t[u].tag = true;
        return;
    }
    pushdown(u);
    int mid = (l + r) >> 1;
    if(x <= mid) upd(ls, l, mid, x, y, val, id);
    if(y > mid)  upd(rs, mid + 1, r, x, y, val, id);
    pushup(u);
}

void query(int u, int l, int r, int x, int y) {
    if(x <= l && r <= y) {
        if(t[u].mx >= mx) {
            mx = t[u].mx;
            pos = t[u].id;
        }
        return;
    }
    pushdown(u);
    int mid = (l + r) >> 1;
    if(x <= mid) query(ls, l, mid, x, y);
    if(y > mid)  query(rs, mid + 1, r, x, y);
}

int main() {
    cin >> n >> m;
    rep(i, 1, m) {
        p[i] = {rd, rd, rd};
        s.insert(p[i].x), s.insert(p[i].y);
    }
    for(auto ele : s) mp[ele] = ++cnt;
    rep(i, 1, m) a[p[i].id].pb({mp[p[i].x], mp[p[i].y]});
    rep(i, 1, n) {
        for(auto ele : a[i]) {
            int x = ele.x, y = ele.y;
            mx = pos = 0;
            query(1, 1, cnt, x, y);
            if(mx + 1 > f[i]) {
                f[i] = mx + 1;
                pre[i] = pos;
            }
        }
        for(auto ele : a[i]) upd(1, 1, cnt, ele.x, ele.y, f[i], i);
    }
    int now = max_element(f + 1, f + n + 1) - f;
    rep(i, 1, n) ans.insert(i);
    while(now) {
        ans.erase(now);
        now = pre[now];
    }
    cout << sz(ans) << endl;
    for(auto ele : ans) cout << ele << ' ';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值