Maybe something will ch|

Semorius

园龄:1年9个月粉丝:4关注:10

2023-07-09 08:45阅读: 8评论: 0推荐: 0

P5392 [Cnoi2019]雪松树之约

P5392 [Cnoi2019]雪松树之约

首先分析一下给出的图的形态,限制 1 表示每相邻两层之间对应点之间两两连边,限制 2 和限制 3 表示每一层是一个环,所以整张图可以形象地看成是由 L 个大小为 x 的环上下顺次相连形成的柱状图。

观察数据范围,L 很大但 x 很小,所以可以考虑对每一层做状态压缩,然后利用矩阵快速幂进行层与层之间的转移。对每个环逆时针编号为 1x,设 si 表示第 i 层的状态,si 二进制下低位开始第 j 位为 10 表示环上编号为 j 的点选或不选。

先预处理出每层满足独立集的状态,发现当 x=17 时,一共有 3571 种。如果用这些状态转移在矩阵乘法自带的 O(n3) 复杂度下根本无法通过。那么能否再压缩掉一些状态?

手动模拟发现有些情况其实是本质相同的。

1
2

例如上面两个环,忽略编号时,右图中的环可由左图中的环逆时针旋转 144 得到,也就是说,这两个环实际上是本质相同的。

考虑合并这种旋转以后能重合的本质相同的环,再跑一遍发现可用状态只剩下 211 种,算一下复杂度,似乎只要常数小刚好能过?

然后想压缩掉这些本质相同的状态后如何转移。对于一些本质相同的状态,我们从中选出其中一个作为这些状态的代表环,而这个代表环是有编号对应的。我们设 dpi,sj 表示第 i 层的状态为 sj固定i不动的方案数。这里不动的含义是当前层选的状态与该状态对应的代表环严格相同,而第 1i1 层可整体旋转与之相匹配。当从 dpi1,skdpi,sj 转移时,记状态 sk 通过旋转得到的 x 个状态中,与 sj 构成独立集的状态数有 cntk,j 种。那么有如下转移:

dpi,sj=dpi1,sk×cntk,j

转化为矩阵,容易发现 cnt 数组即为转移矩阵。

最后统计答案时,对于第 L 层的每一种状态的答案,要乘上该状态里旋转可得的状态个数。(相当于将整张图旋转一定的角度)

注意常数,尽量减少取模,时间复杂度 O(x2x+2113logL)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll SIZE = 200005;
const ll mod = 998244353;
ll L, n;
bool mp[150005];
bool mp1[150005];
ll cntm[150005];
ll a[SIZE], id[150005], tot;
inline ll rd(){
ll f = 1, x = 0;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return f*x;
}
struct node{
ll o[212][212];
}zy;
node jl;
node mul(node A, node B){
for(int i = 0; i <= tot; i++)
for(int j = 0; j <= tot; j++){
jl.o[i][j] = 0;
for(ll k = 0; k <= tot; k++){
jl.o[i][j] += A.o[i][k] * B.o[k][j] % mod;
}
jl.o[i][j] %= mod;
}
return jl;
}
node power(node x, ll y){
node j2;
for(int i = 0; i <= tot; i++)
for(int j = 0; j <= tot; j++)
j2.o[i][j] = (i==j);
while(y){
if(y & 1) j2 = mul(j2, x);
x = mul(x, x);
y >>= 1;
}
return j2;
}
int main(){
L = rd(), n = rd();
for(int i = 0; i < (1<<n); i++){
ll now = i; bool ff = 0;
for(ll j = 2; j <= n; j++){
if((i&(1<<(j-1))) && (i&(1<<(j-2)))){
ff = 1;
break;
}
}
if((i&1) && (i&(1<<(n-1)))) ff = 1;
if(!ff){ //同一个环内满足两两不相邻
for(ll j = 1; j < n; j++){
now = ((now&1)<<(n-1))|(now>>1);
if(mp[now]){
cntm[id[now]]++;//更新循环意义下相同方案的个数
ff = 1;
break;
}
}
}
if(!ff){//出现一种新的本质不同的环
cntm[tot+1] = 1;
mp[i] = 1;
a[++tot] = i;
id[a[tot]] = tot;
}
}
for(int i = 1; i <= tot; i++){//枚举可能的转移,构造转移矩阵
for(int j = 1; j <= tot; j++){
if((a[i]&a[j]) == 0) zy.o[i][j]++;
if(a[i] == 0) continue;
ll now = a[i], st = a[i]; bool ff = 0;
for(int h = 1; h < n; h++){
now = ((now&1)<<(n-1))|(now>>1);
if(ff && now == st) break;
if((now&a[j]) == 0) zy.o[i][j]++;
ff = 1;
}
}
}
node ans = power(zy, L-1);
ll aans = 0;
for(int i = 1; i <= tot; i++){
ll jl = 0;
for(int j = 1; j <= tot; j++){
jl = jl + ans.o[j][i];
if(jl >= mod) jl -= mod;
}
jl = jl * cntm[i] % mod; //统计最顶层为状态i的方案数
aans = aans + jl;
if(aans >= mod) aans -= mod;
}
printf("%lld", aans);
return 0;
}

本文作者:Semorius

本文链接:https://www.cnblogs.com/Semorius/p/17538285.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Semorius  阅读(8)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起