ARC162F Montage

脑子被吃掉了。

手玩一下,容易转化题意为:

按行从上到下填 0/1 矩阵,设第 i非空行上是 1 的位置的集合为 Si,满足:

  • 对于任意 i>1,令 D=SiSi1
  • D=,则 Si 中所有元素均比 Si1 中任意元素小,即 maxiSii<miniSi1i
  • D,则 Si,Si1,D 从小到大排序后,DSi 的后缀,为 Si1 的前缀,即 maxiSiDi<miniDimaxiDi<miniSi1Di

现在这个限制是从右上角走到左下角,那不如先把行翻转一下,就变成了从左上角到右下角:

  • D=miniSii>maxiSi1i
  • DminiSiDi>maxiDiminiDi>maxiSi1Di

那我们直接把空行删掉,令 fi,j,k 表示考虑到第 i 行,第 i 行一共有 j1,最右边的 1 的位置为 k 的方案数。转移枚举新的最右边的 1 的位置 pD 的大小 sSi+1D 的大小 l,转移系数就是个组合数:

fi,j,k(pk1s1)fi+1,l+s,p

枚举非空的行数,答案就是:

ans=1+i=1n(ni)j=1mk=1mj+1fi,j,k

直接做是 O(n6) 的。利用不同的 l 转移相同可用前缀和优化至 O(n5)

然后我们发现把矩阵转置一下,将 n,m 交换不影响答案,也就是说我们可以把空的列也拿出来。于是每个 Si 就是一段区间了,且 Si,Si+1 要么相交不包含要么相邻,Si+1Si 的右边。

fi,j,k 重新定义为 S1=[1,r1],Si=[jk+1,j],满足以上限制的方案数。其实就是把前面的 j,k 两维交换了一下。

那么答案变为:

ans=1+i=1nj=1m(ni)(mj)k=1jfi,j,k

转移还是枚举 Si+1 的两个端点 p,q,满足 jk+1pj+1,max(p,j)qm:

fi,j,kfi+1,q,qp+1

然而这还是 O(n5) 的。由于对于不同的 p 贡献相同,设 fi,j,k 为第三维差分后的 f 数组,可用前缀和优化至 O(n4)

fi,j,kfi+1,q,qmin(j+1,q)+1,fi,j,kfi+1,q,qj+k+1

单独将 q=j 的转移拎出来,这部分 O(n3)

fi,j,kfi+1,j,1,fi,j,kfi+1,j,k+1

剩下的是 qj+1 的转移:

fi,j,kfi+1,q,qj,fi,j,kfi+1,q,qj+k+1

这还是 O(n4) 的,但是我们观察到第二维和第三维的差为定值且与 q 无关,所以令 gi,j,k+1=fi,j,jk

fi,j,kgi+1,q,j+1,fi,j,kgi+1,q,jk

注意到 q[j+1,m],再对 g 的第二维进行差分记作 g,前缀和优化即可做到 O(n3)

fi,j,kgi+1,j+1,j+1,fi,j,kgi+1,j+1,jk

最终时间复杂度 O(n3),空间复杂度 O(n2)

#include <bits/stdc++.h>
#define eb emplace_back
#define pb pop_back
#define mt make_tuple
#define mp make_pair
#define fi first
#define se second

using namespace std;
typedef long long ll;
typedef pair<int, int> pi;
typedef tuple<int, int, int> tu;
bool Mbe;


const int N = 405;
const int P = 998244353;

int n, m, ans, f[2][N][N], g[2][N][N], C[N][N];

int qpow(int p, int q) {
	int res = 1;
	for (; q; q >>= 1, p = 1ll * p * p % P)
		if (q & 1) res = 1ll * res * p % P;
	return res;
}

void init(int lim) {
	C[0][0] = 1;
	for (int i = 1; i <= lim; i++) {
		C[i][0] = 1;
		for (int j = 1; j <= lim; j++)
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
	}
}

void solve() {
	cin >> n >> m, init(400);
	for (int i = 1; i <= m; i++) 
			f[1][i][i] = 1;
	for (int i = 1, t = 1; i <= n; i++, t ^= 1) {
		memset(f[t ^ 1], 0, sizeof(f[t ^ 1]));
		memset(g[t ^ 1], 0, sizeof(g[t ^ 1]));
		for (int j = 1; j <= m; j++) {
			for (int k = 1; k <= j; k++)
				(g[t][j][k] += g[t][j - 1][k]) %= P;
			for (int k = 1; k <= j; k++)
				(f[t][j][k] += g[t][j][j - k + 1]) %= P;
			for (int k = 1; k <= j; k++)
				(f[t][j][k] += f[t][j][k - 1]) %= P;
			for (int k = 1; k <= j; k++) {
				(f[t ^ 1][j][1] += f[t][j][k]) %= P;
				(f[t ^ 1][j][k + 1] += P - f[t][j][k]) %= P;
				(g[t ^ 1][j + 1][j + 1] += f[t][j][k]) %= P;
				(g[t ^ 1][j + 1][j - k] += P - f[t][j][k]) %= P;
			}
			int res = 0;
			for (int k = 1; k <= j; k++) 
				(res += f[t][j][k]) %= P;
			(ans += 1ll * C[n][i] * C[m][j] % P * res % P) %= P;
		}
	}
	cout << (ans + 1) % P << '\n';
}

bool Med;
int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cerr << (&Mbe - &Med) / 1048576.0 << " MB\n";
	#ifdef FILE
		freopen("1.in", "r", stdin);
		freopen("1.out", "w", stdout);
	#endif
	int T = 1;
	// cin >> T;
	while (T--) solve();
	cerr << (int)(1e3 * clock() / CLOCKS_PER_SEC) << " ms\n";
	return 0;
}
posted @   Arghariza  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
主题色彩