[USACO13OPEN] Photo G 题解

前言

题目链接:洛谷

题意简述

一个长度为 n 的序列,有一些位置染了色。现给出 m 条限制,第 i 条限制为 liri 中有且仅有一个位置染色。求出满足这 m 中条件,染色位置个数最多为多少。

n2×105m105

题目分析

方法 1:差分约束

区间问题使用前缀和,退化成关于两端点的限制:vrivli1=1,其中 vi 表示 1i 中有多少染色的位置。以及题目本身的限制:vivi1[0,1],建图后差分约束跑一跑即可。

我们求 maxvn,使用最短路算法。

无解情况等价于存在负环,最短路不存在。

但是显然会超时,如何优化?SPFA 经典优化:SLF 优化。嗯,还差一个点,循环次数超过魔法数字 1736520 就无解。就水过去了。

方法 2:动态规划

差分约束显然不是正解。序列问题,决策是当前点是否染色,可以使用 DP 解决。

我们设 fi 表示 i 染色时 1i 染色的位置的个数的最大值。转移的时候枚举上一次染色的位置 j,有转移方程:

fi=maxmeet given conditions{fj}+1

时间复杂度什么的先不谈,考虑怎么判断一个 j 是否合法。

有一个 trick:「恰好」等价于「不少于并且不大于」

对于本题,恰好区间染色了一个位置,拆成两个限制,即至少染色一个位置,至多染色一个位置。

先考虑前者。由于 (i,j) 中没有染色,如果其中存在一个限制区间就会不合法。所以我们求得 mix 表示右端点在 x 左边,左端点的最大值。j 需要满足 jmii

再考虑后者。如果一个区间同时包括了 ij,那么也是不合法的。我们求得 mxx 表示右端点在 x 及右边,左端点的最小值。j 需要满足 j<mxi

预处理扫一扫是简单的。

转移方程变成:

fi=maxj=miimxi1{fj}+1

由于 mxmi 单调不降,直接上单调队列就行了。注意如果我们记无效的 fi=1,则上式中的 max 需要满足 fj1,如果没有满足条件的 j,那么 fi=1

注意到我们不能直接取 f 的最大值作为答案,因为我们需要满足所有 m 条限制。不妨用 fn+1 统计答案,这样就能完整考虑到所有限制,并求出最大值了。

时间复杂度:Θ(n+m)

Update on 2024.11.2:寻找到 f 的性质,省略单调队列。

经过打表发现 f 是单调递增的,所以单调队列可以换成双指针,维护 <mxij 中,最后一个 fj1k。转移的时候判断 k 是否 mii,转移即可。时间复杂度没有变化。

展开详细证明:

使用第二数学归纳法。假设所有 j<i 满足 fj 具有单调性。如果能推出 fifi1 即证。

mximxi1,那么在 mxi1+1mxi 可能存在 fmxi1f,因为所有 j<i 满足 fj 具有单调性,那么 fi 就能从这里转移过来,从而 fi1

代码

方法 1:差分约束

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 200010, M = 100010;
struct Graph {
struct node {
int to, len, nxt;
} edge[N * 2 + M * 2];
int tot = 1, head[N];
void add(int u, int v, int w) {
edge[++tot] = {v, w, head[u]};
head[u] = tot;
}
inline node & operator [] (const int x) {
return edge[x];
}
} xym;
void smller(int u, int v, int w) {
xym.add(v, u, w);
}
void bigger(int u, int v, int w) {
smller(v, u, -w);
}
void equals(int u, int v, int w) {
bigger(u, v, w);
smller(u, v, w);
}
int n, m;
int dis[N], cnt[N];
bool inq[N];
bool SPFA() {
int yzh_i_love_you = 0;
memset(dis, 0x3f, sizeof dis);
deque<int> Q; Q.push_front(0), dis[0] = 0, cnt[0] = 1;
inq[0] = true;
while (!Q.empty()) {
int now = Q.front(); Q.pop_front();
inq[now] = false;
for (int i = xym.head[now]; i; i = xym[i].nxt) {
int to = xym[i].to, w = xym[i].len;
if (dis[to] > dis[now] + w) {
dis[to] = dis[now] + w;
cnt[to] = cnt[now] + 1;
if (cnt[to] > n + 1 || ++yzh_i_love_you > 1736520) return false;
if (!inq[to]) {
inq[to] = true;
if (!Q.empty() && dis[to] < dis[Q.front()]) Q.push_front(to);
else Q.push_back(to);
}
}
}
}
return true;
}
signed main() {
#ifndef XuYueming
freopen("photo.in", "r", stdin);
freopen("photo.out", "w", stdout);
#endif
scanf("%d%d", &n, &m);
for (int i = 1, l, r; i <= m; ++i) {
scanf("%d%d", &l, &r);
equals(r, l - 1, 1);
}
for (int i = 1; i <= n; ++i) {
bigger(i, i - 1, 0);
smller(i, i - 1, 1);
}
if (!SPFA()) return puts("-1"), 0;
printf("%d", dis[n]);
return 0;
}

方法 2:动态规划

单调队列

#include <cstdio>
#include <iostream>
using namespace std;
const int N = 200010;
int n, m, mx[N], mi[N];
int Q[N], head, tail = -1;
int f[N];
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n + 1; ++i) mx[i] = i;
for (int i = 1, l, r; i <= m; i++) {
scanf("%d%d", &l, &r);
mx[r] = min(mx[r], l);
mi[r + 1] = max(mi[r + 1], l);
}
for (int i = n; i >= 1; --i) mx[i] = min(mx[i], mx[i + 1]);
for (int i = 2; i <= n + 1; ++i) mi[i] = max(mi[i], mi[i - 1]);
for (int i = 1, j = 0; i <= n + 1; ++i) {
while (j < mx[i]) {
if (f[j] != -1) {
while (head <= tail && f[Q[tail]] <= f[j]) --tail;
Q[++tail] = j;
}
++j;
}
while (head <= tail && Q[head] < mi[i]) ++head;
if (head <= tail) f[i] = f[Q[head]] + 1;
else f[i] = -1;
}
if (f[n + 1] == -1) puts("-1");
else printf("%d\n", f[n + 1] - 1);
return 0;
}

双指针

#include <cstdio>
const int MAX = 1 << 23, N = 200001;
int n, m, l, r, mx[N], mi[N], f[N];
char buf[MAX], *p(buf), *e(buf + MAX);
#define getchar() (p == e && fread(p = buf, 1, MAX, stdin), *p++)
[[always_inline]] inline void read(int &x) {
x = 0; char ch = getchar();
for (; ch < 48; ch = getchar());
for (; 48 <= ch; ch = getchar())
x = (x << 3) + (x << 1) + (ch ^ 48);
}
[[always_inline]] inline void tomin(int& a, int& b) { b < a && (a = b); }
[[always_inline]] inline void tomax(int& a, int& b) { b > a && (a = b); }
main() {
fread(buf, 1, MAX, stdin), read(n), read(m);
for (register int i(1); i <= n + 1; ++i) mx[i] = i;
for (; m--; ) read(l), read(r), tomin(mx[r], l), tomax(mi[r + 1], l);
for (register int i(n); i; --i) tomin(mx[i], mx[i + 1]);
for (register int i(1), j(0), k(0); i <= n + 1; ++i, tomax(mi[i], mi[i - 1])) {
for (; j + 1 < mx[i]; ~f[++j] && (k = j));
f[i] = k >= mi[i] ? f[k] + 1 : -1;
}
~f[n + 1] ? printf("%d", f[n + 1] - 1) : puts("-1");
}
posted @   XuYueming  阅读(119)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示