Luogu 3626 [APIO2009]会议中心
很优美的解法。
推荐大佬博客
如果没有保证字典序最小这一个要求,这题就是一个水题了,但是要保证字典序最小,然后我就不会了……
如果一条线段能放入一个区间$[l', r']$并且不影响最优答案,那么对于这条线段$[l, r]$,设$solve(l, r)$表示$[l, r]$这个区间里面最多能放多少条线段,一定要有条件$solve(l', l - 1) + solve(r + 1, r') + 1 == solve(l', r')$。
那么贪心的时候顺便考虑一下怎么检验的问题就可以了,如果暴力检验是$O(n)$的,考虑优化,利用一下倍增思想,我们设$f_{i, j}$表示从$i$开始选择$2^{j}$条线段所能到达的最靠左的右端点,那么在离散化之后就可以用$O(nlogn)$的时间预处理出$f$数组,这样子每一次检验求$solve$的时间是$O(logn)$的,具体怎么处理可以参照下面的代码。
考虑一下最后怎么进行的贪心,对于每一条线段,我们检验一下是不是能放,这个检验的过程可以用很多数据结构做到$O(logn)$,如果能放入,我们就找出之前放过的最前的右端点和最后的左端点,然后chk一下上面的条件是否成立就可以了。
感觉这个过程写个线段树更直观一点,还是学习了一下上面那位大佬高能的树状数组写法。
时间复杂度$O(nlogn)$。
Code:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 2e5 + 5; const int Lg = 20; const int inf = 1 << 30; int n, tot = 0, maxn = 0; int l[N], r[N], f[N << 1][Lg]; int ans[N], lmax[N << 1], rmax[N << 1], lsum[N << 1], rsum[N << 1]; struct Innum { int val, id; } in[N << 1]; bool cmpIn(const Innum &x, const Innum &y) { if(x.val != y.val) return x.val < y.val; else return x.id < y.id; } inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9'|| ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } inline int max(int x, int y) { return x > y ? x : y; } inline int min(int x, int y) { return x > y ? y : x; } inline void chkMax(int &x, int y) { if(y > x) x = y; } inline void discrete() { sort(in + 1, in + 1 + tot, cmpIn); for(int cnt = 0, i = 1; i <= tot; i++) { if(in[i].val != in[i - 1].val) ++cnt; maxn = max(maxn, cnt); if(in[i].id > n) r[in[i].id - n] = cnt; else l[in[i].id] = cnt; } } #define lowbit(x) ((x) & (-x)) inline void aSum(int *now, int x) { for(; x <= maxn; x += lowbit(x)) ++now[x]; } inline int qSum(int *now, int x) { int res = 0; for(; x > 0; x -= lowbit(x)) res += now[x]; return res; } inline void aMax(int *now, int x) { for(int t = x; x <= maxn; x += lowbit(x)) chkMax(now[x], t); } inline int qMax(int *now, int x) { int res = 0; for(; x > 0; x -= lowbit(x)) chkMax(res, now[x]); return res; } inline int solve(int ln, int rn) { int res = 0; for(int i = 18; i >= 0; i--) if(f[ln][i] <= rn) res += (1 << i), ln = f[ln][i] + 1; return res; } int main() { read(n); for(int i = 1; i <= n; i++) { read(l[i]), read(r[i]); in[++tot] = (Innum) {l[i], i}; in[++tot] = (Innum) {r[i], i + n}; } discrete(); memset(f, 0x7f, sizeof(f)); for(int i = 1; i <= n; i++) f[l[i]][0] = min(f[l[i]][0], r[i]); for(int i = maxn; i >= 1; i--) { f[i][0] = min(f[i][0], f[i + 1][0]); for(int j = 1; f[i][j - 1] <= maxn && (1 << j) <= n; j++) f[i][j] = f[f[i][j - 1] + 1][j - 1]; } /* for(int i = 1; i <= maxn; i++) printf("%d ", f[i][0]); printf("\n"); */ /* for(int i = 1; i <= n; i++) printf("%d %d\n", l[i], r[i]); printf("\n"); */ tot = 0; for(int i = 1; i <= n; i++) { int v = tot - qSum(rsum, l[i] - 1) - qSum(lsum, maxn - r[i]); if(v > 0) continue; int lst = qMax(rmax, l[i] - 1), nxt = maxn - qMax(lmax, maxn - r[i]); if(solve(lst + 1, l[i] - 1) + solve(r[i] + 1, nxt) + 1 == solve(lst + 1, nxt)) { ans[++tot] = i; aSum(rsum, r[i]), aSum(lsum, maxn - l[i] + 1); aMax(rmax, r[i]), aMax(lmax, maxn - l[i] + 1); } // printf("%d ", v); } // printf("\n"); printf("%d\n", tot); for(int i = 1; i <= tot; i++) printf("%d ", ans[i]); printf("\n"); return 0; }