bzoj3648: 寝室管理(环套树+点分治)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj3648: 寝室管理(环套树+点分治)相关的知识,希望对你有一定的参考价值。
好题。。写了两个半小时hh,省选的时候要一个半小时内调出这种题目还真是难= =
题目大意是给一棵树或环套树,求点距大于等于K的点对数
这里的树状数组做了一点变换。不是向上更新和向下求和,而是反过来,所以求和的时候sum(k)实际上是求k到n的和
所以我们要求大于等于k的dis的次数和,就是求sum(1,k-1),注意k要减一
如果是树,就是常规的点分治,然后用树状数组维护dis【t】出现的次数
如果是环套树,找环之后割掉一条边,然后先求这棵树的答案。接着考虑过了这条割掉的边s--t的情况:我们以这条边的一点t为起点,对于环上的每个点(即每棵子树的根),我们求出这棵子树的所有dis后,dis+cir_len-i为所求链的第一部分,链的第二部分的长度为k-(dis+cir_len-i),用树状数组求就可以了。更新树状数组的时候不是更新dis,而是dis+i;i即根到割的那条边的另一个点s的距离&&这条割边
完美解决。。然而常数还是很大,跑了两秒多
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define INF 0x3f3f3f3f 5 #define LL long long 6 using namespace std; 7 const int maxn = 100010; 8 struct node{ 9 int to,next; 10 }e[maxn*2]; 11 int n,m,K,head[maxn],size[maxn],vis[maxn],sz,total,root,dis[maxn],tot,fa[maxn]; 12 int ban1,ban2,cir[maxn],len=0; 13 LL p[maxn*2],ans; 14 15 void insert(int u, int v){ 16 e[++tot].to=v; e[tot].next=head[u]; head[u]=tot; 17 } 18 19 void add(int x, LL c){ 20 for (;x;x-=x&-x) p[x]+=c; 21 } 22 LL query(int x){ //注意:这里的树状数组是倒过来的, query(1,k) 是求得k+1到n 23 LL ret=0; 24 if (x<1) x=1; 25 for (;x<=2*n;x+=x&-x) ret+=p[x]; 26 return ret; 27 } 28 29 void getroot(int u, int f){ 30 size[u]=1; int mx=0; 31 for (int v,i=head[u]; i; i=e[i].next){ 32 if (vis[v=e[i].to] || v==f || i==ban1 || i==ban2) continue; 33 getroot(v,u); 34 size[u]+=size[v]; 35 mx=max(mx,size[v]); 36 } 37 mx=max(mx,total-size[u]); 38 if (mx<sz) sz=mx,root=u; 39 } 40 41 void getdis(int u, int f, int d){ 42 dis[++tot]=d; 43 for (int i=head[u],v; i; i=e[i].next){ 44 if (vis[v=e[i].to] || v==f || i==ban1 || i==ban2) continue; 45 getdis(v,u,d+1); 46 } 47 } 48 49 void work(int u){ 50 total=size[u]?size[u]:n; 51 sz=INF; 52 getroot(u,0); u=root; 53 vis[u]=1; tot=0; 54 for (int i=head[u],v,last=0; i; i=e[i].next){ 55 if (vis[v=e[i].to] || i==ban1 || i==ban2) continue; 56 last=tot; 57 getdis(v,0,1); //printf("%d\n", tot); 58 for (int j=last+1; j<=tot; j++) ans+=query(K-1-dis[j]); 59 for (int j=last+1; j<=tot; j++) add(dis[j],1); 60 } 61 ans+=query(K-1); 62 while (tot) add(dis[tot--],-1); 63 for (int v,i=head[u]; i; i=e[i].next) 64 if (!vis[v=e[i].to] && i!=ban1 && i!=ban2) work(v); 65 } 66 67 void find_cir(int u, int f){ 68 vis[u]=1; if (len) return;//printf(" %d\n", u); 69 for (int i=head[u],v; i; i=e[i].next){ 70 v=e[i].to; 71 if (v==f || len) continue; 72 fa[v]=u;// printf("now %d\n", u); 73 if (vis[v]){ 74 ban1=i; ban2=i^1; 75 for (int x=fa[v]; x!=v; x=fa[x]) cir[++len]=x; cir[++len]=v; 76 return; 77 } 78 find_cir(v,u); 79 } 80 } 81 82 void cut(){ 83 for (int i=1; i<=n; i++) vis[i]=0; 84 work(1);// printf(" %lld\n", ans); 85 for (int i=0; i<=n; i++) p[i]=0LL,vis[i]=0; 86 for (int i=1; i<=len; i++) vis[cir[i]]=1; 87 for (int i=1; i<=len; i++){ 88 int u=cir[i]; tot=0; 89 getdis(u,0,0); //printf(" %d\n", tot); 90 for (int j=1; j<=tot; j++) ans+=query(K-dis[j]-(len-i+1));//, printf("%lld\n", ans); 91 while (tot) add(dis[tot--]+i,1); 92 } 93 } 94 95 int main(){ 96 scanf("%d%d%d", &n, &m, &K); tot=1; 97 for (int i=1,u,v; i<=m; i++){ 98 scanf("%d%d", &u, &v); 99 insert(u,v); insert(v,u); 100 } 101 if (m==n-1) work(1); 102 else{ 103 find_cir(1,0); 104 cut(); 105 } 106 printf("%lld\n", ans); 107 return 0; 108 }
以上是关于bzoj3648: 寝室管理(环套树+点分治)的主要内容,如果未能解决你的问题,请参考以下文章