CF 997D Cycles in product

传送门
题目大意

给你大小分别为 n 1 n 2 的两棵树 T 1 T 2 。构造一张新图,该图中每一个点的编号为 ( u , v ) 。如果在 T 1 u 1 u 2 之间有边,那么在该图上对于任意的 v ( u 1 , v ) ( u 2 , v ) 之间有边。同样,如果在 T 2 v 1 v 2 之间有边,那么在该图上对于任意的 u ( u , v 1 ) ( u , v 2 ) 之间有边。问你这个图上长度为 K 的环有多少个。环的定义是从一个点出发,走 k 步回到起点,可以经过重复点和重复边,路径相同,起点不同的环看作是不同的。

n 1 , n 2 4000 k 75 ,答案对 998244353 取模。

思路

什么都不会的感觉……

考虑子问题。如果直接给你一张图,让你求出走 k 步回到原点的方案数可以怎么做?由于可以经过重复点和重复边,因此设 f i , j 表示走 j 步到 i 的方案数,边界条件为 f s , 0 = 1 ,最终答案为 f s , k n 个点的时间复杂度为 O ( n 2 k ) 或者 O ( n 3 log k )

现在我们要求从每个点出发的答案之和,显然我们不能写一个 O ( n 4 k ) 的算法……


显然我们甚至不能把这张新的图建出来,因此我们考虑下这个新图到底是什么意思。我们把它看作一个二维坐标 ( x , y ) ,每次移动要么根据 T 1 改变 x ,要么根据 T 2 改变 y 。这样我们就明白了,原来可以把问题抽象到这两棵树上,看作两棵树上分别有一个棋子,我们每次能够选一个棋子走一步,问操作 k 次后两个棋子都在原位的方案数。

考虑枚举在第一棵树上走了多少步。设 f 1 , i 表示在第一棵树上走了 i 步回到起点的方案数, f 2 , i 表示在第二棵树上走了 i 步回到起点的方案数,那么最终答案为:

i = 0 k f 1 , i f 2 , k i C k i

f 1 , i 等于从第一棵树上的每个结点出发走 i 步回到自己的方案数之和;注意要乘以一个组合数。如果沿用前面的思路,那么不可避免的每个结点出发都要算一遍,时间复杂度为 O ( n 2 k ) ,不足以通过此题。

有没有更好的做法呢?

扫描二维码关注公众号,回复: 1962938 查看本文章

由于不同的路径对于所有不同的起点都有贡献,那我们考虑一下,对于一条路径能不能让所有经过的点都算上贡献。考虑点分治,每次统计经过分治中心的路径对所有点的答案的贡献。注意到,对于一个点来说,一条回路必然呈花瓣状,于是我们求两个东西: f i , j g i , j f i , j 表示从分治中心出发走 j 步停在 j 点的方案数,要求中间不经过分治中心(也就是一个花瓣), g i , j 意义与 f i , j 相同,但是没有限制(也就是任意多个花瓣)。对于分治中心而言,贡献就是 g c , k ,但是对于其它点 v 而言,我们考虑这样两条路径:从分治中心出发,不经过分治中心到达 v ;从分治出发,可以经过分治中心到达 v 。把第二条路径反转,我们就得到一条从 v 出发的回路。我们枚举第一条路径的长度(这条路径必然是一个花瓣的一部分,就不会算重了),然后用 O ( k 2 ) 的时间复杂度做卷积即可。时间复杂度 O ( n k 2 log n )

参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef int INT_PUT;
INT_PUT readIn()
{
    INT_PUT a = 0; bool positive = true;
    char ch = getchar();
    while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
    if (ch == '-') { positive = false; ch = getchar(); }
    while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
    return positive ? -a : a;
}
void printOut(INT_PUT x)
{
    char buffer[20]; int length = 0;
    if (x < 0) putchar('-'); else x = -x;
    do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
    do putchar(buffer[--length]); while (length);
    putchar('\n');
}

const int mod = 998244353;
inline void add(int& a, const int& b)
{
    register int t;
    a = (t = a + b) >= mod ? t - mod : t;
}
const int maxn = 4005;
const int maxk = 80;
int k;
typedef std::vector<std::vector<int> > Graph;
struct Tree
{
    int n;
    Graph G;
    void resize(int size)
    {
        n = size;
        G.resize(size + 1);
    }
    void read()
    {
        for (int i = 2; i <= n; i++)
        {
            int from = readIn();
            int to = readIn();
            G[from].push_back(to);
            G[to].push_back(from);
        }
    }

    bool vis[maxn];
    int size[maxn];
    void DFS1(int node, int parent)
    {
        size[node] = 1;
        for (int i = 0; i < G[node].size(); i++)
        {
            int to = G[node][i];
            if (to == parent || vis[to]) continue;
            DFS1(to, node);
            size[node] += size[to];
        }
    }
    int findRoot(int node, int parent, int s)
    {
        for (int i = 0; i < G[node].size(); i++)
        {
            int to = G[node][i];
            if (to == parent || vis[to])
                continue;
            if (size[to] > (s >> 1))
                return findRoot(to, node, s);
        }
        return node;
    }

    std::vector<std::pair<int, int> > transfer[2];
    std::vector<int> nodes;
    int f[maxn][maxk];
    int g[maxn][maxk];

    void DFS2(int node, int parent)
    {
        nodes.push_back(node);
        std::memset(f[node], 0, sizeof(f[node]));
        std::memset(g[node], 0, sizeof(g[node]));
        for (int i = 0; i < G[node].size(); i++)
        {
            int to = G[node][i];
            if (to == parent || vis[to])
                continue;
            transfer[0].push_back(std::make_pair(node, to));
            transfer[1].push_back(std::make_pair(node, to));
            transfer[1].push_back(std::make_pair(to, node));
            if (node != nodes.front())
                transfer[0].push_back(std::make_pair(to, node));
            DFS2(to, node);
        }
    }

    void solve(int node)
    {
        DFS1(node, 0);
        node = findRoot(node, 0, size[node]);
        vis[node] = true;

        transfer[0].clear();
        transfer[1].clear();
        nodes.clear();
        DFS2(node, 0);
        f[node][0] = g[node][0] = 1;
        for (int i = 1; i <= k; i++)
        {
            for (int j = 0; j < transfer[0].size(); j++)
            {
                const std::pair<int, int>& trans = transfer[0][j];
                add(f[trans.second][i], f[trans.first][i - 1]);
            }
            for (int j = 0; j < transfer[1].size(); j++)
            {
                const std::pair<int, int>& trans = transfer[1][j];
                add(g[trans.second][i], g[trans.first][i - 1]);
            }
        }
        for (int i = 0; i <= k; i++)
            add(ans[i], g[node][i]);
        for (int i = 1; i < nodes.size(); i++)
        {
            int t = nodes[i];
            for (int j = 0; j <= k; j++)
                for (int l = 0; l <= j; l++)
                    ans[j] = (ans[j] + (LL)f[t][l] * g[t][j - l]) % mod;
        }

        for (int i = 0; i < G[node].size(); i++)
        {
            int to = G[node][i];
            if (vis[to]) continue;
            solve(to);
        }
    }

    int ans[maxk];
    void run()
    {
        nodes.reserve(maxn);
        transfer[0].reserve(maxn * 2);
        transfer[1].reserve(maxn * 2);
        solve(1);
    }
    const int& operator[](int x) const { return ans[x]; }
} tree[2];

int C[maxk][maxk];
void init()
{
    C[0][0] = 1;
    for (int i = 1; i <= k; i++)
    {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++)
        {
            register int t;
            C[i][j] = (t = C[i - 1][j - 1] + C[i - 1][j]) >= mod ? t - mod : t;
        }
    }
}

void run()
{
    tree[0].resize(readIn());
    tree[1].resize(readIn());
    k = readIn();
    for (int i = 0; i < 2; i++)
    {
        tree[i].read();
        tree[i].run();
    }
    init();

    int ans = 0;
    for (int i = 0; i <= k; i++)
        add(ans, (LL)C[k][i] * tree[0][i] % mod * tree[1][k - i] % mod);
    printOut(ans);
}

int main()
{
    run();
    return 0;
}
总结

首先要知道如何用动态规划求解一般图走 k 步的回路的方案数,然后要把这道题转化模型,前两步做出来后就能写出一个合法的暴力了。要想出点分治的方法,应该要从“贡献一起算”出发,进而想到从某个点出发引两条路径,把其中一条路径反转,进而想到点分治。

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/80963142
今日推荐