题目
题目背景
我以为,我
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
ai⩽xi⩽bi 且
∣
x
i
−
x
j
∣
⩾
k
(
1
⩽
i
<
j
⩽
n
)
|x_i-x_j|\geqslant k\;(1\leqslant i<j\leqslant n)
∣xi−xj∣⩾k(1⩽i<j⩽n) 且
∀
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],ci⩾∑j[li⩽xj⩽ri],或者报告无解。
数据范围与约定
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| ∣xi−xj∣ 这种玩意儿……
线性规划要选好变量。我以 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 ai⩽xi⩽bi 成立。这里可以用 二分图匹配,亲爱的 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[xi⩽v],则题目中的 所有条件 可以 等价 翻译为
{
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}
⎩⎪⎨⎪⎧sr−sl−1⩾U(l,r)sri−sli−1⩽cisi+k−si⩽1(l⩽r)(1⩽i⩽m)
注意还有隐藏条件 s i ∈ Z s_i\in\Z si∈Z 和 s i + 1 ⩾ s i s_{i+1}\geqslant s_i si+1⩾si,因为要保证 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) sr−sl−1⩾U(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) (l−1) 上的。
此时,仅剩
s
i
+
k
−
s
i
⩽
1
s_{i+k}-s_i\leqslant 1
si+k−si⩽1 涉及整个值域。也就是说,除了集合
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={ai−1,bi,li−1,ri}
中的点连接了其他种类的边,剩下的点都只有这种边。——其实还有 s i + 1 ⩾ s i s_{i+1}\geqslant s_i si+1⩾si 的边。
考虑去掉这些点,因为二度链对最短路的唯一作用是中转站。即,考虑再次使用其余类型的边之前,只走这种类型的边(以及
s
i
+
1
⩾
s
i
s_{i+1}\geqslant s_i
si+1⩾si 的边)能走出什么名堂。可以发现
s
r
−
s
l
⩽
⌈
r
−
l
k
⌉
(
l
⩽
r
)
s_r-s_{l}\leqslant\lceil{r-l\over k}\rceil\;(l\leqslant r)
sr−sl⩽⌈kr−l⌉(l⩽r) 无论是代数角度还是原本的现实意义下都是合情合理的等价转化。我去,这句话竟然这么长。
于是,只剩下了 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(k∣E∣) 的,其中 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) sr−sl−1⩾U(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 sr−sl⩽⌈kr−l⌉,值取决于 ( 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 ⌊kr⌋−⌊kl⌋ 。所以按照模 k k k 的余数排序,用线段树保证 l ⩽ r l\leqslant r l⩽r,就可以做到 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;
}