CF gym 102916 F Exactly One Point 解题报告
数轴上有若干线段,请在数轴上放置若干点,满足:
每个线段恰好包含一个点
每个点至少被一个线段所包含
n\leq 200000
Sol:
考虑差分约束,差分约束不会的可以先看这个,add(r+1,l,-1)我们记
-
每个线段恰好包含一个点,也就是
num_{r-1} - num_l = 1 ,也就是建两条边:l \rightarrow r+1(w = 1) ,r+1 \rightarrow l (w = -1) - 每个点只能被选一次,那么我们需要人为对点进行限制,也就是
num_i - num_{i-1} \leq 1,num_{i-1} - num_i \leq 0 也就是建边i-1 \rightarrow i(w=0),i \rightarrow i-1(w = 1)
但是这个题 SPFA 是过不去的,所以你要把 SPFA 换成 Dijkstra。并且为了解决负环的问题你需要小小魔改一下:
void Dijkstra() {
memset(dis,-1,sizeof dis);
dis[0] = 0;q.push({0,0});
while (!q.empty()) {
pii p = q.top();q.pop();
int x = p.second;
if (p.first != dis[x]) continue;//魔改地方
for (int i = hd[x];i;i = nxt[i]) {
int y = to[i],w = edg[i];
if (dis[x] + w > dis[y]) {
dis[y] = dis[x] + w;
q.push({dis[y],y});
if (++cnt >= (ll)1e7) {//魔改地方
puts("-1");exit(0);
}
}
}
}
}
可以看到,这样魔改完我们每个点不止经过一次,但是我们可以人为判断一下我们是否做了很多次松弛操作(有负环不成立),直接排除掉即可,可以通过本题。
下面介绍一下本题的正经做法(但是我觉得比差分约束难想&写很多):
首先我们考虑 DP,设
为了做到这点,我们先预处理出两个量:
-
maxR_x ,表示对于所有l\leq x 的线段的最大右端点,用 set 可以随手维护。 Next_x 表示对于所有l > x 的线段中有最小右端点的线段,这个和上面一样用 set 维护即可。
接下来我们来考虑一下转移的范围,它和这两个量息息相关。
首先我们肯定不能放在小于等于
其次,我们肯定不能放在大于
所以我们就得出了我们的转移范围
对于这个转移,我们实际上可以用一颗线段树维护出这个区间内为 0 的点,然后我们将这个为 0 的点所在的线段进行一个区间修改。
每个线段只会被操作到一次,所以时间复杂度是严格的
Code:
乱搞:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>
#include <vector>
#include <cmath>
#include <queue>
using namespace std;
#define ll long long
#define ri register int
char buf[1 << 20], *p1, *p2;
//#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2)?EOF: *p1++)
template <typename T> inline void read(T &t) {
ri v = getchar();T f = 1;t = 0;
while (!isdigit(v)) {if (v == '-')f = -1;v = getchar();}
while (isdigit(v)) {t = t * 10 + v - 48;v = getchar();}
t *= f;
}
template <typename T,typename... Args> inline void read(T &t,Args&... args) {
read(t);read(args...);
}
const int N = 2e6 + 10;
#define pii pair<int,int>
priority_queue <pii> q;
int hd[N],nxt[N],to[N],edg[N],tot,n,num[N],dis[N],cnt;
bool vis[N],flag;
inline void add(int u,int v,int w) {to[++tot] = v;nxt[tot] = hd[u];hd[u] = tot;edg[tot] = w;}
void Dijkstra() {
memset(dis,-1,sizeof dis);
dis[0] = 0;q.push({0,0});
while (!q.empty()) {
pii p = q.top();q.pop();
int x = p.second;
if (p.first != dis[x]) continue;
for (int i = hd[x];i;i = nxt[i]) {
int y = to[i],w = edg[i];
if (dis[x] + w > dis[y]) {
dis[y] = dis[x] + w;
q.push({dis[y],y});
if (++cnt >= (ll)1e7) {
puts("-1");exit(0);
}
}
}
}
}
signed main() {
read(n);
for (int i = 1;i <= 4*n-1;++i) {
add(i-1,i,0);
add(i,i-1,-1);
}
for (int i = 1;i <= n;++i) {
int l,r;read(l,r);
add(l,r+1,1),add(r+1,l,-1);
}
Dijkstra();
int ans = 0;
for (int i = 1;i <= 4*n-1;++i) {
if (dis[i] > dis[i-1]) num[++ans] = i-1;
}
printf("%d\n",ans);
for (int i = 1;i <= ans;++i) printf("%d ",num[i]);
return 0;
}
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭