[ACNOI2022]林昆

280 篇文章 1 订阅
137 篇文章 1 订阅

题目

题目背景
我以为,我 A K AK AK 了。或许这次真有可能战胜 D D ( X Y X ) \sf DD(XYX) DD(XYX),或者更准确地说,和他同分。

忽然一阵强光,天旋地转。再睁眼,原来我一题也没做出来,只有 D D ( X Y X ) \sf DD(XYX) DD(XYX) 的名字高悬在 r a n k 1 \rm rank 1 rank1 的位置。而我,走向了另一个极端。

“你已经身陷幻术之中。”

题目描述
构造长为 n n n 的序列 { x i } \{x_i\} {xi} 满足 a i ⩽ x i ⩽ b i a_i\leqslant x_i\leqslant b_i aixibi ∣ x i − x j ∣ ⩾ k    ( 1 ⩽ i < j ⩽ n ) |x_i-x_j|\geqslant k\;(1\leqslant i<j\leqslant n) xixjk(1i<jn) ∀ i ∈ [ 1 , m ] ,    c i ⩾ ∑ j [ l i ⩽ x j ⩽ r i ] \forall i\in[1,m],\;c_i\geqslant \sum_{j}[l_i\leqslant x_j\leqslant r_i] i[1,m],cij[lixjri],或者报告无解。

数据范围与约定
n ∈ [ 1 , 1 0 3 ] n\in[1,10^3] n[1,103] m ∈ [ 0 , 1 0 3 ] m\in[0,10^3] m[0,103],以及 k ∈ [ 1 , 1 0 9 ] k\in[1,10^9] k[1,109],其余数字为 1 0 9 10^9 109 以内自然数。

思路

推了半天线性规划,完全木大。因为我造不出 ∣ x i − x j ∣ |x_i-x_j| xixj 这种玩意儿……

线性规划要选好变量。我以 x i x_i xi 为变元,必定嗝屁。说起来,本题和《遇到困难睡大觉》似乎有一点像……

我们先确定数集 S = { x 1 , x 2 , … , x n } S=\{x_1,x_2,\dots,x_n\} S={x1,x2,,xn},然后试图将 { x i } \{x_i\} {xi} 重排使得 a i ⩽ x i ⩽ b i a_i\leqslant x_i\leqslant b_i aixibi 成立。这里可以用 二分图匹配,亲爱的 Hall \text{Hall} Hall 定理。只需考虑 [ a i , b i ] [a_i,b_i] [ai,bi] 的并集是一个连续段(否则每个连续段可单独考虑)。那么枚举连续段,贪心地想,对应的区间数量最多是所有段内区间。即使区间的并集不完全覆盖连续段,也是必要条件,所以不需要特判。

U ( l , r ) U(l,r) U(l,r) 为,使 [ a i , b i ] ⫅ [ l , r ] [a_i,b_i]\subseteqq[l,r] [ai,bi][l,r] 成立的 i i i 的数量。为了方便求出区间中 x i x_i xi 数量,不妨设 x i x_i xi 的桶排前缀和为 s i s_i si,即 s v = ∑ i [ x i ⩽ v ] s_v=\sum_i[x_i\leqslant v] sv=i[xiv],则题目中的 所有条件 可以 等价 翻译为
{ s r − s l − 1 ⩾ U ( l , r ) ( l ⩽ r ) s r i − s l i − 1 ⩽ c i ( 1 ⩽ i ⩽ m ) s i + k − s i ⩽ 1 \begin{cases} s_r-s_{l-1}\geqslant U(l,r) & (l\leqslant r)\\ s_{r_i}-s_{l_i-1}\leqslant c_i & (1\leqslant i\leqslant m)\\ s_{i+k}-s_i\leqslant 1 \end{cases} srsl1U(l,r)srisli1cisi+ksi1(lr)(1im)

注意还有隐藏条件 s i ∈ Z s_i\in\Z siZ s i + 1 ⩾ s i s_{i+1}\geqslant s_i si+1si,因为要保证 s i s_i si 可以还原出合法的 { x i } \{x_i\} {xi} 。第一个条件显然,第二个条件平凡。无论如何,这都很 差分约束 嘛。只要不存在负环即可。

只是值域偏大了些,导致图建不出来。仔细想想,我们需要在整个值域上建图吗?比如 s r − s l − 1 ⩾ U ( l , r ) s_r-s_{l-1}\geqslant U(l,r) srsl1U(l,r),在 U ( l , r ) U(l,r) U(l,r) 不变时当然是 min ⁡ r \min r minr max ⁡ l \max l maxl 限制最紧。所以有用的 l l l 只可能是某个 a i a_i ai,而 r r r 只可能是某个 b i b_i bi 。注意边是连在 ( l − 1 ) (l-1) (l1) 上的。

此时,仅剩 s i + k − s i ⩽ 1 s_{i+k}-s_i\leqslant 1 si+ksi1 涉及整个值域。也就是说,除了集合
V = { a i − 1 ,    b i ,    l i − 1 ,    r i } V=\{a_i{\rm-}1,\;b_i,\;l_i{\rm-}1,\;r_i\} V={ai1,bi,li1,ri}

中的点连接了其他种类的边,剩下的点都只有这种边。——其实还有 s i + 1 ⩾ s i s_{i+1}\geqslant s_i si+1si 的边。

考虑去掉这些点,因为二度链对最短路的唯一作用是中转站。即,考虑再次使用其余类型的边之前,只走这种类型的边(以及 s i + 1 ⩾ s i s_{i+1}\geqslant s_i si+1si 的边)能走出什么名堂。可以发现 s r − s l ⩽ ⌈ r − l k ⌉    ( l ⩽ r ) s_r-s_{l}\leqslant\lceil{r-l\over k}\rceil\;(l\leqslant r) srslkrl(lr) 无论是代数角度还是原本的现实意义下都是合情合理的等价转化。我去,这句话竟然这么长

于是,只剩下了 O ( n + m ) \mathcal O(n+m) O(n+m) 个点的图,边数是平方级别。存在负边,所以普通 D i j k s t r a \rm Dijkstra Dijkstra 可能不太行。直接用 Bellman-Fort \text{Bellman-Fort} Bellman-Fort 复杂度则是三次方的理论上界。

番外:强调 “理论上界”,因为讲题的同学表示 “大家都坚信 spfa \text{spfa} spfa O ( k ∣ E ∣ ) \mathcal O(k|E|) O(kE) 的,其中 k k k 是某个常数” 就行了……

又是我们最喜欢的数据结构优化最短路。还是 Bellman-Fort \textrm{Bellman-Fort} Bellman-Fort,但是每层的转移不能暴力枚举边。看看两种数量达到平方级别的边的特点:

  • 对于 s r − s l − 1 ⩾ U ( l , r ) s_r-s_{l-1}\geqslant U(l,r) srsl1U(l,r),经典的扫描线 + + + 线段树,可以 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 求出最优转移。
  • 对于 s r − s l ⩽ ⌈ r − l k ⌉ s_r-s_{l}\leqslant\lceil{r-l\over k}\rceil srslkrl,值取决于 ( r   m o d   k ) (r\bmod k) (rmodk) ( l   m o d   k ) (l\bmod k) (lmodk) 大小关系,以及 ⌊ r k ⌋ − ⌊ l k ⌋ \lfloor{r\over k}\rfloor-\lfloor{l\over k}\rfloor krkl 。所以按照模 k k k 的余数排序,用线段树保证 l ⩽ r l\leqslant r lr,就可以做到 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 转移。

O ( n ) \mathcal O(n) O(n) 层,总时间复杂度 O ( n 2 log ⁡ n ) \mathcal O(n^2\log n) O(n2logn)

代码

被卡常了,过不了。卡常的傻🅱️出题人怎么不去吃吃💩啊,差不多得了😅

#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for LONELINESS)
#include <cctype> // ZXY yydSISTER!!!
#include <vector>
using namespace std;
# 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;
}
inline void getMin(int &x, const int &y){
	if(y < x) x = y;
}

const int MAXN = 4005, INF = 0x3fffffff;
int lost; ///< length of segment tree (BIT)
struct BIT{ // query for minimum
	int c[MAXN];
	void clear(){ std::fill(c,c+lost+1,INF); }
	inline void modify(int qid, int qv){
		for(int i=qid; i<=lost; i+=(i&-i))
			c[i] = std::min(c[i],qv);
	}
	inline int query(int qid){
		int res = c[qid];
		for(int i=qid; i; i&=(i-1))
			getMin(res,c[i]);
		return res;
	}
};
namespace SgTree{
	int val[MAXN<<2], tag[MAXN<<2], ass;
	inline void build(const int &n){
		for(ass=1; ass<n+2; ) ass <<= 1;
	}
	inline void clear(){
		fill(val+1,val+(ass<<1),INF);
		memset(tag,0,(ass<<1)<<2);
	}
	inline void pushUp(const int &o){
		val[o>>1] = std::min(val[o],val[o^1])+tag[o>>1];
	}
	inline void setval(int qid, const int &qv){
		for(qid+=ass,val[qid]=qv; qid!=1; )
			pushUp(qid), qid >>= 1;
	}
	void subtract(int l, int r){
		l = ass+l-1, r = ass+r+1;
		for(; (l^r)!=1; l>>=1,r>>=1){
			if(!(l&1)) -- val[l^1], -- tag[l^1];
			if(r&1) -- val[r^1], -- tag[r^1];
			pushUp(l), pushUp(r); // to father
		}
		for(; l!=1; l>>=1) pushUp(l);
	}
	int query(int l, int r){
		l = ass+l-1, r = ass+r+1;
		int lres = INF, rres = INF;
		for(; (l^r)!=1; l>>=1,r>>=1){
			if(!(l&1)) getMin(lres,val[l^1]);
			if(r&1) getMin(rres,val[r^1]);
			lres += tag[l>>1], rres += tag[r>>1];
		}
		getMin(rres,lres), rres += tag[r >>= 1];
		while(r >>= 1) rres += tag[r];
		return rres; // zkw fast
	}
}

int dis[2][MAXN], tmp[MAXN], id[MAXN], k;
std::vector<int> buc[MAXN];
int rem[MAXN], quo[MAXN]; ///< reduce constant
BIT zuo, you; ///< F**K YOUR MOTHER
bool spfa(const int &source, const int &n,
  int *a, int *b, int *c, const int &m){
	SgTree::build(n);
	int *now = dis[0], *nxt = dis[1];
	fill(now+2,now+n+1,INF), now[1] = 0;
	for(int tim=0; tim!=2000; ++tim,swap(now,nxt)){
		nxt[n] = now[n]; // suffix minimum
		drep(i,n-1,1) nxt[i] = std::min(now[i],nxt[i+1]);
		SgTree::clear();
		drep(i,n,1){ // first type
			for(const int &v : buc[i])
				SgTree::subtract(v,n);
			SgTree::setval(i,nxt[i]); // won't be tagged
			getMin(nxt[i],SgTree::query(i,n));
		}
		zuo.clear(), you.clear();
		for(int i=1; i<=n; ++i){
			zuo.modify(rem[i],nxt[i]-quo[i]+1);
			you.modify(lost+1-rem[i],nxt[i]-quo[i]);
			int upd = zuo.query(rem[i]-1);
			getMin(upd,you.query(lost+1-rem[i]));
			getMin(nxt[i],upd+quo[i]); // fall in
		}
		rep(i,1,m) getMin(nxt[b[i]],nxt[a[i]]+c[i]);
		bool done = true; // steady
		rep(i,1,n) if(now[i] != nxt[i]){
			done = false; break; // updated
		}
		if(done) return true;
	}
	return false;
}

int tot, a[MAXN], b[MAXN], c[MAXN], ddg[MAXN];
void discretize(int *l, int *r){
	for(int *i=l; i!=r; ++i) *i = int(
		std::lower_bound(tmp+1,tmp+tot+1,*i)-tmp);
}
int main(){
	int n = readint(), m = readint();
	k = readint(); // global
	rep(i,1,n) a[i] = readint(), b[i] = readint()-k+1;
	rep(i,n+1,n+m){
		a[i] = readint(), b[i] = readint()-k+1;
		c[i] = readint(); // maximum density
	}
	tot = n+m, memcpy(tmp+1,a+1,tot<<2);
	memcpy(tmp+tot+1,b+1,tot<<2), tot <<= 1;
	std::sort(tmp+1,tmp+tot+1); // useful nodes
	tot = int(std::unique(tmp+1,tmp+tot+1)-tmp-1);
	discretize(a+1,a+n+m+1), discretize(b+1,b+n+m+1);
	rep(i,1,n) buc[a[i]].push_back(b[i]);
	rep(i,1,tot) rem[i] = tmp[i]%k, quo[i] = tmp[i]/k;
	memcpy(ddg+1,rem+1,tot<<2), std::sort(ddg+1,ddg+tot+1);
	lost = int(std::unique(ddg+1,ddg+tot+1)-ddg-1);
	rep(i,1,tot) rem[i] = int(std:: // discretize
		lower_bound(ddg+1,ddg+lost+1,rem[i])-ddg);
	puts(spfa(1,tot,a+n,b+n,c+n,m) ? "Yes" : "No");
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值