[BZOJ1494][NOI2007]生成树计数(状压dp+矩阵乘法)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=1494

Solution

定义状态:
f [ i ] [ S ] 表示 i 个点的图,编号差 k 的点对之间有连边,现选出一些边,连通第 1 个点到第 i k 个点,第 i k + 1 个点到第 i 个点的连通性为 S ,第 i k + 1 个点到第 i 个点至少存在一点与前 i 1 个点连通,且选出的边无环的方案数。。
注意这里的 S 压缩连通性使用的是最小表示法
对连通块进行标号,从 1 到连通块个数,首次出现位置越靠前的连通块编号越小
会发现有效 S 的个数是贝尔数,即第二类斯特林数的前缀和。 k = 5 时状态数只有 52
如何求 f [ i + 1 ] [ T ] f [ i ] [ S ] 的转移系数呢?????
我们可以对于一个 S 2 k 枚举第 i k + 1 个点到第 i 个点是否与 i + 1 连边,大力计算即可。
但要注意:
(1)如果第 i k + 1 个点在第 i k + 1 到第 i 个点中单独属于一个连通块,那么 i k + 1 i + 1 间必须连边,否则不能保证「连通前 i k + 1 个点」。
(2)不能由 i 连向第 i k + 1 个点到第 i 个点中的同一个连通块,否则会形成环。
发现转移系数不变且为一阶。
因此直接上矩阵快速幂。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const int N = 60, E = 7, ZZQ = 65521;
int k, m, tmp[E], pmt[E], col[N][E], vis[E], pwe[E][E];
ll n;
struct cyx {
    int n, m, a[N][N];
    cyx() {}
    cyx(int _n, int _m) :
        n(_n), m(_m) {memset(a, 0, sizeof(a));}
    friend inline cyx operator * (cyx a, cyx b) {
        int i, j, k;
        cyx res = cyx(a.n, b.m);
        For (i, 1, res.n) For (j, 1, res.m) For (k, 1, a.m)
            res.a[i][j] = (res.a[i][j] +
                1ll * a.a[i][k] * b.a[k][j] % ZZQ) % ZZQ;
        return res;
    }
    friend inline cyx operator ^ (cyx a, ll b) {
        int i;
        cyx res = cyx(a.n, a.m);
        For (i, 1, a.n) res.a[i][i] = 1;
        while (b) {
            if (b & 1) res = res * a;
            a = a * a;
            b >>= 1;
        }
        return res;
    }
} S, A;
void orzcyx(int dep, int cnt) {
    int i;
    if (dep == k + 1) {
        col[++m][0] = 1;
        For (i, 1, k) col[m][i] = tmp[i];
        For (i, 1, k) pmt[i] = 0;
        For (i, 1, k) pmt[tmp[i]]++;
        For (i, 1, k) if (pmt[i] > 1)
            col[m][0] *= pwe[pmt[i]][pmt[i] - 2];
        return;
    }
    For (i, 1, cnt) tmp[dep] = i, orzcyx(dep + 1, cnt);
    tmp[dep] = ++cnt; orzcyx(dep + 1, cnt);
}
int trans(int st, int qn) {
    int i, j, tot = 0; bool o = 0;
    For (i, 2, k) if (col[st][i] == col[st][1])
        {o = 1; break;}
    if (!o && !(qn & 1)) return -1;
    For (i, 1, k) For (j, i + 1, k)
        if (((qn >> i - 1) & 1) && ((qn >> j - 1) & 1)
        && col[st][i] == col[st][j]) return -1;
    For (i, 1, k) tmp[i] = col[st][i];
    For (i, 1, k) if ((qn >> i - 1) & 1)
        For (j, 1, k) if (tmp[j] == col[st][i]) tmp[j] = k + 1;
    For (i, 1, k - 1) tmp[i] = tmp[i + 1];
    tmp[k] = k + 1;
    For (i, 1, k + 1) vis[i] = 0;
    For (i, 1, k) {
        if (!vis[tmp[i]]) vis[tmp[i]] = ++tot;
        pmt[i] = vis[tmp[i]];
    }
    For (i, 1, m) {
        bool is = 1;
        For (j, 1, k)
            if (col[i][j] != pmt[j]) {is = 0; break;}
        if (is) return i;
    }
    return -1;
}
int main() {
    int i, j;
    cin >> k >> n;
    For (i, 1, k) {
        pwe[i][0] = 1;
        For (j, 1, k) pwe[i][j] = pwe[i][j - 1] * i;
    }
    orzcyx(1, 0);
    S = cyx(m, 1); A = cyx(m, m);
    For (i, 1, m) For (j, 0, (1 << k) - 1) {
        int z = trans(i, j);
        if (z != -1) A.a[z][i]++;
    }
    For (i, 1, m) S.a[i][1] = col[i][0];
    cout << ((A ^ n - k) * S).a[1][1] << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/81455450
今日推荐