1. 程式人生 > >BZOJ 4675(點分治)

BZOJ 4675(點分治)

include ref scan num != 點分治 分析 強制 n-k

題面

傳送門

分析

由於期望的線性性,我們可以分別計算每個點對對答案的貢獻

有三個人取數字,分開對每個人考慮

設每個人分別取了k個數,則一共有\(C_n^k\)種組合,選到每種組合的概率為\(\frac{1}{C_n^k}\)

對於一個幸運點對,包含它的組合有\(C_{n-2}^{k-2}\)種(k個點中有2個點是該點對,再從剩下的n-2個點中選k-2個點,每種的貢獻均為1)

所以每一個點對的貢獻是

\[\frac{C_{n-2}^{k-2}}{C_n^k}=\frac{\frac{(n-2)!}{(n-k)!\times (k-2)!}}{\frac{n!}{(n-k)! \times k !}}=\frac{(n-2)! \times k !}{n! \times (k-2)!}=\frac{k(k-1)}{n(n-1)}\]

因此總答案為\(a \times \frac{k(k-1)}{n(n-1)}\),其中a為幸運點對的數量

所以只要求出幸運點對數量即可

對於每一個幸運數num[i],我們進行一次點分治,求出長度為num[i]的路徑數(直接套點分治板子,先求長度>=num[i]的路徑數,再減去長度>num[i]的路徑數量),並累計進答案

註意最後n*(n-1)要用double,否則會爆int

代碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 50005 
using namespace std;


int n,k;
struct edge{
    int from;
    int to;
    int next;
}E[maxn<<1];
int ecnt=1;
int head[maxn];
inline void add_edge(int u,int v){
    ecnt++;
    E[ecnt].from=u;
    E[ecnt].to=v;
    E[ecnt].next=head[u];
    head[u]=ecnt;
}

int root=0,sum;
int f[maxn];
int sz[maxn];
int vis[maxn];
void get_root(int x,int fa){
    sz[x]=1;
    f[x]=0;
    for(int i=head[x];i;i=E[i].next){
        int y=E[i].to;
        if(y!=fa&&!vis[y]){
            get_root(y,x);
            sz[x]+=sz[y];
            f[x]=max(f[x],sz[y]);
        } 
    }
    f[x]=max(f[x],sum-f[x]);
    if(f[x]<f[root]) root=x; 
}

int cnt=0;
int deep[maxn];
int res[maxn];
void get_deep(int x,int fa){
    res[++cnt]=deep[x];
    for(int i=head[x];i;i=E[i].next){
        int y=E[i].to;
        if(y!=fa&&!vis[y]){
            deep[y]=deep[x]+1;
            get_deep(y,x);
        }
    }
}

int calc(int x,int d0){
    deep[x]=d0;
    cnt=0;
    get_deep(x,0);
    sort(res+1,res+1+cnt);
    int l=1,r=cnt;
    int ans1=0;
    while(l<r){
        if(res[l]+res[r]<=k){
            ans1+=(r-l);
            l++;
        }else r--;
    }
    
    l=1,r=cnt;
    int ans2=0;
    while(l<r){
        if(res[l]+res[r]<k){
            ans2+=(r-l);
            l++;
        }else r--;
    }
    return ans1-ans2;
}

int ans=0;
void solve(int x){
    vis[x]=1;
    ans+=calc(x,0);
    for(int i=head[x];i;i=E[i].next){
        int y=E[i].to;
        if(!vis[y]){
            ans-=calc(y,1);
            root=0;
            sum=sz[y];
            get_root(y,0);
            solve(root);
        }
    }
}

void divide_ini(){
    memset(deep,0,sizeof(deep));
    memset(f,0,sizeof(f));
    memset(sz,0,sizeof(sz));
    memset(vis,0,sizeof(vis));
    root=0;
    sum=n;
    f[0]=n;
    get_root(1,0);
}

int m;
int num[maxn];
int main(){
    int u,v;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&num[i]);
    }
    for(int i=1;i<n;i++){
        scanf("%d %d",&u,&v);
        add_edge(u,v);
        add_edge(v,u);
    }
    for(int i=1;i<=m;i++){
        k=num[i];
        divide_ini();
        solve(root); 
    }
    double k1,k2,k3;
    if(n%3==0) k1=k2=k3=n/3;
    else if(n%3==1){
        k1=n/3+1;
        k2=n/3;
        k3=n/3;
    }else{
        k1=n/3+1;
        k2=n/3+1;
        k3=n/3;
    }
    printf("%.2lf\n",ans*k1*(k1-1)/((double)n*(n-1)));//強制轉成double,防止溢出
    printf("%.2lf\n",ans*k2*(k2-1)/((double)n*(n-1)));
    printf("%.2lf\n",ans*k3*(k3-1)/((double)n*(n-1)));
}

BZOJ 4675(點分治)