[Solution] POI2007 MEG-Megalopolis
本博客首发于洛谷:
据老师所说这是一道模拟题
然而身为蒟蒻的我在考场上只混到了七十分(利用邻接矩阵暴力)
但显然邻接矩阵开不了这么大的内存
70分Code
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 int mp[1000][1000]; 5 int way[200000][3]; 6 int ans; 7 void dfs(int num); 8 int main() { 9 freopen("meg.in","r",stdin); 10 freopen("meg.out","w",stdout); 11 cin>>n; 12 int i; 13 int head,tail; 14 for(i=1; i<n; i++) { 15 cin>>head>>tail; 16 mp[head][tail]=1; 17 mp[tail][head]=1;//邻接矩阵存图 18 } 19 cin>>m; 20 char ch; 21 for(i=1; i<n+m; i++) { 22 cin>>ch; 23 way[i][0]=ch-'A'+1; 24 if(ch=='A') cin>>way[i][1]>>way[i][2]; 25 else cin>>way[i][1]; 26 } 27 for(i=1; i<n+m; i++) { 28 if(way[i][0]==1) mp[way[i][1]][way[i][2]]=2,mp[way[i][2]][way[i][1]]=2; 29 else { 30 ans=0; 31 dfs(way[i][1]); 32 cout<<ans<<endl; 33 } 34 } 35 fclose(stdin); 36 fclose(stdout); 37 return 0; 38 } 39 40 void dfs(int num) { 41 if(num==1) return; 42 for(int i=num-1; i>=1; i--) 43 if(mp[num][i]) { 44 if(mp[num][i]==1) ans++; 45 dfs(i); 46 } 47 }//深搜路径
考完后,我翻了一下洛谷的题解,但发现所有题解都无法通过我们考试时候的第八个数据点
但是有一个并查集思路的代码能AK掉除了这个点的所有数据(这个点会爆栈)
90 Code
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1010; 4 int a[N][N]; 5 struct node 6 { 7 int x, y, h; 8 bool operator <(const node &z)const 9 { 10 return h<z.h; 11 } 12 }p[N*N]; 13 int pl; 14 int fa[N*N], num[N][N], s[N*N]; 15 int find(int x) 16 { 17 if(fa[x] == x) return x; 18 return fa[x] = find(fa[x]); 19 } 20 int mov[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; 21 void hb(int x, int y) 22 { 23 int X = find(x), Y = find(y); 24 s[Y] |= s[X]; 25 fa[X] = Y; 26 /*这里要注意了,不能是fa[Y] = X。 27 如果是fa[Y] = X, 那么前面应改成s[X] |= s[Y]。 28 假如, Y集合中有抽水机, X集合中没有。 29 按 30 s[Y] |= s[X]; 31 fa[Y] = X; 32 那么合并后 s[Y] = 1, s[X] = 0。 33 如果是 34 则find(Y) = X 35 s[X] = 0, 所以集合中没有抽水机。 36 然而,这个集合是有抽水机的。 37 */ 38 return ; 39 } 40 int main() 41 { 42 int n, m, ans = 0; 43 scanf("%d%d", &n, &m); 44 memset(a, 127, sizeof(a)); 45 for(int i = 1; i <= n; i++) 46 for(int j = 1; j <= m; j++) 47 scanf("%d", &a[i][j]); 48 for(int i = 1; i <= n; i++) 49 for(int j = 1; j <= m; j++) 50 { 51 num[i][j] = pl; 52 p[++pl] = (node){i, j, abs(a[i][j])}; 53 } 54 sort(p+1, p+1+pl); 55 for(int i = 1; i <= pl; i++) 56 fa[i] = i; 57 for(int i = 1; i <= pl; i++) 58 { 59 int X = p[i].x, Y = p[i].y; 60 for(int j = 0; j < 4; j++) 61 { 62 int x = X + mov[j][0], y = Y + mov[j][1]; 63 if(abs(a[x][y]) <= p[i].h) 64 hb(num[x][y], num[X][Y]); 65 } 66 if(p[i].h != p[i+1].h) 67 for(int j = i; p[j].h == p[i].h; j--) 68 if(a[p[j].x][p[j].y]>0) 69 { 70 int x = find(num[p[j].x][p[j].y]); 71 if(!s[x]) 72 s[x] = 1, ans++; 73 } 74 } 75 printf("%d\n", ans); 76 return 0; 77 }
接下来我们就要思考正确的100分$Code$
正如很多题解上写的一样我们可以采用$dfs$序加线段树的写法来$AC$掉洛谷的数据点 但还是存在和上一个解法一样的问题,$dfs$会爆栈
所以我们可以考虑另外一种写法
树链剖分
树链剖分:
通过树的先序遍历(遍历子节点时,优先遍历子树最大的子节点),构造dfs序线段树。
每个节点都属于某条重链,重链的条数不超过 $log_n$ 条。
而每条重链在线段树上是一段连续区间。
由于任一轻儿子对应的子树大小要小于父节点所对应子树大小的一半,因此从一个轻儿子沿轻边向上走到父节点后 所对应的子树大小至少变为两倍以上,经过的轻边条数自然是不超过$log_n$的。
然后由于重链都是间断的 (连续的可以合成一条),所以经过的重链的条数是不超过轻边条数$+1$的,因此经过重链的条数也是$log$级别的
故对于一条树上的路径,对应于线段树上的若干段区间,每次操作的复杂度为$O(log_n\times log_n).$
树链剖分较多用于处理树上的路径问题。
但仅仅如此还不够,为了保证时间复杂度在$O(log_n \times n)$以内,我们还需运用另外一种对树的操作
$Splay$算法
$splay$操作$ splay$操作是指将一个点一直上旋,知道某个指定点。默认是根节点。
因为对父亲,儿子和祖先进行双旋,保证了层数在$log_n$以内 从而使我们递归询问的层数减少
这样就稳定不会爆栈还跑的飞起
$AC Code$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define lc c[x][0] 4 #define rc c[x][1] 5 const int N=250009; 6 int f[N],c[N][2],s[N];//s表示路径上土路总和 7 bool tu[N];//是否为土路 8 9 int read(); 10 bool nroot(int x); 11 void pushup(int x); 12 void rotate(int x); 13 void splay(int x); 14 void access(int x); 15 16 int main(){ 17 freopen("meg.in","r",stdin); 18 freopen("meg.out","w",stdout); 19 int n,m,u,v,i; 20 char c; 21 n=read(); 22 for(i=1;i<n;++i){ 23 u=read();v=read(); 24 if(u>v) swap(u,v);//题目保证了编号小的为父亲 25 f[v]=u;tu[v]=1; 26 } 27 m=read(); 28 for(i=n+m-1;i;--i){ 29 cin>>c; 30 if(c=='A'){ 31 u=read();v=read(); 32 if(u>v) swap(u,v); 33 splay(v);tu[v]=0;pushup(v);//转到根再改 34 } 35 else{ 36 v=read(); 37 access(v);splay(v); 38 printf("%d\n",s[v]); 39 } 40 } 41 return 0; 42 } 43 44 int read(){ 45 int x=0,f=1; 46 char ch=getchar(); 47 while(ch<'0'||ch>'9'){ 48 if(ch=='-') 49 f=-1; 50 ch=getchar(); 51 } 52 while(ch>='0'&&ch<='9'){ 53 x=(x<<1)+(x<<3)+(ch^48); 54 ch=getchar(); 55 } 56 return x*f; 57 } 58 59 bool nroot(int x){ 60 return c[f[x]][0]==x||c[f[x]][1]==x; 61 } 62 63 void pushup(int x){ 64 s[x]=s[lc]+s[rc]+tu[x]; 65 } 66 67 void rotate(int x){ 68 int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k]; 69 if(nroot(y))c[z][c[z][1]==y]=x;c[x][!k]=y;c[y][k]=w; 70 f[w]=y;f[y]=x;f[x]=z; 71 pushup(y); 72 } 73 74 void splay(int x){ 75 int y; 76 while(nroot(x)){ 77 if(nroot(y=f[x])) 78 rotate((c[f[y]][0]==y)^(c[y][0]==x)?x:y); 79 rotate(x); 80 } 81 pushup(x); 82 } 83 84 void access(int x){ 85 for(int y=0;x;x=f[y=x]) 86 splay(x),rc=y,pushup(x); 87 }//Splay链剖分(似乎所有函数节选自LCT)