传送门
题意
有 n n n行全 0 0 0段,将其中 m m m段赋值为 1 1 1,问满足条件的行数最多是多少。条件为:两行至少有一列全 1 1 1。
分析
比赛的时候想的是贪心,用线段树
c
h
e
c
k
check
check,赛后直接被辛格
h
a
c
k
hack
hack了
本着贪心不对就往
d
p
dp
dp上考虑的原则,我们考虑一下怎么去设计状态
f
i
f_i
fi为保留第
i
i
i行的话,最少需要删多少行,假设第
j
j
j行和第
i
i
i行符合条件,那么,
f
[
i
]
=
f
[
j
]
+
(
i
−
j
−
1
)
f[i] = f[j] + (i - j - 1)
f[i]=f[j]+(i−j−1),转化一下就是
f
[
i
]
=
(
f
[
j
]
−
j
)
+
(
i
−
1
)
f[i] = (f[j] - j) + (i - 1)
f[i]=(f[j]−j)+(i−1),其中跟
j
j
j相关的部分直接用线段树维护就可以了
因为数据范围比较大,所以需要离散化一下
代码
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define dl(x) printf("%lld\n",x);
#define di(x) printf("%d\n",x);
#define _CRT_SECURE_NO_WARNINGS
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef vector<int> VI;
const int INF = 0x3f3f3f3f;
const int N = 8e5 + 10;
const ll mod = 1000000007;
const double eps = 1e-9;
const double PI = acos(-1);
template<typename T>inline void read(T &a) {
char c = getchar(); T x = 0, f = 1; while (!isdigit(c)) {if (c == '-')f = -1; c = getchar();}
while (isdigit(c)) {x = (x << 1) + (x << 3) + c - '0'; c = getchar();} a = f * x;
}
int gcd(int a, int b) {return (b > 0) ? gcd(b, a % b) : a;}
vector<PII> p[N];
vector<int> nums;
int n, m;
int f[N],pre[N];
bool st[N];
int find(int x) {
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
struct Node {
int l, r;
PII x, add; // f[j] - j,节点编号
} tr[N * 4];
void push(int u){
tr[u].x = min(tr[u << 1].x,tr[u << 1 | 1].x);
}
void down(int u){
if(tr[u].add.fi == INF) return;
tr[u << 1].x = tr[u].add;
tr[u << 1 | 1].x = tr[u].add;
tr[u << 1].add = tr[u].add;
tr[u << 1 | 1].add = tr[u].add;
tr[u].add = {INF,0};
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r, tr[u].x = mp(0, 0), tr[u].add = mp(INF, 0);
if (l == r) return;
int mid = (l + r) >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
void modify(int u,int l,int r,PII k){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].x = min(tr[u].x,k);
tr[u].add = min(tr[u].add,k);
return;
}
down(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) modify(u << 1,l,r,k);
if(r > mid) modify(u << 1 | 1,l,r,k);
push(u);
}
PII query(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].x;
}
down(u);
int mid = (tr[u].l + tr[u].r) >> 1;
PII x = mp(INF,0);
if(l <= mid) x = query(u << 1,l,r);
if(r > mid) x = min(query(u << 1 | 1,l,r),x);
return x;
}
int main() {
read(n), read(m);
while (m--) {
int id, l, r;
read(id), read(l), read(r);
p[id].pb(mp(l, r));
nums.pb(l), nums.pb(r);
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
for (int i = 1; i <= n; i++)
for (auto &t : p[i]) t.fi = find(t.fi) + 1, t.se = find(t.se) + 1;
int si = SZ(nums);
build(1,1,si);
for(int i = 1;i <= n;i++){
if(!SZ(p[i])) {f[i] = INF;continue;}
PII ans = mp(INF,0);
for(auto t:p[i]) ans = min(ans,query(1,t.fi,t.se));
f[i] = ans.fi + i - 1,pre[i] = ans.se;
for(auto t:p[i]) modify(1,t.fi,t.se,mp(f[i] - i,i));
}
int ans = INF,id = 0;
for(int i = 1;i <= n;i++) if(f[i] + n - i < ans) ans = f[i] + n - i,id = i;
while(id){
st[id] = true;
id = pre[id];
}
di(ans);
for(int i = 1;i <= n;i++) if(!st[i]) printf("%d ",i);
return 0;
}
博客讨论了一道竞赛题目,涉及将一定数量的全0段转换为全1段,同时要求至少存在两行有相同列全为1。作者首先尝试贪心算法,但被证明不正确。接着转向动态规划,设计状态转移方程,使用线段树优化查询和修改操作。代码中展示了如何实现这一动态规划和线段树结合的解决方案。

638

被折叠的 条评论
为什么被折叠?



