点分治QAQ

发布于 2018-05-31  114 次阅读


点分治
介绍:用于解决树上的路径一类问题的算法。比如求树上所有的两点路径(u,v)。

题目:
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

Input
输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。

Output
以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。

Sample Input
5
1 2 1
1 3 2
1 4 1
2 5 3
Sample Output
13/25

【样例说明】

13组点对分别是(1,1) (2,2) (2,3) (2,5) (3,2) (3,3) (3,4) (3,5) (4,3) (4,4) (5,2) (5,3) (5,5)。

【数据规模】

对于100%的数据,n<=20000。

树上的路径问题可以通过点分治来解决。但是关键问题是“3的倍数”如何满足?
假设两点的距离记为两点到根的深度之和,那么一棵树内的合法点对可以表示为:(所有深度%3=0的点)^2+所有深度%3=1的点所有深度%3=2的点2。正确性显然。
那么每一次遍历只需要统计每个点深度%3的值就可以了,每一次的结果都可以O(1)O(1)计算。
总时间复杂度为O(nlogn)O(nlogn)。

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <deque>
#include <queue>
#define ri(n) scanf("%d",&n)
#define oi(n) printf("%d\n",n)
#define rl(n) scanf("%lld",&n)
#define ol(n) printf("%lld\n",n)
#define rep(i,l,r) for(i=l;i<=r;i++)
#define rep1(i,l,r) for(i=l;i<r;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int epg=10-8;
const int maxn=1e6+20;
int tot,ans,sum,root;
int head[maxn],vis[maxn];
int sz[maxn],big[maxn];
int d[maxn],t[5];
struct node
{
    int v,next,w;
}e[maxn];
int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}
void init()
{
    tot=ans=0;
    memset(head,-1,sizeof(head));
    memset(sz,0,sizeof(sz));
    memset(big,0,sizeof(big));
    memset(vis,0,sizeof(vis));
}
void add(int u,int v,int w)
{
    e[++tot].v=v;
    e[tot].next=head[u];
    e[tot].w=w;
    head[u]=tot++;
}
void getroot(int x,int fa)
{
    sz[x]=1;big[x]=0;
    for(int i=head[x];~i;i=e[i].next)
    {
        int v=e[i].v;
        if(vis[v]==0&&v!=fa)
        {
            getroot(v,x);
            sz[x]+=sz[v];
            big[x]=max(big[x],sz[v]);
        }
    }
    big[x]=max(big[x],sum-sz[x]);
    if(big[x]<big[root])
        root=x;
}
void getdeep(int x,int fa)
{
    t[d[x]]++;
    for(int i=head[x];~i;i=e[i].next)
    {
        int v=e[i].v;
        if(v!=fa&&vis[v]==0)
        {
            d[v]=(d[x]+e[i].w)%3;
            getdeep(v,x);
        }
    }
}
int cal(int x,int w)
{
    d[x]=w%3;
    t[0]=t[1]=t[2]=0;
    getdeep(x,0);
    return t[0]*t[0]+2*t[1]*t[2];
}
void dfs(int x)
{
    ans+=cal(x,0);
    vis[x]=1;
    for(int i=head[x];~i;i=e[i].next)
    {
        int v=e[i].v;
        if(vis[v]==0)
        {
            ans-=cal(v,e[i].w);
            sum=sz[v];
            root=0;
            getroot(v,0);
            dfs(root);
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    init();
    int u,v,w;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        w%=3;
        add(u,v,w);
        add(v,u,w);
    }
    sum=n;
    big[0]=inf;
    root=0;
    getroot(1,0);
    dfs(root);
    int t=gcd(ans,n*n);
    if(ans==0)
        printf("%d/%d",0,0);
    else
        printf("%d/%d",ans/t,n*n/t);
    return 0;
}

题目2:poj-1741

题意:一棵有n个节点的树,每条边有个权值代表相邻2个点的距离,要求求出所有距离不超过k的点对(u,v)

代码:

#include <cstdio>  
#include <cstring>  
#include <iostream>  
#include <vector>  
#include <algorithm>  
using namespace std;  
const int MX = 1e4 + 5;  

struct Edge {  
    int v, w, nxt;  
} E[MX * 2];  

int n, k, root, Max, ans;  
vector <int> dis;  
int sz[MX], maxv[MX], head[MX], tot;  
bool vis[MX];  

void init() {  
    memset(vis, false, sizeof(vis));  
    memset(head, -1, sizeof(head));  
    tot = 0;  
}  
void add(int u, int v, int w) {  
    E[tot].v = v;  
    E[tot].w = w;  
    E[tot].nxt = head[u];  
    head[u] = tot++;  
}  


void dfs_size(int u, int fa) {  
    sz[u] = 1; maxv[u] = 0;  
    for (int i = head[u]; ~i; i = E[i].nxt) {  
        int v = E[i].v;  
        if (vis[v] || v == fa) continue;  
        dfs_size(v, u);  
        sz[u] += sz[v];  
        maxv[u] = max(maxv[u], sz[v]);  
    }  
}  

void dfs_root(int r, int u, int pre) {  // 找出以u为根的子树的重心  
    maxv[u] = max(maxv[u], sz[r] - sz[u]);  
    if (Max > maxv[u]) {  
        Max = maxv[u];  
        root = u;  
    }  
    for (int i = head[u]; ~i; i = E[i].nxt) {  
        int v = E[i].v;  
        if (v == pre || vis[v]) continue;  
        dfs_root(r, v, u);  
    }  
}  

void dfs_dis(int u, int fa, int dir) {  
    dis.push_back(dir);  
    for (int i = head[u]; ~i; i = E[i].nxt) {  
        int v = E[i].v, w = E[i].w;  
        if (vis[v] || v == fa) continue;  
        dfs_dis(v, u, dir + w);  
    }  
}  

int cal(int rt, int d) {  
    dis.clear();  
    dfs_dis(rt, -1, d);  
    sort(dis.begin(), dis.end());  
    int i = 0, j = dis.size() - 1, ret = 0;  
    while (i < j) {  
        while (dis[i] + dis[j] > k && i < j) j--;  
        ret += j - i;  
        i++;  
    }  
    return ret;  
}  

void DFS(int u) {  
    Max = n;  
    dfs_size(u, -1);  
    dfs_root(u, u, -1);  
    int rt = root;  
    ans += cal(rt, 0);  
    vis[rt] = 1;  
    for (int i = head[rt]; ~i; i = E[i].nxt) {  
        int v = E[i].v, w = E[i].w;  
        if (vis[v]) continue;  
        ans -= cal(v, w);  
        DFS(v);  
    }  
}  

int main() {  
    //freopen("in.txt","r",stdin);  
    while (scanf("%d%d", &n, &k), n || k) {  
        init();  
        for (int i = 1; i < n; i++) {  
            int u, v, w;  
            scanf("%d%d%d", &u, &v, &w);  
            add(u, v, w); add(v, u, w);  
        }  
        ans = 0;  
        DFS(1);  
        printf("%d\n", ans);  
    }  
    return 0;  
}  

Comments


愿我如星君如月,夜夜流光相皎洁。