[ACNOI2022]线性规划之殇

280 篇文章 1 订阅
27 篇文章 0 订阅

题目

题目背景
“我说过,我能够一个人承担所有的憎恨,” 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 n3×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 2SijSiwj2Si

类似地,试着将其化为松弛型。但这样会引入两组基变量,恐怕会引起麻烦。那么,我们就先用一组 “基变量” 试试:
{ ϕ 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=jSiwj2Siϕi(Simod2)

好像也无大碍。老规矩,用 i i i ( i − 1 ) (i{\rm-}1) (i1) 给出的两个等式作差。当 j ∈ S i ∩ S i − 1 j\in S_i\cap S_{i-1} jSiSi1 时,其 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ϕi1=lj=iwjrj=i1wj2Si+2Si1

其中 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) (Simod2),表示 ϕ 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 2Si2Si1 的正负,连接 S → i S\to i Si i → T i\to T iT 表示常数项。

然后跑最大流,满流则有解。得到解的方案则比较容易了。

若题目要求改为数量差值不大于 λ \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 2degi 需要这样的操作,所以相邻的两个 2 ∤ deg ⁡ i 2\nmid\deg_i 2degi 之间的 [ 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值