生成树计数
luogu 2109
题目大意
有n个排成一列的点,把距离不超过k的点之间连边,问这个图的生成树个数
输入样例
3 5
输出样例
75
样例说明
样例对应的图如下:
数据范围
解题思路
因为n十分大,不能直接2^m暴力枚举(m为总边数)
k十分小,可以从k入手
考虑到对某个点有贡献的只有前k个点
我们可以设状态表示前k个点的连接情况(即那几个点连在了一起)
状态如果直接用k位数表示肯定不行
考虑除掉无用状态
对于着k个数,如果前面有和它相连的点,那么这个点的数值等于和它相连的点的数值,如果没有,则它的数值等于前k个数中最大数值+1
例如:
k=5,1和3相连,2和5相连
则表示出来的状态为12132
s1=++ws_1=++ws1=++w
s2=++ws_2=++ws2=++w
s3=s1s_3=s_1s3=s1
s4=++ws_4=++ws4=++w
s5=s2s_5=s_2s5=s2
得出状态后,我们输出总装台数,k=5时竟只有52种!
用一个数组给各个状态存一下新的编号(52种中的第几种)
然后考虑状态之间的转移
对于所有用的状态,2k2^k2k枚举第k+1k+1k+1个点和kkk个点是否连边
使其满足以下条件
1.不连成环(即数值相同的点最多连一个,不然会形成环)
2.保证第1个点要和2∼k+12\sim k+12∼k+1个点中的一个相连(要么和k+1k+1k+1相连,要么和2∼k2\sim k2∼k个点中某个点的数值相等)
得出状态后通过前面的计算方法,把新状态的编号求出来,然后在矩阵中连起来
得到转移矩阵后,就可以用矩阵乘法快速得出n个点的结果了
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define wyc 65521
using namespace std;
ll n, k, w, ww, s[10], ss[10], fa[10], p[100000];
struct matrix
{ll n, m, a[100][100];matrix operator *(const matrix &b) const{matrix c;c.n = n;c.m = b.m;for (int i = 1; i <= c.n; ++i)for (int j = 1; j <= c.m; ++j)c.a[i][j] = 0;for (int i = 1; i <= c.n; ++i)for (int k = 1; k <= m; ++k)for (int j = 1; j <= c.m; ++j)c.a[i][j] = (c.a[i][j] + a[i][k] * b.a[k][j] % wyc) % wyc;return c;}
}A, B;
void dfs1(ll x, ll y, ll z)
{if (x > k){p[z] = ++w;//新编号ll num = 1, g = 0;for (int i = 1; i <= k; ++i){g = 1;for (int j = 1; j <= s[i] - 2; ++j)//题目说明中n个点的完全图生成树个数为n^(n-2)g = g * s[i];num = num * g;//各种种数相乘}A.a[1][w] = num;//计入初始矩阵return;}for (int i = 1; i <= y; ++i){s[i]++;//这个数值的数+1dfs1(x + 1, y, z * 10 + i);//和前面已有的点相连s[i]--;}s[y + 1]++;//新的数值dfs1(x + 1, y + 1, z * 10 + y + 1);//不相连s[y + 1]--;return;
}
ll find(ll x)
{return x == fa[x]?x:find(fa[x]);//并查集
}
void pp(ll x, ll y)
{if (x > k){ll num = 0, ww = 0;memset(ss, 0, sizeof(ss));for (int i = 2; i <= k + 1; ++i){ll g = find(i);if (!ss[g]) ss[g] = ++ww;//没有相连的就新加一个数值num = num * 10 + ss[g];//用十进制存起来}if (ss[find(1)]) B.a[p[y]][p[num]]++;//第一个数有被加到,那就连接状态return;}ll g = find(k + 1), gg = find(x);if (gg != g)//暂未相连{fa[g] = gg;//连接pp(x + 1, y);fa[g] = g;//没有路径压缩所以可拆边}pp(x + 1, y);//不练的情况return;
}
void dfs2(ll x, ll y, ll z)
{if (x > k){memset(ss, 0, sizeof(ss));for (int i = 1; i <= k; ++i){if (!ss[s[i]]) ss[s[i]] = i;//找到相连的点中的第一个点,以这个点为父节点,ss和上面重复利用fa[i] = ss[s[i]];}fa[k + 1] = k + 1;pp(1, z);return;}for (int i = 1; i <= y; ++i)//再找一次有效状态{s[x] = i;dfs2(x + 1, y, z * 10 + i);s[x] = 0;}s[x] = y + 1;dfs2(x + 1, y + 1, z * 10 + y + 1);s[x] = 0;return;
}
void Counting(ll x)//快速幂
{while(x){if (x&1) A = A * B;B = B * B;x>>=1;}return;
}
int main()
{scanf("%lld%lld", &k, &n);dfs1(1, 0, 0);//得出编号dfs2(1, 0, 0);//连接状态A.n = 1;A.m = B.n = B.m = w;Counting(n - k);//初始状态已经是前k个printf("%lld", A.a[1][1]);return 0;
}