team2012-E1-mysol-0015

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

{{{
/* 解题报告 //

给出一棵树,给每个结点染色,要求相邻两结点不能同色,共有 C 种颜色可选
并且有一些询问,每次询问若在 X 和 Y 之间加一条边,则染色方案数减少多少

首先,对于原始的树,方案数必定是 C * (C - 1) ^ (N - 1)
选择任意一点为根,它自身可以染 C 种颜色,确定了它的颜色之后
其余结点都不能和父亲同色,从而得到上面的公式

树上加一条边会形成乌贼型图,比如这样:
}}}
{{{
#!html
<div style="font-family: 'MS Pゴシック','モナーフォント','IPAモナーフォント','IPA モナ','M+IPAモナ','Mona','mona-gothic-jisx0208.1990-0','sans-serif'; line-height: normal; white-space: pre-wrap; word-wrap: break-word;">                  ________<br>               .   ´          __`丶_<br>                 /         ,.  ´: : : : : : : : :`   、<br>           /  |     /: : : : : : : : : :、: : : : : : : \<br>              /   |   ./ : : : : ト、: : : : : ∧: :、 : : : :!⌒<br>          /    :|   /: : : :∧ ,:|--\ : / ‐∨、\ : |<br>.         /       '  /: : : : :|/、|   `     |: V<br>            ` =ニニニV : : : : : |           ,x=、 Vハ<br>                 { |: |: |: : :|  ,x==、    〃    V|<br>              '. j : ム:|∨:| 〃    ____ /// }|<br>                    ∨: :{ r|: : :l/// r ´    \}    ハ、<br>                /: : : ヽ|: : :|  |       ノ /: : : :\<br>              _/ : : /: :/ : : ト ._丶 __ . イ: :{ \:_:_: :ヽ<br>          / : : : : :,.一': :_/: :x'⌒\l|`ヽ、: : ト、: ヽ    |: :|<br>     ┌―一': : :/ ̄/: : : :/: : / }}     } \ | }: : }   |: :└┐<br>     |: : : : : : :/__,/: : /| : / ,.イ{       !\ \ ヽ: ヽ    ̄]: :|<br>    /: : : : | ̄ |: : :_:_;∧__|f二yイ |: 〉       V( ー' )、〉 : 〉 く : : :\<br>    \ : : : : &gt; __」: : | 〉: : : (_/:〈 /         ∨下-:' : :/    \/<br>.       \/ \ : : :\: : : : ∧: /、           ,ハ |: : : : :|<br>              \/  ̄ ̄ /〈\O\ ______ /O 〉 ̄ ̄<br>                     ̄ \\ __O__O//<br>                    />ー-----r' |<br>                   〈/         し'<br><br></div>
}}}
{{{
哦好像图片用错了,总之是一个环加一些根结点在环上的树,意思理解了就行

于是先预处理出一个数组,表示长度为 x 的环的染色方案数
公式是 F[i] = F[i - 1] * (C - 2) + F[i - 2] * (C - 1) (i >= 4)
前三项略,至于整个公式怎么得出的,分析起来比较麻烦,建议自己推去吧
环上的点染色方案数确定后,剩下 N - x 个点的方案数就是 (C - 1) ^ (N - x)

如何在添加一条边时计算出这个环的大小?LCA(最近公共祖先)
关于 LCA 的做法有很多,不会的话可以去网上搜一下
我的做法是预处理,得到每个结点的父亲,父亲 ^ 2,父亲 ^ 4 ...
同时记录每个结点的高度,在做 LCA 时,分为这么几步:
1. 先将查询的结点 X 和 Y 化归到同一高度
2. 若 X == Y 则说明已经找到了 LCA,返回 X
3. 从大往小检查:如果将它们提升 2 ^ i 的高度,是否是同一个结点
   若是同一个结点,则什么也不做
   若非同一个结点,则将它们提升 2 ^ i 的高度,到达 X' 和 Y'
4. 最后检查完毕时,他们的直接父亲必定是同一个结点,返回 fa[X]

我觉得这种做法比 tarjan 离线做法简单易懂,如果还有不理解的可以看看代码

// By 猛犸也钻地 @ 2012.07.22 */
}}}

{{{
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long LL;

int fa[50001][16],lv[50001],ans[50001];
vector<int> e[50001];

void make_tree(int x, int src){
    fa[x][0]=src;
    lv[x]=lv[src]+1;
    for(int i=1;i<16;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
    for(size_t i=0;i<e[x].size();i++)
        if(e[x][i]!=src) make_tree(e[x][i],x);
}

int gao(int x, int y){
    int c=lv[x]-lv[y];
    if(c>0) for(int i=0;i<16;i++) if(+c&1<<i) x=fa[x][i];
    if(c<0) for(int i=0;i<16;i++) if(-c&1<<i) y=fa[y][i];
    c=abs(c);
    if(x==y) return c;
    for(int i=15;~i;i--) if(fa[x][i]!=fa[y][i]){
        x=fa[x][i];
        y=fa[y][i];
        c+=2<<i;
    }
    return c+=2;
}

int main(){
    int n,m,c,p;
    while(scanf("%d%d%d%d",&n,&m,&c,&p)==4){
        int x,y,w=1;
        ans[1]=c;
        ans[2]=c*LL(c-1)%p;
        ans[3]=c*LL(c-1)%p*(c-2)%p;
        for(int i=4;i<=n;i++)
            ans[i]=(LL(c-2)*ans[i-1]+LL(c-1)*ans[i-2])%p;
        for(int i=n;i>=1;i--){
            ans[i]=ans[i]*LL(w)%p;
            w=w*LL(c-1)%p;
        }
        for(int i=1;i!=n;i++){
            scanf("%d%d",&x,&y);
            e[x].push_back(y);
            e[y].push_back(x);
        }
        make_tree(1,1);
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            printf("%d\n",(ans[1]+p-ans[gao(x,y)+1])%p);
        }
        for(int i=1;i<=n;i++) e[i].clear();
    }
}
}}}
/* 解题报告 //
给出一棵树,给每个结点染色,要求相邻两结点不能同色,共有 C 种颜色可选
并且有一些询问,每次询问若在 X 和 Y 之间加一条边,则染色方案数减少多少
首先,对于原始的树,方案数必定是 C * (C - 1) ^ (N - 1)
选择任意一点为根,它自身可以染 C 种颜色,确定了它的颜色之后
其余结点都不能和父亲同色,从而得到上面的公式
树上加一条边会形成乌贼型图,比如这样:
                  ________
               .   ´          __`丶_
                 /         ,.  ´: : : : : : : : :`   、
           /  |     /: : : : : : : : : :、: : : : : : : \
              /   |   ./ : : : : ト、: : : : : ∧: :、 : : : :!⌒
          /    :|   /: : : :∧ ,:|--\ : / ‐∨、\ : |
.         /       '  /: : : : :|/、|   `     |: V
            ` =ニニニV : : : : : |           ,x=、 Vハ
                 { |: |: |: : :|  ,x==、    〃    V|
              '. j : ム:|∨:| 〃    ____ /// }|
                    ∨: :{ r|: : :l/// r ´    \}    ハ、
                /: : : ヽ|: : :|  |       ノ /: : : :\
              _/ : : /: :/ : : ト ._丶 __ . イ: :{ \:_:_: :ヽ
          / : : : : :,.一': :_/: :x'⌒\l|`ヽ、: : ト、: ヽ    |: :|
     ┌―一': : :/ ̄/: : : :/: : / }}     } \ | }: : }   |: :└┐
     |: : : : : : :/__,/: : /| : / ,.イ{       !\ \ ヽ: ヽ    ̄]: :|
    /: : : : | ̄ |: : :_:_;∧__|f二yイ |: 〉       V( ー' )、〉 : 〉 く : : :\
    \ : : : : > __」: : | 〉: : : (_/:〈 /         ∨下-:' : :/    \/
.       \/ \ : : :\: : : : ∧: /、           ,ハ |: : : : :|
              \/  ̄ ̄ /〈\O\ ______ /O 〉 ̄ ̄
                     ̄ \\ __O__O//
                    />ー-----r' |
                   〈/         し'

哦好像图片用错了,总之是一个环加一些根结点在环上的树,意思理解了就行
于是先预处理出一个数组,表示长度为 x 的环的染色方案数
公式是 F[i] = F[i - 1] * (C - 2) + F[i - 2] * (C - 1) (i >= 4)
前三项略,至于整个公式怎么得出的,分析起来比较麻烦,建议自己推去吧
环上的点染色方案数确定后,剩下 N - x 个点的方案数就是 (C - 1) ^ (N - x)
如何在添加一条边时计算出这个环的大小?LCA(最近公共祖先)
关于 LCA 的做法有很多,不会的话可以去网上搜一下
我的做法是预处理,得到每个结点的父亲,父亲 ^ 2,父亲 ^ 4 ...
同时记录每个结点的高度,在做 LCA 时,分为这么几步:
1. 先将查询的结点 X 和 Y 化归到同一高度
2. 若 X == Y 则说明已经找到了 LCA,返回 X
3. 从大往小检查:如果将它们提升 2 ^ i 的高度,是否是同一个结点
   若是同一个结点,则什么也不做
   若非同一个结点,则将它们提升 2 ^ i 的高度,到达 X' 和 Y'
4. 最后检查完毕时,他们的直接父亲必定是同一个结点,返回 fa[X]
我觉得这种做法比 tarjan 离线做法简单易懂,如果还有不理解的可以看看代码
// By 猛犸也钻地 @ 2012.07.22 */
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long LL;
int fa[50001][16],lv[50001],ans[50001];
vector<int> e[50001];
void make_tree(int x, int src){
    fa[x][0]=src;
    lv[x]=lv[src]+1;
    for(int i=1;i<16;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
    for(size_t i=0;i<e[x].size();i++)
        if(e[x][i]!=src) make_tree(e[x][i],x);
}
int gao(int x, int y){
    int c=lv[x]-lv[y];
    if(c>0) for(int i=0;i<16;i++) if(+c&1<<i) x=fa[x][i];
    if(c<0) for(int i=0;i<16;i++) if(-c&1<<i) y=fa[y][i];
    c=abs(c);
    if(x==y) return c;
    for(int i=15;~i;i--) if(fa[x][i]!=fa[y][i]){
        x=fa[x][i];
        y=fa[y][i];
        c+=2<<i;
    }
    return c+=2;
}
int main(){
    int n,m,c,p;
    while(scanf("%d%d%d%d",&n,&m,&c,&p)==4){
        int x,y,w=1;
        ans[1]=c;
        ans[2]=c*LL(c-1)%p;
        ans[3]=c*LL(c-1)%p*(c-2)%p;
        for(int i=4;i<=n;i++)
            ans[i]=(LL(c-2)*ans[i-1]+LL(c-1)*ans[i-2])%p;
        for(int i=n;i>=1;i--){
            ans[i]=ans[i]*LL(w)%p;
            w=w*LL(c-1)%p;
        }
        for(int i=1;i!=n;i++){
            scanf("%d%d",&x,&y);
            e[x].push_back(y);
            e[y].push_back(x);
        }
        make_tree(1,1);
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            printf("%d\n",(ans[1]+p-ans[gao(x,y)+1])%p);
        }
        for(int i=1;i<=n;i++) e[i].clear();
    }
}