LGP7519 [PUTS 2021-S] 滚榜 学习笔记
LGP7519 [PUTS 2021-S] 滚榜 学习笔记
题意简述
有大小为 \(n\) 的数组 \(A\)、\(B\),其中 \(A\) 已给定。
定义下标 \(i\) 比下标 \(j\) 排名更高,当且仅当 \(A_i>A_j\) 或 \(A_i=A_j\land i<j\)。
现在我们以 \(b_i\) 单调不降的顺序,将 \(a_i\) 加上 \(b_i\),满足每操作一次后 \(i\) 都成为了排名最高的下标。以及 \(\sum b_i=m\)。问最终有多少种可能的下标的排名情况。
\(n\le 13\),\(m\le 500\),\(a_i\le 10^4\)。
做法解析
先考虑点朴素的东西。注意到你的计数对象并不是 \(b_i\) 的可能性而是排名情况的,所以你不妨先试着写个枚举全排列的暴力。好,那你怎么检验一个排列合不合法呢?
某个意义上来说,\(b_i\) 的数值是一种“资源”,因为对于每一个下标而言,给它的题目越多它越有可能跃到榜首(同时给后面那些下标的压力也越大),所以我们可以贪心地给每个下标分配最少的足以让它变成榜首的 \(b_i\),最后剩下的就一股脑扔给最后的榜首就行了。时间复杂度 \(O(n!\times n)\)。其实这已经可以带给你 \(\text{60pts}\) 了。哇多麽好的機會啊。
显然枚举全排列也太 \(\text{naive}\) 了。考虑状压因为省选总是有状压的。设 \(f(S,i,j)\) 表示:前 \(|S|\) 位为 \(S\) 内元素,第 \(|S|\) 位为 \(i\),已用的 \(\sum b_i\) 为 \(j\) 时的方案总数。
现在的时间复杂度就是 \(O(2^nn^2m)\) 了。可喜可贺!
代码实现
为了代码方便,下标从 \(0\) 开始。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=15,MaxM=5e2+5,MaxT=1<<13;
int N,M,alf,A[MaxN],mxi,ppc[MaxT],num[MaxT],dlt[MaxN][MaxN];
lolo F[MaxT][MaxN][MaxM],ans;
int lowbit(int x){return x&(-x);}
int main(){
readis(N,M);alf=(1<<N)-1;
for(int i=0;i<=alf;i++)ppc[i]=ppc[i>>1]+(i&1);
for(int i=0;i<N;i++){num[1<<i]=i;readi(A[i]);if(A[i]>A[mxi])mxi=i;}
for(int i=0;i<N;i++)for(int j=0;j<N;j++)dlt[i][j]=max(0,A[i]-A[j]+(j>i));
for(int i=0;i<N;i++){
int tgt=N*dlt[mxi][i];
if(tgt<=M)F[1<<i][i][tgt]=1;
}
for(int i=1;i<=alf;i++){
for(int t=i;t;t-=lowbit(t)){
int pos=num[lowbit(t)];
for(int m=0;m<=M;m++){
for(int j=0;j<N;j++){
if(i&(1<<j))continue;
int tgt=m+(N-ppc[i])*dlt[pos][j];
if(tgt<=M)F[i|(1<<j)][j][tgt]+=F[i][pos][m];
}
}
}
}
for(int i=0;i<N;i++)for(int j=0;j<=M;j++)ans+=F[alf][i][j];
writi(ans);
return 0;
}
浙公网安备 33010602011771号