1. 程式人生 > >【題解】SCOI2006萌萌噠

【題解】SCOI2006萌萌噠

etc 題解 pac 沒有 操作 區間 class hellip 如果

  看到這題,首先想到\(n^{2}\)的暴力,就是用並查集暴力合並兩個相等的點。但由於這樣會導致反復地訪問同一個操作,顯然是不能夠的。於是我們可以聯想這題的特殊性質,就是互相連變的點都是一段一段的區間。然後很自然地聯想到線段樹分解優化,堅定地想了一個半小時還多,然後很自然地掛了。天知道我是怎麽把一個暴力的復雜度給生生算出 \(nlog^{2}m\) 的復雜度來的……(⊙﹏⊙)

  線段樹的區間分割並不是很靈活,而且完全沒有改變暴力的本質。於是灰溜溜的去看題解,倍增?恍然大悟一般。是啊,分解區間我們還有倍增呀~我們可以用 \(f[i][j]\) 表示 \(i\) 與向後的 \(2^{j}\) 個點,我們就可以\(O(1))\)地完成區間的合並了。最後,由於如果 \(f[i][j]\) 與 \(f[k][j]\) 是相等的,那麽他們下面的所有倍增區間也都是相等的。我們類似 push_down 操作,讓兒子繼承一下父親的集合關系即可。受教啦~

#include <bits/stdc++.h>
using namespace std;
#define maxn 100500
#define mod 1000000007
#define LL long long
int n, m, cnt, fa[maxn * 20];
int lc[maxn * 20], rc[maxn * 20];
int bit[20], Log[maxn], f[maxn][20];

int read()
{
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < 0 || c > 
9) { if(c == -) k = -1; c = getchar(); } while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar(); return x * k; } void init() { bit[0] = 1; for(int i = 1; i < 20; i ++) bit[i] = bit[i - 1] << 1; Log[0] = -1; for(int i = 1; i <= n; i ++) Log[i] = Log[i >> 1
] + 1; for(int j = 0; bit[j] <= n; j ++) for(int i = 1; i + bit[j] - 1 <= n; i ++) { f[i][j] = ++ cnt; if(j) { lc[cnt] = f[i][j - 1]; rc[cnt] = f[i + bit[j - 1]][j - 1]; } } for(int i = 1; i <= cnt; i ++) fa[i] = i; } int find(int x) { return ((fa[x] == x) ? x : fa[x] = find(fa[x])); } void merge(int x, int y) { x = find(x), y = find(y); if(x != y) fa[x] = y; } int main() { n = read(), m = read(); init(); for(int i = 1; i <= m; i ++) { int l1 = read(), r1 = read(), l2 = read(), r2 = read(); int k = Log[r1 - l1 + 1]; merge(f[l1][k], f[l2][k]); merge(f[r1 - bit[k] + 1][k], f[r2 - bit[k] + 1][k]); } for(int i = cnt, tem; i > n; i --) if((tem = find(i)) != i) { merge(lc[i], lc[tem]); merge(rc[i], rc[tem]); } int ans = 0, base = 9; for(int i = 1; i <= n; i ++) ans += (find(i) == i); for(int i = 1; i < ans; i ++) base = ((LL) base * 10LL) % mod; printf("%d\n", base); return 0; }

【題解】SCOI2006萌萌噠