[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\) 的价值,则有:
复杂度为 \(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;
}