Bootstrap

NOIP2019 Emiya家今天的饭

NOIP2019 Emiya家今天的饭

ACM退役选手远程口胡
csf如今真的是太菜了,最后16分的做法愣是想了一下午
考虑使用容斥方法:

1

采用动态规划,先求出在无限制情况下,安排 k k k种烹饪方法总的方案数.
d p 2 [ i ] [ j ] dp2[i][j] dp2[i][j]表示已经考虑完前 i i i种烹饪方法,共做了 j j j个菜的方案数.
那么显然,决策分2种情况,用或不用第 i i i种烹饪方法,用的话就只能选一种主要食材.
d p 2 [ i ] [ j ] = d p 2 [ i − 1 ] [ j ] + d p 2 [ i − 1 ] [ j − 1 ] ∗ ( ∑ t = 1 m a [ i ] [ t ] ) dp2[i][j]=dp2[i-1][j] + dp2[i-1][j-1]*(\sum_{t=1}^m a[i][t]) dp2[i][j]=dp2[i1][j]+dp2[i1][j1](t=1ma[i][t])
时间复杂度 O ( n 2 ) O(n^2) O(n2), ∑ t = 1 m a [ i ] [ t ] \sum_{t=1}^m a[i][t] t=1ma[i][t]可以提前维护好.

2

采用动态规划,计算出那些不合法的方案,并将这些方案减掉.
因为每次只能有一个主要食材不合法.所以对每个主要食材单独考虑,假设当前 t t t食材不合法了.
最朴素的想法是,采用 d p 1 [ i ] [ j ] [ k ] dp1[i][j][k] dp1[i][j][k]表示考虑完前 i i i烹饪方法,已经做了 j j j个菜,使用 t t t食材的有 k k k个的方案数.

那么,决策就是第 i i i中烹饪方案选不选,选了之后,选不选 t t t作为食材,一共 3 3 3个转移.

记录 l i n e s u m [ i ] = ∑ t = 1 m a [ i ] [ t ] linesum[i]=\sum_{t=1}^m a[i][t] linesum[i]=t=1ma[i][t]

d p 1 [ i ] [ j ] [ k ] = d p 1 [ i − 1 ] [ j ] [ k ] + d p 1 [ i − 1 ] [ j − 1 ] [ k − 1 ] ∗ a [ i ] [ t ] + d p 1 [ i − 1 ] [ j − 1 ] [ k ] ∗ ( l i n e s u m [ i ] − a [ i ] [ t ] ) dp1[i][j][k]=dp1[i-1][j][k]+dp1[i-1][j-1][k-1]*a[i][t]+dp1[i-1][j-1][k]*(linesum[i]-a[i][t]) dp1[i][j][k]=dp1[i1][j][k]+dp1[i1][j1][k1]a[i][t]+dp1[i1][j1][k](linesum[i]a[i][t])

最后答案减去 d p 1 [ n ] [ j ] [ k ] ∣ k > j / 2 dp1[n][j][k]|_{k \gt j/2} dp1[n][j][k]k>j/2

时间复杂度为 O ( n 3 m ) O(n^3m) O(n3m),只能过84分,接下来继续优化

事实上,我们无需同时记录 j j j k k k,而只需要记录 k − ( j − k ) k - (j-k) k(jk)的值就足够了,也就是 t t t食材的数量和非 t t t食材的数量.

这样的话,记录 d p 1 [ i ] [ Δ ] dp1[i][\Delta] dp1[i][Δ]表示考虑完前 i i i行,选取的 t t t食材和非 t t t食材的差值为 Δ \Delta Δ时候,方案数.
转移方程如下
d p 1 [ i ] [ Δ ] = d p 1 [ i − 1 ] [ Δ ] + d p 1 [ i − 1 ] [ Δ − 1 ] ∗ a [ i ] [ t ] + d p 1 [ i − 1 ] [ Δ + 1 ] ∗ ( l i n e s u m [ i ] − a [ i ] [ t ] ) dp1[i][\Delta]=dp1[i-1][\Delta] + dp1[i-1][\Delta-1]*a[i][t]+dp1[i-1][\Delta+1]*(linesum[i]-a[i][t]) dp1[i][Δ]=dp1[i1][Δ]+dp1[i1][Δ1]a[i][t]+dp1[i1][Δ+1](linesum[i]a[i][t])
最后答案减去 d p 1 [ n ] [ Δ ] ∣ Δ > 0 dp1[n][\Delta]|_{\Delta>0} dp1[n][Δ]Δ>0
时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)

综上,总的时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)

AC代码

#include <iostream>
#include <cstring>
using namespace std;
#define int long long
const int MOD = 998244353;
const int maxn = 107,maxm = 2007;
int dp1[maxn][2*maxn]; //
int dp2[maxn][maxn];
int linesum[maxn]; //s1表示
int a[maxn][maxm];
int n, m;
signed main() {
	cin >> n >> m;
	for(int i = 1;i <= n;++i) {
		for(int j = 1;j <= m;++j) {
			cin >> a[i][j];
			a[i][j] %= MOD;
			linesum[i] = ( linesum[i] + a[i][j] ) % MOD;
		}
	}
	int ans = 0;
	for(int t = 1;t <= m;++t) {
		memset(dp1,0,sizeof(dp1));
		dp1[0][0 + 100] = 1;
		for(int i = 1;i <= n;++i) {
			for(int j = -i + 100;j <= i + 100;++j) {
				dp1[i][j] = dp1[i-1][j];
				dp1[i][j] += (dp1[i-1][j-1] * a[i][t]) % MOD;//取
				dp1[i][j] += (dp1[i-1][j+1] * ((linesum[i] - a[i][t] + MOD) % MOD)) % MOD;//不取
				dp1[i][j] %= MOD;
			}
		}
		for(int j = 1;j <= n;++j) {
			ans = (ans - dp1[n][j + 100]) % MOD;
		}
	}
	dp2[0][0] = 1;
	for(int i = 1;i <= n;++i) {
		for(int j = 0;j <= i;++j) {
			dp2[i][j] = dp2[i-1][j];
			for(int k = 1;k <= m;++k) {
				dp2[i][j] += dp2[i-1][j-1] * a[i][k] % MOD;
				dp2[i][j] %= MOD;
			}
		}
	}
	for(int j = 1;j <= n;++j)
		ans = (ans + dp2[n][j]) % MOD;

	cout << ans << endl;
	return 0;
}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;