BZOJ 2152: 聪聪可可(点分治/树形DP)

题目在这里


题解

这题有两种作法,一开始我想的是点分治,类似于POJ1741那题(点这里),我们按同样的方法搜索 dep 并记录,不过这次记录的是 dep%3=012 的数量,记作 t[i] 。那么根据乘法原理, ans1=t[1]t[2]2+t[0]t[0] ,然后再算出儿子的 ans2 ,并减去即可。求出满足要求的有序点对数后,概率即可求。由于每一层里不用排序,只用搜一遍,这种方法的时间复杂度是 O(nlogn) 的。

然而对于这样的题,我们只关心 dep%3 的数量,故可以用树形dp。记 f[x][i] 为以 x 为根的子树中到 x 的深度 mod 3 i 的节点数。然后就是从儿子到根的简单的转移,讨论连接的这条边 l%3 i=0 对应要加的是 (3l%3)%3 ,其他的类推。在转移之前先递归得到儿子的状态,并计算出当前对答案的贡献,有点类似于treap+启发式合并,都是根和一些子树看作一个整体,并与当前的子树看作两部分,计算之间的答案,这里的路径都是保证过当前根的,不会算重算漏。由于 dep 的值只有 0,1,2 这个性质,所以直接记掉所有状态,用树形DP就可以 O(n) 完美解决。


Code(点分治)

#include 
#include 
#include 
#include 
#include 
#include 
#define N 20005
#define mod 5

using namespace std;

int n, cur = -1, head_p[N], f[N], sum, t[mod], dep[N], siz[N], ans, root;
bool vis[N];
struct Adj{int next, obj, len;} Edg[N<<1];

void Insert(int a, int b, int c){
    Edg[++cur].next = head_p[a];
    Edg[cur].obj = b;
    Edg[cur].len = c;
    head_p[a] = cur;
}

void Getroot(int x, int fa){
    siz[x] = 1;  f[x] = 0; 
    for(int i = head_p[x]; ~ i; i = Edg[i].next){
        int v = Edg[i].obj;
        if(v == fa || vis[v])  continue;
        Getroot(v, x);
        siz[x] += siz[v];
        f[x] = max(f[x], siz[v]);
    }
    f[x] = max(f[x], sum - siz[x]);
    if(f[x] < f[root])  root = x;
}

void Getdeep(int x, int fa){
    t[dep[x]] ++;
    for(int i = head_p[x]; ~ i; i = Edg[i].next){
        int v = Edg[i].obj, l = Edg[i].len;
        if(v == fa || vis[v])  continue;
        dep[v] = (dep[x] + l) % 3;
        Getdeep(v, x);
    }
}

int Calc(int x, int v){
    t[0] = t[1] = t[2] = 0;
    dep[x] = v % 3;
    Getdeep(x, 0);
    return t[0] * t[0] + t[1] * t[2] * 2;
}

void Solve(int x){
    ans += Calc(x, 0);
    vis[x] = true;
    for(int i = head_p[x]; ~ i; i = Edg[i].next){
        int v = Edg[i].obj, l = Edg[i].len;
        if(vis[v])  continue;
        ans -= Calc(v, l);
        sum = siz[v];
        root = 0;
        Getroot(v, 0);
        Solve(root);
    }
}

int Gcd(int a, int b){
    if(!b)  return a;
    return Gcd(b, a % b);
}

int main(){

    freopen("bzoj2512.in", "r", stdin);
    freopen("bzoj2512.out", "w", stdout);

    scanf("%d", &n);

    for(int i = 1; i <= n; i++)  head_p[i] = -1;

    int a, b, c;
    for(int i = 1; i < n; i++){
        scanf("%d%d%d", &a, &b, &c);
        Insert(a, b, c);
        Insert(b, a, c);
    }

    f[0] = sum = n;
    root = 0;

    Getroot(1, 0);
    Solve(root);

    int gg = Gcd(ans, n * n);

    printf("%d/%d\n", ans / gg, n * n / gg);    

    return 0;
} 

Code(树形dp)

#include 
#include 
#include 
#include 
#include 
#include 
#define N 20010
using namespace std;

int n, cur = -1, head_p[N], f[N][3], ans;
struct Adj{int next, obj, len;} Edg[N<<1];

void Insert(int a, int b, int c){
    Edg[++cur].next = head_p[a];
    Edg[cur].obj = b;
    Edg[cur].len = c;
    head_p[a] = cur;
}

void Dp(int x, int fa){
    ans ++;
    f[x][0] = 1;
    for(int i = head_p[x]; ~ i; i = Edg[i].next){
        int v = Edg[i].obj, l = Edg[i].len % 3;
        if(v == fa)  continue;
        Dp(v, x);
        ans += (f[x][0] * f[v][(3-l)%3] + f[x][1] * f[v][(2-l)%3] + f[x][2] * f[v][(4-l)%3]) << 1;
        f[x][0] += f[v][(3-l)%3];
        f[x][1] += f[v][(4-l)%3];
        f[x][2] += f[v][(2-l)%3];
    }
}

int Gcd(int a, int b){
    if(!b)  return a;
    return Gcd(b, a % b);
}

int main(){

    freopen("bzoj2512.in", "r", stdin);
    freopen("bzoj2512.out", "w", stdout);

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)  head_p[i] = -1;

    int a, b, c;
    for(int i = 1; i < n; i++){
        scanf("%d%d%d", &a, &b, &c);
        Insert(a, b, c);
        Insert(b, a, c);
    }

    Dp(1, 0);

    int gg = Gcd(ans, n * n);

    printf("%d/%d\n", ans / gg, n * n / gg);

    return 0;
} 

BZOJ 2152: 聪聪可可(点分治/树形DP)_第1张图片

春天 马上就要来了
让我与你相遇的春天 就要来了
再也没有你的春天 就要来了

——《四月是你的谎言》

你可能感兴趣的:(DP,&,记忆化搜索,BZOJ,点分治)