CSP-S 模拟 毛笔与圆珠笔 (状压DP)(搜索打表)(线性递推)(Berlekamp-Massey )

题意:
给定一个 4 ∗ n 4*n 4n的矩阵,可以用任意面积为 4 4 4的连通图形覆盖
完美覆盖的方案个数,对 998244353 998244353 998244353取模, n ≤ 1 e 9 n\le 1e9 n1e9


首先会想到状压 d p dp dp,因为最坏情况就是一个横着的长为 4 的插到前面
这样的话需要压 3 列的状态
然后容易想到先把状态的转移打表打出来
然后你发现讨论所有 4 联通块已经让你头疼了,还要枚举转移
我们无脑直接搜索 4 联通块的形态,为了不重复计算,强制当前连通块顶着最后一列
并且强制当前填的在已经填了的下面
直接枚举 16 个格子的状态判断合不合法即可
这就处理可以填进去的东西
然后处理转移矩阵,先枚举前 3 列,然后最后一列强制必须选
得到后两列的情况后枚举它的超集,用刚刚预处理的可以填进去的东西去转移
然后你发现如果直接做的复杂度是 O ( n ∗ 2 24 ) O(n*2^{24}) O(n224)
接着你发现可以矩阵乘法,紧接着你想到应该可以线性递推
然后发现要解出转移矩阵的特征多项式非常的不可做
我们考虑到,如果解出了它的特征多项式,然后求出
A n = ∑ i = 0 k b i A i A^n=\sum_{i=0}^kb_iA^i An=i=0kbiAi,那么最后的答案就是 ∑ i = 0 k b i ∗ v ∗ A i = ∑ i = 0 k b i ∗ d p i \sum_{i=0}^kb_i*v*A^i=\sum_{i=0}^kb_i*dp_i i=0kbivAi=i=0kbidpi
有一个性质是如果知道 A n = ∑ i = 0 k b i A i A^n=\sum_{i=0}^kb_iA^i An=i=0kbiAi,那么 A n + 1 = ∑ i = 0 k b i A i + 1 A^{n+1}=\sum_{i=0}^kb_iA^{i+1} An+1=i=0kbiAi+1
并且他们的转移系数是一样的,所以矩阵 A A A 可以线性递推
那么矩阵中的某一个值,在这里是第 2 12 − 1 2^{12}-1 2121 2 12 − 1 2^{12}-1 2121 列,也是可以线性递推的
于是暴力预处理前 200 项用 B M BM BM 解出最短递推式,发现长度是 35
直接把表拿到你的标程里面线性递推即可
复杂度 O ( T l o g ( n ) 3 5 2 ) O(Tlog(n)35^2) O(Tlog(n)352)

收获挺多的
现场学了一波 B M BM BM,觉得增量构造特别巧妙
然后学到了不用手算而暴力搜索的 t r i c k trick trick
然后还有为了避免重复计数钦定顺序的方法
复习了一下线性递推,还了解了 A i A^i Ai 可以线性递推的巧妙性质


#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int Mod = 998244353;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
int ksm(int a, int b){ int ans = 1; for(;b;b>>=1, a=mul(a,a)) if(b&1) ans=mul(ans, a); return ans; }
int popcnt(int S){ int x = 0; while(S) S -= S&-S, ++x; return x; }
int cntzero(int S){
	for(int i = 0; i < 4; i++) if((1 << i) & S) return i;
	return 4;
}
void Add(int &a, int b){ a += b; if(a >= Mod) a -= Mod;  }
namespace ckbit{
	int vis[4][4], cur = 0;
	int d[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
	int dfs(int x, int y, int S){
		vis[x][y] = cur;
		int res = 1;
		for(int k = 0; k < 4; k++){
			int nx = x + d[k][0], ny = y + d[k][1]; 
			if(nx >= 0 && ny >= 0 && nx < 4 && ny < 4 && (S >> (nx + ny * 4) & 1) && vis[nx][ny] != cur) res += dfs(nx, ny, S); 
		} return res;
	}
	bool ck(int S){
		int res = 0;
		for(int x = 0; x < 4; x++){
			if(S >> (x + 3 * 4) & 1){
				++cur; res = dfs(x, 3, S);
				break;
			}
		} return res == popcnt(S);
	}
} using namespace ckbit;
cs int SIZE = 300;
int a[SIZE];
#define poly vector<int> 
poly operator + (poly a, poly b){
	poly c; int len = max(a.size(), b.size());
	a.resize(len); b.resize(len); c.resize(len);
	for(int i = 0; i < len; i++) c[i] = add(a[i], b[i]);
	return c;
}
namespace BM{
	poly R[SIZE];
	int cur = 0, fail[SIZE], delta[SIZE];
	void upt(int n){
		int t = a[n];
		for(int i = 1; i < R[cur].size(); i++) t = add(t, Mod - mul(R[cur][i], a[n - i]));
		delta[n] = t;
		if(!t) return;
		fail[cur] = n;
		if(!cur){ R[++cur].resize(n + 1); return; }
		t = mul(t, ksm(delta[fail[cur - 1]], Mod - 2));
		R[cur + 1].resize(n - fail[cur - 1]);
		R[cur + 1].push_back(t);
		for(int i = 1; i < R[cur-1].size(); i++) R[cur + 1].push_back(mul(Mod-R[cur-1][i], t));
		R[cur + 1] = R[cur] + R[cur + 1];
		cur++;
	}
	void solve(){
		cout << R[cur].size() << endl;
		for(int i = 1; i < R[cur].size(); i++) cout << R[cur][i] << ",";
		puts("");
		for(int i = 1; i < R[cur].size(); i++) cout << a[i] << ",";
		puts("");
	}
}
void init(){
	static int blk[1 << 16 | 5], cnt = 0;
	for(int S = 1; S < (1 << 16); S++) if(popcnt(S) == 4 && ck(S)) blk[++cnt] = S;
	static int bs[1 << 16 | 5];
	bs[0] = 1;
	for(int S = 0; S < (1 << 16); S++) 
		for(int i = 1; i <= cnt; i++){
			if(bs[S] && !(S & blk[i]) && ((S >> 12) == 0 || cntzero(S >> 12) > cntzero(blk[i] >> 12)))
				Add(bs[S | blk[i]], bs[S]);
	}
	static int trans[1 << 12 | 5][1 << 12 | 5];
	static int id[1 << 13 | 5]; int ct = 0;
	static bool vis[1 << 12];
	for(int S = 0; S < (1 << 12); S++){
		for(int T = S >> 4; T < 1 << 12; T = (T + 1) | (S >> 4)){
			int T0 = (T << 4) | ((1 << 4) - 1);
			if(bs[T0 ^ S]) trans[S][T] = bs[T0 ^ S];
		}
	} 
	cs int K = 100, U = (1 << 12) - 1;
	static int dp[(K << 1) + 5][U + 5];
	dp[0][U] = 1;
	for(int i = 0; i < (K << 1); i++) for(int S = 0; S < (1 << 12); S++)
		if(dp[i][S]){ for(int T = 0; T < (1 << 12); T++){
			dp[i + 1][T] = add(dp[i + 1][T], mul(dp[i][S], trans[S][T]));
			}
		}
	for(int i = 0; i <= (K << 1); i++) a[i] = dp[i][U], BM::upt(i);
	puts("");
	BM::solve();
}
int main(){ init(); }

偷懒写的 n 2 n^2 n2 取模

#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 40, K = 35;
int a[N] = {1,1,4,23,117,454,2003,9157,40899,179399,796558,3546996,15747348,69834517,310058192,378623792,
122781000,179638924,664040578,693857604,150068224,487780789,81153444,201496361,206356061,443239188,
923221963,678050018,834687149,938086801,37535712,164288625,780488339,49747373,890150065,992668567};
int r[N] = {0,2,8,8,54,998244276,998244063,998244277,998243805,469,2258,414,1970,
998243300,998237468,998242733,998241004,1102,9566,3210,2786,998243864,998238306,
998241753,998243251,60,1476,659,225,39,998244230,998244303,998244340,998244350,3,1};
#define poly vector<int> 
int T, n;
cs int Mod = 998244353;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
int ksm(int a, int b){ int ans=1; for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans, a); return ans; }
poly mod, pw[31];
poly operator * (poly a, poly b){
	int deg = a.size() + b.size() - 1;
	poly c; c.resize(deg);
	for(int i = 0; i < a.size(); i++)
		for(int j = 0; j < b.size(); j++)
			c[i + j] = add(c[i + j], mul(a[i], b[j]));
	for(int i = deg - 1; i >= K; i--){
		if(c[i]){
			int inv = c[i];
			for(int j = 0; j <= K; j++){
				c[i - j] = add(c[i - j], Mod - mul(mod[K - j], inv));
			}
		}
	} if(deg > K) c.resize(K); return c;
}
int calc(int n){
	if(n <= K) return a[n];
	poly nx; nx.push_back(1);
	for(int i = 0; i <= 30; i++) if(n>>i&1) nx = nx * pw[i];
	int ans = 0;
	for(int i = 0; i < K; i++) ans = add(ans, mul(a[i], nx[i]));
	return ans;
}
int main(){
	scanf("%d", &T);
	mod.resize(K + 1);
	for(int i = 1; i <= K; i++) mod[K - i] = r[i] ? Mod - r[i] : 0; mod[K] = 1;
	pw[0].push_back(0); pw[0].push_back(1);
	for(int i = 1; i < 30; i++){
		pw[i] = pw[i-1] * pw[i-1];
	}
	while(T--){ scanf("%d", &n); cout << calc(n) << '\n';}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值