ARC 028 D – 注文の多い高橋商店

题目意思

原题面

高橋商店では \(N\) 種類の商品が売られています。「どの種類の商品がいくつあるか」の情報が与えられるので、「合計 \(M\) 個の商品を選ぶ方法」の数を求めて下さい。ただし、同じ種類の商品は区別しないこととします。
いや、これは少し簡単過ぎるので、ちょっとした注文も追加しよう。整数 k, x からなる \(Q\) 個の注文を用意したので、それぞれについて「\(k_i\) 種類目の商品をちょうど \(x_i\) 個選ばなければならないとき、合計 \(M\) 個の商品を選ぶ方法」の数を求めて下さい。

DeepL 翻译后的题面

高桥商店出售 \(N\) 种类型的商品。 给出 "有多少种产品可供选择 "的信息,找出 "选择总共M种产品的方法 "的数量。 然而,我们将不对同一类型的产品进行区分。
不,这有点太容易了,所以让我们增加一个小订单。 我们有 \(Q\) 个由整数 \(k\)\(x\) 组成的订单。对于每一个订单,请找出 "当正好要选择第 \(k_i\) 种产品的 \(x_i\) 个产品时,总共要选择 \(M\) 个产品的方法 "的数目。

数据范围

商品的种类个数:\(a_i\)\(k_i\le M\)\(x_i\le a_{x_i}\)

  • \(10\%\) 的数据,\(N,M,Q \le 100\)

  • \(30\%\) 的数据,\(N,M\le 100,Q\le 5\cdot 10^5\)

  • \(80\%\) 的数据,\(N,M\le 2000,Q\le 1000\)

  • \(100\%\) 的数据,\(N,M\le 2000,Q\le 5\cdot 10^5\)

解题

方法 \(1\)

我们可以每一次询问单独做一个 dp。设计 \(dp_{i,j}\) 为到了第 \(i\) 种商品,已经选了 \(j\) 个的方案数。转移为 \(\displaystyle dp_{i,j}=\sum^{j}_{k=\max(0,j-a_i)} dp_{i-1,k}\)。保存前缀和,更新的时候就可以 \(O(1)\)。总复杂度 \(O(NMQ)\)

期望得分 \(10\%\)

方法 \(2\)

发现如果 \(x_i\) 不会对答案做贡献,可以预处理,枚举每一个 \(i\le N\),对于每一个除了 \(i\) 的做 dp。这样时间复杂度 \(O(N^2M)\)

期望得分 \(30\%\)

方法 \(3\)

发现每一次都重新计算一遍非常的麻烦,也很费时。

其实第 \(i\) 次也就是 \(i\) 影响了,那么 \(0\sim i\) 以及 \(i+1\sim N\) 其实是不需要重新计算的。这样维护两个 dp,一个前缀,一个后缀就可以了,每一次询问 \(O(M)\),总时间复杂度 \(O(QM+NM)\)

期望得分 \(80\%\)

方法 \(4\)

我们先用 「方法 \(1\)」里的 dp 计算一次,设答案数组为 \(dp\)

定义 \(ans_{i,j}\) 为除了第 \(i\) 种选 \(j\) 个的方法数。很容易发现每一次询问要求的就是 \(ans_{k_i,m-x_i}\)

\(ans\) 数组的转移为:\(ans_{i,j}=dp_{n,j}-\displaystyle \sum^{j-1}_{k=\max(0,j-a_i)} ans_{i,k}\)。这个式子很好理解。\(dp_{n,j}\) 多考虑了一些方案,就是选取了一些第 \(i\) 种的,这些需要减去。假设选了 \(k\) 个第 \(i\) 种,那么就还要选 \(j-k\) 个不是第 \(i\) 种的,也就是 \(ans_{i,j-k}\),于是就有了 \(\displaystyle \sum^{j-1}_{k=\max(0,j-a_i)} ans_{i,k}\)

上面式子里的 \(\displaystyle \sum^{j-1}_{k=\max(0,j-a_i)} ans_{i,k}\) 可以用前缀和处理。复杂度 \(O(NM)\)

期望得分 \(100\%\)

代码
#include <bits/stdc++.h>

using namespace std;

#define dg(x) cout<<#x<<"="<<x<<endl

using ll = long long;

const int N = 2e3+3;
const int mod = 1e9+7;

ll n,m,q,a[N];
ll dp[N][N],ans[N][N],pre[N];

ll cal(int i){
	if (i<0){
		return 0;
	}
	return pre[i];
}

void precalc(){
	dp[0][0]=1;
	for (int i=0; i<=m; i++){
		pre[i]=1;
	}
	for (int i=1; i<=n; i++){
		for (int j=0; j<=m; j++){
			dp[i][j]=pre[j]-cal(j-a[i]-1);
			dp[i][j]=(dp[i][j]+mod)%mod;
		}
		pre[0]=dp[i][0];
		for (int j=1; j<=m; j++){
			pre[j]=pre[j-1]+dp[i][j];
			pre[j]%=mod;
		}
	}
}

void anscalc(){
	for (int i=1; i<=n; i++){
		for (int j=0; j<=m; j++){
			pre[j]=0;
		}
		ans[i][0]=pre[0]=1;
		for (int j=1; j<=m; j++){
			ll interval_sum=(pre[j-1]-cal(j-a[i]-1)+mod)%mod;
			ans[i][j]=(dp[n][j]-interval_sum+mod)%mod;
			pre[j]=pre[j-1]+ans[i][j];
			pre[j]%=mod;
		}
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n>>m>>q;
	for (int i=1; i<=n; i++){
		cin>>a[i];
	}
	precalc();
	anscalc();
	while (q--){
		int k,x;
		cin>>k>>x;
		cout<<ans[k][m-x]<<endl;
	}
	return 0;
}

原文链接: https://www.cnblogs.com/SFlyer/p/17067015.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    ARC 028 D - 注文の多い高橋商店

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/313995

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月16日 下午1:07
下一篇 2023年2月16日 下午1:08

相关推荐