题解:P17600 「RiOI-8」城ケ崎 ノア
Karieciation · · 题解
题解区怎么没有我的做法,感觉二分+序列并查集比较自然。
保证整体思路和代码全部由人类完成,但是由于笔者表达能力太菜某些部分使用 gpt 辅助解释证明。
思路
有个比较显然的贪心:想要丑陋度最小,如果一个区间后面还能再放一个区间,放
::::info[gpt 给的贪心策略的严谨化版本] 存在一个最优方案,使多区间的行都不与其他非空行相邻;其余单区间行尽量用空行隔开。 ::::
::::info[我给出一个比较感性的证明。]
如果合并了一个区间,那么可以天然地空出一行,受到合并的区间一定可以不再受到干扰。
要放
| 那么我们很容易知道,最后没有被合并的区间一定能互相有交,不论如何排列都无法避免有丑陋度,如果想要避免这些区间获得丑陋度,此时除了最后一句的避免方法,剩下想要避免必定会干扰到上面的多区间的行,这样一定不优。 |
|---|
::::info[gpt 给的证明。]
设第
即同一列中最多有多少个区间覆盖它。画布共有
下面证明最小丑陋度为
一、丑陋度的下界
取一列
集合
- 它们不能放在同一行,否则会发生颜色混合;
- 如果它们放在相邻两行,这两种颜色之间一定会产生一次晕染。
设这
每一段中,若有
点丑陋度。
相邻两段之间至少要有一个不属于
于是
又因为丑陋度非负,所以
二、构造达到下界的方案
1. 将区间分成
按
处理区间
- 如果存在一组,其最后一个区间的右端点小于
l_i ,就把当前区间放入该组; - 否则新开一组。
这里要求右端点严格小于
这样每组中的区间互不重叠,可以放在同一行。这个贪心恰好得到
- 若需要新开一组,那么当前区间的左端点
l_i 被所有已有组的最后一个区间覆盖,也被当前区间覆盖。因此新开组是必要的; - 在列
x 上有K 个区间覆盖,它们两两重叠,必然分属不同的组,所以至少需要K 组。
因此总共正好有
由于集合
2. 安排各组对应的行
设:
每个多区间组都包含一个
先拿出
还剩
个空行,以及
每个单区间组中的区间都属于
| 这与下界相等,因此构造最优。 |
|---|
实现是简单的。记一下原下标,按照
构造按照上面的贪心策略把原来我们构造出的区间所在行重构并且按照原下标还原出来即可。
代码
::::success[code]
#include <bits/stdc++.h>
char buf[1000010], obuf[1000010], *p1, *p2, *p3=obuf;
#define gc (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1000000, stdin), p1==p2) ? EOF : *p1++)
#define pc(x) (p3==obuf+1000000&&fwrite(p3=obuf, 1, 1000000, stdout), *p3++=(x))
#define LF pc('\n')
#define SP pc(' ')
#define ll unsigned long long
#define ui unsigned int
#define N 1000005
#define M 1000005
#define K 20
#define INF 0x3f3f3f3f
#define tT template<typename T>
#define tA template<typename T, typename... Ts>
using namespace std;
namespace FastIO {
tT inline void read(T& x) {
x=0; char ch=gc; while(ch<'0'||ch>'9') ch=gc;
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch&15), ch=gc;
}
tA inline void read(T& x, Ts&... args) { read(x); read(args...); }
tT inline void write(T x) {
if(x==0) { pc('0'); return ; }
static ui a[40]; ui t=0; while(x) a[++t]=x%10, x/=10; while(t) pc(a[t--]|48);
}
tA inline void write(T x, Ts... args) { write(x); if(sizeof...(args)!=0) { SP; write(args...); } }
}
using namespace FastIO;
namespace M_K_W_ {
tT inline T max(const T a, const T b) { return a>b ? a : b; }
tT inline T min(const T a, const T b) { return a<b ? a : b; }
struct node { ui l, r, i; } a[N];
ui n, f[N], g[N], cnt[N], now[N], ans, b[N];
inline bool cmp(node a, node b) {
return a.l==b.l ? a.i<b.i : a.l<b.l;
}
inline ui Find(const ui x) {
return x==f[x] ? x : f[x]=Find(f[x]);
}
inline ui bound(ui l, ui r, const ui x) {
while(l<=r) {
const ui mid=l+r>>1;
if(a[mid].l>x) r=mid-1;
else l=mid+1;
} return Find(l);
}
inline void solve() {
read(n); ans=n-1;
for(ui i=1; i<=n; ++i)
read(a[i].l, a[i].r), cnt[a[i].i=f[i]=g[i]=i]=now[i]=0;
f[n+1]=n+1;
sort(a+1, a+1+n, cmp);
for(ui i=1; i<n; ++i) {
const ui t=bound(i+1, n, a[i].r);
if(t<=n)
f[t]=Find(t+1), ++cnt[g[t]=g[i]], ans-=2;
} ui p=1, x=0;
if(ans<n) write(ans), LF;
else pc('0'), LF;
for(ui i=1; i<=n; ++i)
if(g[i]==i&&cnt[g[i]]>0)
now[i]=p, p+=2, x+=cnt[g[i]]-1;
for(ui i=1; x&&i<=n; ++i)
if(x&&g[i]==i&&!cnt[g[i]])
now[i]=p, p+=2, --x;
for(ui i=1; i<=n; ++i)
if(g[i]==i&&!now[i])
now[i]=p++;
for(ui i=1; i<=n; ++i)
b[a[i].i]=now[g[i]];
for(ui i=1; i<n; ++i)
write(b[i]), SP;
write(b[n]), LF;
}
inline void main() {
ui T;
read(T);
while(T--)
solve();
}
}
int main() {
M_K_W_ :: main();
fwrite(obuf, 1, p3-obuf, stdout);
return 0;
}
::::