题目
题目背景
“我说过,我能够一个人承担所有的憎恨,”
D
D
(
X
Y
X
)
\sf DD(XYX)
DD(XYX) 说,“今天你就消失吧!”
题目描述
有若干区间
[
l
i
,
r
i
]
[l_i,r_i]
[li,ri],有些区间为黑色或白色,另外的区间则未染色。请给每个区间指定一个颜色,使得对于数轴上每个点,覆盖它的黑色区间和白色区间个数相差不大于
1
1
1 。无解则报告无解。
数据范围与约定
区间数量
n
⩽
3
×
1
0
4
n\leqslant 3\times 10^4
n⩽3×104 。
思路一
考场上一直试图用线性规划解决问题。可惜我学得太差。还想起了 志愿者招募(传送门),记得当时就不会做,现在果然仍不会;出来混,迟早是要还的 😭
写出线性规划后,每个变量出现时间是 区间,转松弛型然后 差分 就可以凑出网络流。记
w
i
=
[
c
o
l
o
r
(
i
)
=
black
]
w_i=[color(i)=\text{black}]
wi=[color(i)=black],记
S
i
S_i
Si 为覆盖
i
i
i 的区间的集合,则线性约束为
⌊
∣
S
i
∣
2
⌋
⩽
∑
j
∈
S
i
w
j
⩽
⌈
∣
S
i
∣
2
⌉
\left\lfloor\frac{|S_i|}{2}\right\rfloor \leqslant \sum_{j\in S_i}w_j \leqslant \left\lceil\frac{|S_i|}{2}\right\rceil
⌊2∣Si∣⌋⩽j∈Si∑wj⩽⌈2∣Si∣⌉
类似地,试着将其化为松弛型。但这样会引入两组基变量,恐怕会引起麻烦。那么,我们就先用一组 “基变量” 试试:
{
ϕ
i
=
∑
j
∈
S
i
w
j
−
⌊
∣
S
i
∣
2
⌋
ϕ
i
⩽
(
∣
S
i
∣
m
o
d
2
)
\begin{cases} \phi_i=\sum_{j\in S_i}w_j-\left\lfloor\frac{|S_i|}{2}\right\rfloor\\ \phi_i\leqslant(|S_i|\bmod 2) \end{cases}
{ϕi=∑j∈Siwj−⌊2∣Si∣⌋ϕi⩽(∣Si∣mod2)
好像也无大碍。老规矩,用
i
i
i 和
(
i
−
1
)
(i{\rm-}1)
(i−1) 给出的两个等式作差。当
j
∈
S
i
∩
S
i
−
1
j\in S_i\cap S_{i-1}
j∈Si∩Si−1 时,其
w
j
w_j
wj 恰好相抵。就会只剩下
ϕ
i
−
ϕ
i
−
1
=
∑
l
j
=
i
w
j
−
∑
r
j
=
i
−
1
w
j
−
⌊
∣
S
i
∣
2
⌋
+
⌊
∣
S
i
−
1
∣
2
⌋
\phi_i-\phi_{i-1}=\sum_{l_j=i}w_j-\sum_{r_j=i-1}w_j-\left\lfloor\frac{|S_i|}{2}\right\rfloor+\left\lfloor\frac{|S_{i-1}|}{2}\right\rfloor
ϕi−ϕi−1=lj=i∑wj−rj=i−1∑wj−⌊2∣Si∣⌋+⌊2∣Si−1∣⌋
其中 l j , r j l_j,r_j lj,rj 分别表示 j j j 区间的左右端点。此时 w j w_j wj 只在 l j l_j lj 处,以 + 1 +1 +1 系数出现,在 ( r j + 1 ) (r_j{+}1) (rj+1) 处,以 − 1 -1 −1 系数出现。而 ϕ i \phi_i ϕi 更是如此。流量守恒,经典模型了属于是。
规定等式右侧的正数为流出。建图方式已现:
- ( i + 1 ) → i (i{+}1)\to i (i+1)→i 容量为 ( ∣ S i ∣ m o d 2 ) (|S_i|\bmod 2) (∣Si∣mod2),表示 ϕ i \phi_i ϕi 。注意它在等式左侧。
- l i → ( r i + 1 ) l_i\to(r_i{+}1) li→(ri+1) 容量为 1 1 1,表示 w i w_i wi 。
- 根据 ⌊ ∣ S i ∣ 2 ⌋ − ⌊ ∣ S i − 1 ∣ 2 ⌋ \left\lfloor\frac{|S_i|}{2}\right\rfloor-\left\lfloor\frac{|S_{i-1}|}{2}\right\rfloor ⌊2∣Si∣⌋−⌊2∣Si−1∣⌋ 的正负,连接 S → i S\to i S→i 或 i → T i\to T i→T 表示常数项。
然后跑最大流,满流则有解。得到解的方案则比较容易了。
若题目要求改为数量差值不大于 λ \lambda λ,此方法仍然适用;本题中 λ = 1 \lambda=1 λ=1 的好处是,除了 S , T S,T S,T 的邻边,每条边的容量都是 1 1 1,可以让 d i n i c \tt dinic dinic 快到飞起 😂
思路二
相比之下,这个思路会显得比较 c o n s t r u c t i v e \rm constructive constructive 。
首先,将题目中区间转化为左闭右开 [ l i , r i ) [l_i,r_i) [li,ri),把端点离散化。只需要考虑这样的 “小区间”。考虑在某些小区间 [ x i , x i + 1 ) [x_i,x_{i+1}) [xi,xi+1) 上,额外增加一个区间,给这个区间染色,可以让每个点都被黑白色区间覆盖同样多次。按:这实际上就是引入基变量,转为松弛型。
然后,只需考虑端点处的差分值。按:还是要用差分!我偏生就是不会这差分!我宁愿对偶、单纯形,我也没差分!我真是个废物 😿😢😭
那么 [ l i , r i ) [l_i,r_i) [li,ri) 就是给一个端点提供 + 1 +1 +1,另一个为 − 1 -1 −1 。假设我们已经规定所有点被黑白区间覆盖同样多次,则每个点的差分值都是 0 0 0 。即 i i i 上 + 1 +1 +1 的数量为 deg i 2 \deg_i\over 2 2degi,这是简单的多重二分图匹配,用 d i n i c \tt dinic dinic 就能实现。
所以怎样加入 [ x i , x i + 1 ) [x_i,x_{i+1}) [xi,xi+1) 呢?显然 2 ∤ deg i 2\nmid\deg_i 2∤degi 需要这样的操作,所以相邻的两个 2 ∤ deg i 2\nmid\deg_i 2∤degi 之间的 [ x i , x i + 1 ) [x_i,x_{i+1}) [xi,xi+1) 全部加入就好了。
代码
我实现了 思路二
,因为 畏难心理让我不想再做一次志愿者招募 要多写写自己不熟悉的做法。
#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for loneliness)
#include <cctype> // DDG yydDOG & ZXY yydBUS !!!
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MAXN = 1000006, MAXM = 10000007;
struct Edge{ int to, nxt, val; };
Edge e[MAXM]; int head[MAXN], cntEdge;
inline void _addEdge(int a, int b, int c){
e[cntEdge].to = b, e[cntEdge].nxt = head[a];
e[cntEdge].val = c, head[a] = cntEdge ++;
}
inline void addEdge(int a, int b, int c){
_addEdge(a,b,c), _addEdge(b,a,0);
}
const int INF = 0x3fffffff;
int dis[MAXN], cur[MAXN];
bool bfs(const int &source, const int &sink, const int &n){
static int _que[MAXN]; int *fro = _que, *bac = _que;
memset(dis+1,-1,n<<2), dis[source] = 0, *bac = source;
for(; fro!=bac+1; ++fro) for(int i=head[*fro]; ~i; i=e[i].nxt)
if(e[i].val && !(~dis[e[i].to])) // bfs
dis[e[i].to] = dis[*fro]+1, *(++bac) = e[i].to;
return dis[sink] != -1;
}
int dfs(int x, int inFlow, const int &sink){
int sum = 0; if(x == sink) return inFlow;
for(int &i=cur[x]; ~i; i=e[i].nxt)
if(e[i].val && dis[e[i].to] == dis[x]+1){
int d = dfs(e[i].to,std::min(inFlow-sum,e[i].val),sink);
e[i].val -= d, e[i^1].val += d;
if((sum += d) == inFlow) return sum;
}
dis[x] = -1; return sum;
}
int dinic(const int &source, const int &sink, const int &n){
int res = 0;
while(bfs(source,sink,n)){
memcpy(cur+1,head+1,n<<2);
res += dfs(source,INF,sink);
}
return res;
}
int l[MAXN], r[MAXN], w[MAXN], tmp[MAXN];
int deg[MAXN], got[MAXN], haxi[MAXN];
int main(){
int n = readint(); readint();
memset(head,-1,sizeof(head));
rep(i,1,n){
l[i] = readint(), r[i] = readint()+1, w[i] = readint();
tmp[i<<1] = l[i], tmp[(i<<1)-1] = r[i];
}
std::sort(tmp+1,tmp+(n<<1)+1);
int m = int(std::unique(tmp+1,tmp+(n<<1)+1)-tmp-1);
const int source = m+1, sink = source+1;
int tot = sink; ///< total nodes
rep(i,1,n){ // for every constraint
l[i] = int(std::lower_bound(tmp+1,tmp+m+1,l[i])-tmp);
r[i] = int(std::lower_bound(tmp+1,tmp+m+1,r[i])-tmp);
if(w[i] == -1){
haxi[i] = ++ tot, addEdge(source,tot,1);
addEdge(tot,l[i],1), addEdge(tot,r[i],1);
}
else ++ got[w[i] ? r[i] : l[i]];
++ deg[l[i]], ++ deg[r[i]];
}
rep(i,1,m) if(deg[i]&1){
++ tot, addEdge(source,tot,1), ++ deg[i+1];
addEdge(tot,i,1), addEdge(tot,i+1,1);
}
int need = 0;
rep(i,1,m){ // for each node
if(((deg[i]+1)>>1) < got[i])
return void(puts("-1")), 0;
addEdge(i,sink,((deg[i]+1)>>1)-got[i]);
need += ((deg[i]+1)>>1)-got[i];
}
if(dinic(source,sink,tot) != need)
return void(puts("-1")), 0;
rep(i,1,n){
if(~w[i]) putchar(w[i]^48);
else putchar(e[head[haxi[i]]].val ? '0' : '1');
putchar(' ');
}
return 0;
}