Gravatar
yrtiop
积分:2101
提交:309 / 808

Pro2020  [NOI 2015]寿司晚宴

首先,两两互质等价于两个集合的质数集无交集。

那么从质数的方面考虑,不难想到状态压缩 DP。

但是打个表发现 $500$ 以内的质数接近 $100$ 个,显然压缩不了。

考虑一个解决这种问题相当常用的方法:根号分治。

注意到 $22$ 以内的质数只有 $8$ 个,而 $22$ 以上的质数至多会被一个数包含一次,称这些质数为「大质数」。

因为它们至多被包含一次,我们可以直接分类讨论它们在哪个集合。

具体地,我们把前 $8$ 个质数压缩状态,把 $2\sim n$ 除去前 $8$ 个质数的结果升序排序。

依次枚举 $2\sim n$,开三个 DP 数组:

$f(j,k)$:表示当前第一个集合状态为 $j$,第二个集合状态为 $k$ 的方案数。

$F(j,k)$:表示当前第一个集合状态为 $j$,第二个集合状态为 $k$,且当前枚举到的「大质数」都在第一个集合的方案数。

$G(j,k)$:表示当前第一个集合状态为 $j$,第二个集合状态为 $k$,且当前枚举到的「大质数」都在第二个集合的方案数。

注:其实这里本该是 $f(i,j,k)$,不过可以直接滚动数组滚掉第一维的阶段,所以我没有写在式子里。

转移相当简单:$F(j|s,k)=\sum F(j,k),G(j,k|s)=\sum G(j,k)$

而 $f$ 数组较为特殊:$f(j,k)=F(j,k)+G(j,k)-f(j,k)$

原因不难理解:$F(j,k),G(j,k)$ 都经过了一轮转移,但 $f(j,k)$ 还保留着上一轮的结果。

直接 $F(j,k)+G(j,k)$ 会将 $f(j,k)$ 计算两遍,所以要再减回来。

还有一些小细节:比如 $f(j,k)$ 复制到 $F(j,k),G(j,k)$,以及 $f(j,k)$ 的计算条件可以参考代码。

时间复杂度 $\mathcal{O}(n\times 2^{16})$


// Problem: #129. 【NOI2015】寿司晚宴
// Contest: UOJ
// URL: https://uoj.ac/problem/129
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
typedef long long ll;
typedef std::pair<int,int> pii;

const int maxn = 505;
int n,prime[10] = {2 , 3 , 5 , 7 , 11 , 13 , 17 , 19};
pii a[maxn];
ll p,f[maxn][maxn],F[maxn][maxn],G[maxn][maxn];

int main() {
	scanf("%d %lld",&n,&p);
	
	for(int i = 2;i <= n;++ i) {
		auto& [s , res] = a[i];
		res = i;
		for(int k = 0;k < 8;++ k) {
			if(res % prime[k])continue ;
			s |= 1 << k;
			for(;!(res % prime[k]);res /= prime[k]);
		}
	}
	
	std::sort(a + 2 , a + n + 1 , [&](const pii& p,const pii& q) {
		return p.second < q.second;
	});
	
	f[0][0] = 1;
	for(int i = 2;i <= n;++ i) {
		auto& [s , res] = a[i];
		if(res == 1||res != a[i - 1].second) {
			memcpy(F , f , sizeof(f));
			memcpy(G , f , sizeof(f));
		}
		for(int j = 255;~ j;-- j) {
			for(int k = 255;~ k;-- k) {
				if(j & k)continue ;
				if(!(s & k))(F[j | s][k] += F[j][k]) %= p;
				if(!(s & j))(G[j][k | s] += G[j][k]) %= p;
			}
		}
		if(i == n||res == 1||res != a[i + 1].second) {
			for(int j = 0;j < 256;++ j) {
				for(int k = 0;k < 256;++ k) {
					if(j & k)continue ;
					f[j][k] = (F[j][k] + G[j][k] - f[j][k] + p) % p;
				}
			}
		}
	}
	
	ll ans = 0;
	for(int j = 0;j < 256;++ j) {
		for(int k = 0;k < 256;++ k) {
			if(j & k)continue ;
			(ans += f[j][k]) %= p;
		}
	}
	
	printf("%lld",ans);
	return 0;
}



2024-06-22 16:32:43    
我有话要说
暂无人分享评论!