Loading

AtCoder Regular Contest 067

C,D 还是简单题,口胡了一下没写。

E - Grouping

\(n\) 个人,编号依次是 \(1,2,\dots ,n\), 现在要将他们分成若干组,满足:

  • 每一组的人数均在 \([a,b]\) 之间。

  • \(F_i\) 为 当前分组方案中人数为 \(i\) 的组的数量,则 \(F_i\) 应满足 \(F_i=0\) 或 $ c\leq F_i\leq d$。

求本质不同的分组方案数对 \(10^9+7\) 取模后的结果。两种方案是本质不同的当且仅当存在两个人使得在第一种方案中他们在同一组,而在第二种方案中不是。

数据范围:\(1\leq n\leq 10^3,1\leq a\leq b\leq n,1\leq c\leq d\leq n\)

数据这么小,又要求方案数,显然是道 dp。

\(f(i,j)\) 表示人数为 \(a\sim a+j-1\) 的组分了 \(i\) 个人的方案数,则有:

\(f(i,j)=\sum\limits_{k=c}^d f(i-k\times j,j-1)\times \binom{n-(i-k\times j)}{k\times j}\times g(k,j)\div k!\)

其中 \(g(k,j)\) 表示用 \(k\times j\) 个人分 \(k\) 组的方案数,显然 \(g(k,j)=\binom{k\times j}{j}\times g(k-1,j)\),直接递推即可。

又因为组序无影响,故要除以 \(k!\)

我一开始以为上面那串式子是 \(\mathcal O(n^3)\) 的,过不去,但是要注意到 \(k\) 的枚举是 \(\sum \frac{n}{i}\) 级别的,也就是调和级数,所以真正的时间复杂度是 \(\mathcal O(n^2\ln n)\),足以通过。

Code

// Problem: E - Grouping
// Contest: AtCoder - AtCoder Regular Contest 067
// URL: https://atcoder.jp/contests/arc067/tasks/arc067_c
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using i64 = long long;

const i64 mod = 1e9 + 7;
const int maxn = 1e3 + 5;
int n,a,b,c,d;
i64 dp[maxn][maxn],frac[maxn],inv[maxn],g[maxn][maxn];

i64 power(i64 x,i64 y) {
	i64 ans = 1;
	for(;y;y >>= 1) {
		if(y & 1)(ans *= x) %= mod;
		(x *= x) %= mod;
	}
	return ans;
}

i64 C(int n,int m) {
	if(n < m||n < 0||m < 0)return 0;
	return frac[n] * inv[n - m] % mod * inv[m] % mod;
}

int main() {
	scanf("%d %d %d %d %d",&n,&a,&b,&c,&d);
	frac[0] = 1;
	for(int i = 1;i <= n;++ i)
		frac[i] = 1ll * i * frac[i - 1] % mod;
	inv[n] = power(frac[n] , mod - 2);
	for(int i = n - 1;~ i;-- i)
		inv[i] = 1ll * (i + 1) * inv[i + 1] % mod;
	
	for(int j = a;j <= b;++ j) {
		g[0][j] = 1;
		for(int k = 1;k <= d;++ k) {
			g[k][j] = g[k - 1][j] * C(k * j , j) % mod;
		}
	}
	
	dp[0][a - 1] = 1;
	for(int j = a;j <= b;++ j) {
		for(int i = 0;i <= n;++ i) {
			dp[i][j] = dp[i][j - 1];
			for(int k = c;k <= d;++ k) {
				if(j * k > i)break ;
				(dp[i][j] += dp[i - k * j][j - 1] * C(n - (i - k * j) , k * j) % mod * g[k][j] % mod * inv[k] % mod) %= mod;
			}
		}
	}
	
	printf("%lld",dp[n][b]);
	return 0;
}

F - Yakiniku Restaurants

有编号从 $ 1 $ 到 $ n $ 的 $ n $ 家烧烤店,烧烤店在一条线上按照编号顺序排序,第 $ i $ 家烧烤店与第 $ i + 1 $ 家烧烤店的距离是 $ a_i $ 。
你有编号从 $ 1 $ 到 $ m $ 的 $ m $ 张烧烤券,不管是在哪一家烧烤店都可用烧烤券来吃烧烤,在第 $ i $ 家烧烤店用烧烤券 $ j $ 可以吃到一顿美味度为 $ b_{i,j} $ 的烧烤,每一张烧烤券只能使用一次,但是在一家烧烤店你可以使用任意多张烧烤券。
你想从自己选择的一家烧烤店开始(随意选择一个开始),然后不断地用未使用的烧烤券去另一家烧烤店。你最终的幸福值是所吃所有烧烤的美味度减去所走的总路程。求最大可能的最终幸福值( $ m $ 张券必须用完)。

\(1\le n\le 5\times 10^3,1\le m\le 200,1\le a_i,b_{i,j}\le 10^9\)

首先要发现:选择的烧烤店一定是连续的线段。

这样的话,我们可以枚举左右端点,距离可以直接算出来,我们只需要让美味度最大化。

注意到每种烧烤券互相独立,我们可以分开考虑。

显然,对于烧烤券 \(j\) 和区间 \([l,r]\),对 \([l,r]\) 有贡献的是 \(\max\limits_{k\in [l,r]} b_{k,j}\)

转向考虑 \(b_{i,j}\) 对于哪些区间有贡献。若 \(b_{i,j}\)\([l,r]\) 有贡献,则 \([l,r]\)\(b\) 的最大值应为 \(b_{i,j}\)

\(L_i,R_i\) 表示极大的满足 \(\max\limits_{k\in (L_i,R_i)}b_{k,j}=b_{i,j}\) 的区间两端点,这是单调栈的经典问题,珂以 \(\mathcal O(n)\) 求解。

显然,当 \(l\in (L_i,i],r\in [i,R_i)\) 时,\(b_{i,j}\)\([l,r]\) 有贡献。

发现这个东西可以转化为平面上一个矩形的整体加,用一个二维差分维护即可。最后统计答案。

时间复杂度 \(\mathcal O(n(n+m))\)

Code

// Problem: F - Yakiniku Restaurants
// Contest: AtCoder - AtCoder Regular Contest 067
// URL: https://atcoder.jp/contests/arc067/tasks/arc067_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using i64 = long long;

const int maxn = 5e3 + 5;
const int maxm = 205;
int n,m,a[maxn],b[maxn][maxm];
int stk[maxn],top,L[maxn],R[maxn];
i64 sum[maxn],res[maxn][maxn];

int main() {
	scanf("%d %d",&n,&m);
	for(int i = 2;i <= n;++ i)
		scanf("%d",&a[i]),sum[i] = sum[i - 1] + a[i];
	for(int i = 1;i <= n;++ i)
		for(int j = 1;j <= m;++ j)
			scanf("%d",&b[i][j]);
	
	for(int j = 1;j <= m;++ j) {
		stk[top = 0] = 0;
		for(int i = 1;i <= n;++ i) {
			while(top&&b[stk[top]][j] <= b[i][j])
				R[stk[top --]] = i;
			L[i] = stk[top];
			stk[++ top] = i;
		}
		while(top)
			R[stk[top --]] = n + 1;
		for(int i = 1;i <= n;++ i) {
			res[L[i] + 1][i] += b[i][j];
			res[i + 1][i] -= b[i][j];
			res[L[i] + 1][R[i]] -= b[i][j];
			res[i + 1][R[i]] += b[i][j];
		}
	}
	
	for(int i = 1;i <= n;++ i)
		for(int j = 1;j <= n;++ j)
			res[i][j] += res[i - 1][j] + res[i][j - 1] - res[i - 1][j - 1];
	
	i64 ans = -1e16;
	for(int i = 1;i <= n;++ i) {
		for(int j = i;j <= n;++ j) {
			ans = std::max(ans , res[i][j] - sum[j] + sum[i]);
		}
	}
	
	printf("%lld\n",ans);
	return 0;
}
posted @ 2022-11-03 22:07  Skylakes  阅读(26)  评论(0编辑  收藏  举报