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: 寝室管理(环套树+点分治)的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ 3648: 寝室管理( 点分治 + 树状数组 )

BZOJ3648寝室管理 树分治

BZOJ 1791 岛屿(环套树+单调队列DP)

BZOJ 1040 骑士(环套树DP)

BZOJ 1040: [ZJOI2008]骑士 [DP 环套树]

BZOJ1040: [ZJOI2008]骑士 环套树DP