LGP7519 [PUTS 2021-S] 滚榜 学习笔记

LGP7519 [PUTS 2021-S] 滚榜 学习笔记

Luogu Link

题意简述

有大小为 \(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;
}
posted @ 2025-07-25 11:34  矞龙OrinLoong  阅读(7)  评论(0)    收藏  举报