POJ_3657
题目的大意是给出若干个条件x y A,表示区间[x,y]中最小整数为A,而且所有整数都是各不相同的,问到第几个条件的时候就会出现矛盾。
细想一下后会发现,实际上我们只要能把每个[x,y]的最小值A放好就一定可以构造出一组解,剩下的值可以直接放INF、INF+1之类的东西就可以了。那么我们就可以得到无解的情况,就是对于某个A,发现其不能放在任何一个位置。
对于任意一个条件x y A,实际上我们可以得到一条信息,就是区间[x,y]内所有整数都大于或等于A。这样满足完所有条件之后,就会得到任意一个数的取值范围。那么接下来就要考虑所有的最小值A是否都可以放到某个位置。这时要考虑一个问题,比如样例中1 10 7和5 19 7,我们就可以得到7只能放在[5,10]中的某个位置,也就是说对于具备相同A的条件,A只能放在这些条件的区间的交集之中。如果这个交集中所有整数都是大于A的话,那么显然A就没有地方可以放了。
于是我们就得到了一个思路,为了求出在哪个位置出现矛盾,我们先二分Q将其转化成判定性问题,比如现在考虑前n个条件是否会推出矛盾。用线段树维护每个点的最小值,然后遍历一遍条件进行区间修改的操作。最后再将条件排序,求出有相同A的条件的区间的交集,并求交集中的最小值,如果交集为空或者最小值比A大都是不合法的。这样做复杂度是O(logQ*(NlogN+QlogQ+QlogN)),其中logQ*N*logN这个部分太大的,即便使用离散化降到logQ*Q*logQ,整体还是会由于常数过大而TLE。
于是我们就要想办法优化算法。如果先将n个条件按A降序列,我们就可以将问题变得更简单一些。顺序扫描排序好的条件,每次查询有相同A的条件的区间的交集是否有无色的部分,并对这些区间的并集染色,如果交集为空或者交集中没有无色的部分就是不合法的,其中交集中没有无色的部分就等同于交集中所有的整数都比A大。由于我们只要考虑区间是否被染色,而不用考虑染色的值,这样染色就可以用并查集来实现,降低了复杂度。整体来看,用并查集染色的复杂度是O(N)的,每次查询可以做到O(1)的复杂度,排序的复杂度是O(QlogQ),于是整体的复杂度就是O(logQ*(QlogQ+N+Q))。
此外我写的递归的并查集会爆栈,于是不得不写成了非递归的形式。
#include<stdio.h> #include<string.h> #include<algorithm> #define MAXD 1000010 #define MAXQ 25010 using namespace std; int N, Q, X, p[MAXD], col[MAXD], s[MAXD]; struct Que { int x, y, z; bool operator < (const Que &t) const { return z > t.z; } }q[MAXQ], t[MAXQ]; int find(int x) { int top = 0; while(p[x] != x) s[++ top] = x, x = p[x]; while(top) p[s[top --]] = x; return x; } void init() { int i; for(i = 1; i <= Q; i ++) scanf("%d%d%d", &q[i].x, &q[i].y, &q[i].z); } void refresh(int x, int y) { int i = find(x - 1), j = find(x); if(col[i]) p[i] = j; col[j] = 1; for(i = j; i <= y; i = j) { j = find(i + 1); if(col[j] || j <= y) col[j] = 1, p[i] = j; } } int deal(int n) { int i, j, x, y, tx, ty, fa; for(i = 0; i <= N + 1; i ++) p[i] = i, col[i] = 0; for(i = 1; i <= n; i ++) t[i] = q[i]; sort(t + 1, t + 1 + n); for(i = 1; i <= n; i = j + 1) { x = tx = t[i].x, y = ty = t[i].y; for(j = i; j < n && t[j + 1].z == t[j].z;) ++ j, x = max(x, t[j].x), y = min(y, t[j].y), tx = min(tx, t[j].x), ty = max(ty, t[j].y); if(x > y) return 0; fa = find(x); if(col[fa] != 0 && find(fa) >= y) return 0; refresh(tx, ty); } return 1; } void solve() { int mid, min, max; min = 1, max = Q + 1; for(;;) { mid = (min + max) >> 1; if(mid == min) break; if(deal(mid)) min = mid; else max = mid; } printf("%d\n", mid == Q ? 0 : mid + 1); } int main() { while(scanf("%d%d", &N, &Q) == 2) { init(); solve(); } return 0; }