[省选联考 2021 A/B 卷] 滚榜 题解

[省选联考 2021 A/B 卷] 滚榜
看见数据范围 \(n\le 13\),马上就想到状压了,最初的想法是设 \(f_{s,i,k,j}\) 表示已经公布了队伍的集合为 \(s\),最新一个公布的是 \(i\),已经用了的 \(b_i\) 总和为 \(k\),上一次用的 \(b_i\) 为 \(j\),这样子转移必须带一个 \(O(m^2)\)。
毕竟题目问的是有几种可能的最终排名,和 \(b\) 没有关系,所以直接来考虑最优的分配 \(b\) 的方式,假设最终的排名序列已经确定为从低到高 \(p_{1},p_{2},p_{3}...p_{n}\),那么每一个 \(a_{p_i} < a_{p_{i-1}}\),就要给它的 \(b_i\) 加上 \(a_{p_{i-1}}-a_{p_i}\)。当然若编号更大的话还要多加一,还要注意新加上 \(b_i\) 的数要大于原榜上的第一名。这样子最后总和小于等于 \(m\) 的话就是合法的排名,小于的话加在最后一个就行了,我们可以用全排列初步实现一下,复杂度 \(O(n!n)\),可以拿六十分。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m;
const int N = (1<<8),M=505;
int a[15],p[15];
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    int id=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(a[i]>a[id])id=i;
        p[i]=i;
    }
    ll ans=0;
    do{
        int sum=0,lst=0;
        for(int i=1;i<=n;i++){
            if(a[p[i]]>=a[p[i-1]]){
                if(a[p[i]]==a[p[i-1]]&&p[i]>p[i-1]&&p[i-1]!=0)lst++;
            }else{
                int x=a[p[i-1]]-a[p[i]];
                lst+=x;
                if(p[i]>p[i-1]&&p[i-1])lst++;
            }
            if(a[p[i]]+lst<a[id]&&p[i]!=id){
                lst+=a[id]-a[p[i]]-lst;
            }
            if(a[p[i]]+lst==a[id]&&p[i]>id)lst++;
            sum+=lst;
            //cout<<lst<<" ";
            if(sum>m)break;
        }
        //cout<<endl;
        if(sum<=m)ans++;
    }while(next_permutation(p+1,p+1+n));
    cout<<ans;
    return 0;
}

回到状压的想法上来,为了为避免 \(m^2\) 的枚举,我们必须这样子考虑,一个数的 \(b_i\) 被确定后,它就会给后面的每一个数都产生贡献,即 \((n-p_i)*b_i\),那我们就考虑提前把这个贡献算了,设 \(f_{s,i,k}\) 表示已经公布的集合为 \(s\),上一个是 \(i\),已经产生了 \(k\) 的总贡献。转移比较简单,复杂度 \(O(2^nn^2m)\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m;
const int N = (1<<13),M=505;
int a[15],p[15],id[N|1];
ll f[N+10][15][M],ans;
int lowbit(int x){
    return x&(-x);
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    int id=0;a[0]=-1;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(a[i]>a[id])id=i;
        id[1<<(i-1)]=i;
    }
    for(int i=1;i<=n;i++){
        int x=n*(a[id]-a[i]+(id<i));
        if(x<=m)f[1<<(i-1)][i][x]=1;
    }
    for(int s=1;s<(1<<n);s++){
        int px=popcount(s);
        for(int t=s;t;t-=lowbit(t)){
            for(int k=0;k<=m;k++){
                int j=lowbit(t);
                for(int i=1;i<=n;i++){
                    if((s>>(i-1))&1)continue;
                    int sum=k+(n-px)*max(0,a[i]-a[j]+(j<i));
                    if(sum<=m)f[s|(1<<(i-1))][i][sum]+=f[s][j][k];
                }
            }
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            ans+=f[(1<<n)-1][i][j];
        }
    }
    cout<<ans;
    return 0;
}


posted @ 2026-08-21 19:27  farenara_sayowell  阅读(17)  评论(0)    收藏  举报