[BJOI2019]排兵布阵

做题时间:2022.7.21

\(【题目描述】\)

给定一个整数 \(M(1\leq M\leq 2\times 10^4)\),以及 \(S(1\leq S\leq 100)\) 个长为 \(N(1\leq N\leq 100)\) 的序列 \(A^{1},A^2,\cdots A^{S}\),满足任意一个序列中所有数的和小于等于 \(M\)。现在你可以将 \(M\) 拆分成 \(N\) 个非负整数 \(s_1,s_2,\cdots s_N\) ,若对于 \(\forall i,j,1\leq i\leq S,1\leq j\leq N\) 满足 \(s_{j}>a_{i,j}\times 2\) ,则会有 \(j\) 的贡献,问如何拆使得贡献最大。

\(【输入格式】\)

第一行三个整数 \(S,N,M\)
接下来 \(S\) 行,第 \(i+1\)\(N\) 个整数表示序列 \(A^i\)

\(【输出格式】\)

一行一个整数表示最大贡献。

\(【考点】\)

背包DP

\(【做法】\)

容易看出分配给 \(j\)\(s_j\) 的话,对答案的贡献则为 \(a_{1,j},a_{2,j},\cdots ,a_{s,j}\) 中所有小于 \(\lceil\frac{s_{j}}{2}\rceil\) 的数的个数,设它为 \(val\),那么我们可以看成花费 \(s_j\) ,获得 \(val\) 的价值,也就转化成了背包了。

定义 \(f_{i,j}\) 表示所有序列前 \(i\) 个位置分配了 \(j\) 的价值,则有:

\[f_{i,j}=\max\limits_{0\leq k\leq j}(f_{i-1,j-k}+val_k) \]

复杂度为 \(O(N^2MS)\) ,会炸

考虑优化,一个个枚举 \(k\) 显然太慢。可以看出,只有在 \(val_k\) 改变的情况下, \(f_{i-1,j-k}+val_k\) 才会发生变化,因此我们可以考虑对于当前的 \(a_{1,j},a_{2,j},\cdots ,a_{s,j}\) 从小到大便利所有的 \(2\times a_{i,j}+1\) ,将其作为 \(k\) 转移,并预处理,可以使得复杂度降到 \(O(NMS)\),且跑不满,可以过。

\(【代码】\)

#include<cstdio>
#include<iomanip>
#include<algorithm>
using namespace std;
const int N=1e2+50,M=2e4+50;
int f[N][M],num[N][N],val[N][N],ed[N],n,m,s;
int a[N][N],tmp[N];
inline int Max(int a,int b){return a>b?a:b;}
int main()
{
//	freopen("data.out","r",stdin);
//	freopen("std.out","w",stdout);
	scanf("%d%d%d",&s,&n,&m);
	for(int i=1;i<=s;i++){
		for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
	}
	for(int j=1;j<=n;j++){
		for(int i=1;i<=s;i++) tmp[i]=a[i][j];
		sort(tmp+1,tmp+1+s);
		int x=0;
		for(int i=1;i<=s;i++){
			if(i==s||tmp[i]!=tmp[i+1]){ 
				num[++ed[j]][j]=tmp[i]*2+1;
				val[ed[j]][j]=j*i;
				//存下所有不相同的 2*a(i,j)+1 及其对答案的贡献 
			}
		}
	}
	
	
	for(int j=1;j<=n;j++){
		for(int k=0;k<=m;k++){
			f[j][k]=f[j-1][k];
			for(int i=1;i<=ed[j]&&k-num[i][j]>=0;i++){
				f[j][k]=Max(f[j][k],f[j-1][k-num[i][j]]+val[i][j]);
			}
		}
	}
	int ans=0;
	for(int j=0;j<=m;j++) ans=Max(ans,f[n][j]);
	printf("%d\n",ans);
	return 0;
}
posted @ 2022-07-22 07:50  lxzy  阅读(17)  评论(0)    收藏  举报