team2012-F3-sol-0015

从 Trac 迁移的文章

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

原文章内容如下:

{{{
题意:给定一个树,每个节点可以染成c种颜色,但是相邻颜色不能相同,若从这树上添加一条边,染色方案减少多少?
解法:在添加边之前,根可以染c种颜色,其余每个节点保证与父亲不同,染色方案总数为c*[(c-1)^(n-1)]
若添加一条边(a,b),形成一个环a->b->lca(a,b)->a,在这个环上染色使得相邻不相同的方案数设为Fi,其中i是环上的节点数
则从环上第一个节点开始染色,染到第i-1个节点时,若i-1的颜色与1相同,则第i个有c-1种染法
此时将1和i-1连接起来作为一个点,形成一个长为i-2的环,在这环上要保证相邻异色,有F(i-2)种方案
所以若i-1的颜色与1相同,有F(i-2)*(c-1)种
同理,若i-1的颜色与1不相同,则第i个有c-2种染法,将1和i-1连接起来形成一个长为i-1的环,有F(i-1)种方案
所以若i-1的颜色与1不相同,有F(i-1)*(c-2)种
所以Fi = F(i-2)*(c-1) + F(i-1)*(c-2)
环的长度为 depth[a] + depth[b] - 2*depth[lca(a,b)] + 1
在环外的“枝”上逐步染色,其余的点均要保证不与前一个同色,还要乘上(c-1)^(n-circle_length)种
将总数减去如上求得的方案数即为答案
}}}
{{{
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <utility>
#include <cstring>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int MAXN = 50005;
bool vis[MAXN];
vector<PII> query[MAXN];
vector<int> conn[MAXN];
int fa[MAXN], depth[MAXN];
LL f[MAXN], g[MAXN], tot;
LL ans[2*MAXN], MOD;
int n, m, c;

LL cal(int cir)
{
    LL ret = f[cir] * g[n-cir];
    ret %= MOD;
    ret = tot-ret;
    if(ret < 0)
        ret += MOD;
    return ret;
}

int find(int x)
{
    if(fa[x] == x)  return x;
    return fa[x] = find(fa[x]);
}

void dfs(int x)
{
    vis[x] = 1;
    for(int i=0;i<query[x].size();i++)
    {
        int v = query[x][i].first;
        if(vis[v])
        {
            int lca = find(v),
                cir = depth[x] + depth[v] - 2*depth[lca] + 1;
            ans[query[x][i].second] = cal(cir);
        }
    }
    for(int i=0;i<conn[x].size();i++)
    {
        int v = conn[x][i];
        if(!vis[v])
        {
            depth[v] = depth[x] + 1;
            dfs(v);
            fa[v] = x;
        }
    }
}

void gao()
{
    f[1] = 0;
    f[2] = 1LL*c*(c-1) % MOD;
    for(int i=3;i<=n;i++)
    {
        f[i] = f[i-1]*(c-2) + f[i-2]*(c-1);
        f[i] %= MOD;
    }
    g[0] = 1;
    for(int i=1;i<=n;i++)
    {
        g[i] = g[i-1]*(c-1);
        g[i] %= MOD;
    }
    tot = (c*g[n-1]) % MOD;

    for(int i=1;i<=n;i++)
        fa[i] = i;
    memset(vis, 0, sizeof(vis));
    depth[1] = 0;
    dfs(1);
    for(int i=1;i<=m;i++)
        printf("%lld\n", ans[i]);
}

int main()
{
    while(~scanf("%d %d %d %lld", &n, &m, &c, &MOD))
    {
        for(int i=1;i<=n;i++)
        {
            conn[i].clear();
            query[i].clear();
        }
        for(int i=1;i<n;i++)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            conn[a].push_back(b);
            conn[b].push_back(a);
        }
        for(int i=1;i<=m;i++)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            query[a].push_back(make_pair(b, i));
            query[b].push_back(make_pair(a, i));
        }
        gao();
    }
    return 0;
}
}}}
题意:给定一个树,每个节点可以染成c种颜色,但是相邻颜色不能相同,若从这树上添加一条边,染色方案减少多少?
解法:在添加边之前,根可以染c种颜色,其余每个节点保证与父亲不同,染色方案总数为c*[(c-1)^(n-1)]
若添加一条边(a,b),形成一个环a->b->lca(a,b)->a,在这个环上染色使得相邻不相同的方案数设为Fi,其中i是环上的节点数
则从环上第一个节点开始染色,染到第i-1个节点时,若i-1的颜色与1相同,则第i个有c-1种染法
此时将1和i-1连接起来作为一个点,形成一个长为i-2的环,在这环上要保证相邻异色,有F(i-2)种方案
所以若i-1的颜色与1相同,有F(i-2)*(c-1)种
同理,若i-1的颜色与1不相同,则第i个有c-2种染法,将1和i-1连接起来形成一个长为i-1的环,有F(i-1)种方案
所以若i-1的颜色与1不相同,有F(i-1)*(c-2)种
所以Fi = F(i-2)*(c-1) + F(i-1)*(c-2)
环的长度为 depth[a] + depth[b] - 2*depth[lca(a,b)] + 1
在环外的“枝”上逐步染色,其余的点均要保证不与前一个同色,还要乘上(c-1)^(n-circle_length)种
将总数减去如上求得的方案数即为答案
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <utility>
#include <cstring>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MAXN = 50005;
bool vis[MAXN];
vector<PII> query[MAXN];
vector<int> conn[MAXN];
int fa[MAXN], depth[MAXN];
LL f[MAXN], g[MAXN], tot;
LL ans[2*MAXN], MOD;
int n, m, c;
LL cal(int cir)
{
    LL ret = f[cir] * g[n-cir];
    ret %= MOD;
    ret = tot-ret;
    if(ret < 0)
        ret += MOD;
    return ret;
}
int find(int x)
{
    if(fa[x] == x)  return x;
    return fa[x] = find(fa[x]);
}
void dfs(int x)
{
    vis[x] = 1;
    for(int i=0;i<query[x].size();i++)
    {
        int v = query[x][i].first;
        if(vis[v])
        {
            int lca = find(v),
                cir = depth[x] + depth[v] - 2*depth[lca] + 1;
            ans[query[x][i].second] = cal(cir);
        }
    }
    for(int i=0;i<conn[x].size();i++)
    {
        int v = conn[x][i];
        if(!vis[v])
        {
            depth[v] = depth[x] + 1;
            dfs(v);
            fa[v] = x;
        }
    }
}
void gao()
{
    f[1] = 0;
    f[2] = 1LL*c*(c-1) % MOD;
    for(int i=3;i<=n;i++)
    {
        f[i] = f[i-1]*(c-2) + f[i-2]*(c-1);
        f[i] %= MOD;
    }
    g[0] = 1;
    for(int i=1;i<=n;i++)
    {
        g[i] = g[i-1]*(c-1);
        g[i] %= MOD;
    }
    tot = (c*g[n-1]) % MOD;
    for(int i=1;i<=n;i++)
        fa[i] = i;
    memset(vis, 0, sizeof(vis));
    depth[1] = 0;
    dfs(1);
    for(int i=1;i<=m;i++)
        printf("%lld\n", ans[i]);
}
int main()
{
    while(~scanf("%d %d %d %lld", &n, &m, &c, &MOD))
    {
        for(int i=1;i<=n;i++)
        {
            conn[i].clear();
            query[i].clear();
        }
        for(int i=1;i<n;i++)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            conn[a].push_back(b);
            conn[b].push_back(a);
        }
        for(int i=1;i<=m;i++)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            query[a].push_back(make_pair(b, i));
            query[b].push_back(make_pair(a, i));
        }
        gao();
    }
    return 0;
}