【BZOJ 4569】【SCOI 2016】萌萌哒
http://www.lydsy.com/JudgeOnline/problem.php?id=4569
用ST表表示所有区间,根据ST表中表示的区间长度种一棵nlogn的树,类似线段树,每个节点的左孩子和右孩子表示的区间拼接起来的总区间即为这个节点表示的区间。树上同一层节点表示的区间长度相同,同一层节点就可以用并查集来维护。
这样对于m个限制,用ST表把每个限制中的区间拆成前一块和后一块两块,限制中要求相同的两个区间的前一块和后一块分别用并查集合并,可以满足这两个区间完全相同。
最后从树的顶部(树根可能有很多)逐层下推就可以啦(把这一层两个区间相同的信息下推到这两个区间在下一层中的前一块相同并且后一块相同)~~~推到最底层统计有多少个不相同的数字,(个数-1)×10×9即为答案。
时间复杂度$O(n\log na(n)+ma(n))$
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N = 100003; int in() { int k = 0, fh = 1; char c = getchar(); for(; c < '0' || c > '9'; c = getchar()) if (c == '-') fh = -1; for(; c >= '0' && c <= '9'; c = getchar()) k = (k << 3) + (k << 1) + c - '0'; return k * fh; } int n, m, f[N][18], cnt, lson[N * 18], rson[N * 18], fa[N * 18], Log_2[N]; void init() { cnt = 0; for(int i = 1; i <= n; ++i) { if ((1 << (cnt + 1)) <= i) ++cnt; Log_2[i] = cnt; } cnt = 0; for(int j = 0; (1 << j) <= n; ++j) for(int i = 1; i + (1 << j) - 1 <= n; ++i) { f[i][j] = ++cnt; lson[cnt] = f[i][j - 1]; rson[cnt] = f[i + (1 << (j - 1))][j - 1]; } for(int i = 1; i <= cnt; ++i) fa[i] = i; } int find(int a) { if (fa[a] == a) return a; fa[a] = find(fa[a]); return fa[a]; } void merge(int a, int b) { a = find(a); b = find(b); if (a != b) fa[a] = b; } int main() { n = in(); m = in(); init(); int len, x1, y1, x2, y2; while (m--) { x1 = in(); y1 = in(); x2 = in(); y2 = in(); len = Log_2[y1 - x1 + 1]; merge(f[x1][len], f[x2][len]); merge(f[y1 - (1 << len) + 1][len], f[y2 - (1 << len) + 1][len]); } int t; for(int i = cnt; i > n; --i) if ((t = find(i)) != i) { merge(lson[i], lson[t]); merge(rson[i], rson[t]); } t = 0; for(int i = 1; i <= n; ++i) if (find(i) == i) ++t; int ret = 9; while (--t) ret = (int) (1ll * ret * 10 % 1000000007); printf("%d\n", ret); return 0; }
对于区间相同之类的限制,即区间操作,线段树往往是十分强大的。但也有线段树解决不了的题,就像这道题,因为线段树表示区间的logn个节点是"不规则的",随区间位置的改变而变化,这样对于两个不同位置相同长度的区间就很难用并查集来维护了。没有区间修改操作时,ST表表现的十分优越,不仅可以$O(1)$回答可合并信息,而且可以快速的且有规律的将区间"剖分",结合其他数据结构很方便地维护两个长度相同的区间之间的关系。
NOI 2017 Bless All