P5972 [PA 2019] Desant 解题报告

P5972 [PA 2019] Desant 解题报告

题目描述

题目描述

分析

想到设 \(f_{i,S}\) 为长度为 \(i\) 且选择情况为S的最小逆序对个数,这样的 \(O(n2^n)\) 的朴素dp是简单的。但是这太不优秀了。

注意到转移时我们不是关注“谁大于 \(i\)”而是“有多少个被选的数大于 \(i\)”。因此我们考虑在 \(i\) 时,记 \(a_{i+1},a_{i+2},...,a_n\) 升序排序后剩下的数列为 \(x_{i+1},x_{i+2},...,x_n\),那么本质上我们只需要知道区间\((x_{i+1},x_{i+2}),(x_{i+2},x_{i+3})...(x_n,n]\)内分别有多少数。

所以设 \(f_{i,S}\) 为长度为 \(i\)\(S\) 表示上述区间内的数的个数情况(怎么存之后说)时的逆序对个数,转移是简单的:

如果不选,就有 f[i+1][S]=min(f[i+1][S],f[i][S])

如果选,就有 f[i+(统计S中大于a[i]的个数)][S+(i对S的贡献)]=min(f[i+(统计S中大于a[i]的个数)][S+(i对S的贡献)],f[i][S])

但是 \(S\) 的数量看起来非常多啊。注意在 \(i\) 时到 \(S\) 的总量为个区间长度 \(+1\) 的乘积。由柯西不等式可以得知,当且仅当区间长度全部相同时,\(S\) 取到最大值,那么我们可以粗略地估计 \(S\) 的总量不超过 \(n*x^{\frac{n}{x}}\),其中 \(x\) 为区间长度。

考虑计算这东西的上限:因为 \(n\) 是常数,所以我们只需要求 \(x^{\frac{1}{x}}\) 就可以了。因为 \(x\) 非负,所以可以取ln,即求 \(\ln x^{\frac{1}{x}} = \frac{\ln x}{x}\) 。再简单的求导,有:

\[(\frac{\ln x}{x})'=\frac{1-\ln x}{x^2} \]

容易发现当 \(x=e\) 时,总量最大。因为 \(x\) 是离散的,所以当 \(x=3\) 时,总量最大(这里取 \(3\) 还是取 \(2\) 可以考虑做积分,但是我不会)。

因此复杂度上限为 \(O(n3^{\frac{n}{3}})\),当然,如果你比较严谨,那你也可以表示为 \(\Large O(\sum \limits_{i=1}^n(\frac{n}{n-i+1}+1)^{n-i+1})\)。总之就是 \(O(\text{能过})\)

但是新的问题随之而来,怎么存储 \(S\) 呢?这就需要科技——变进制状态存储

当我们需要存储一个 \(n\) 维向量,且第 \(i\) 维的值域为 \([0,a_i]\)。那么对于向量 \((c_1,c_2,...,c_n)\) 可以被表示为 \(\large \sum \limits_{i=1}^n (c_i \times \prod \limits_{j=1}^{i-1} a_i-1)\)

实现看代码

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Inf (1ll<<60)
#define For(i,s,t) for(int i=s;i<=t;++i)
#define Down(i,s,t) for(int i=s;i>=t;--i)
//#define ls (i<<1)
//#define rs (i<<1|1)
#define lowbit(x) ((x)&(-(x)))
#define End {printf("NO\n");exit(0);}
using namespace std;
typedef long long ll;
typedef pair<int,ll> pii;
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline ll min(ll x,ll y){return x<y?x:y;}
inline ll max(ll x,ll y){return x>y?x:y;}
inline int read(){
    register int x=0,f=1;
    char c=getchar();
    while(c<'0' || '9'<c) f=(c=='-')?-1:1,c=getchar();
    while('0'<=c && c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
    return x*f;
}
void write(int x){
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
const int N=50;
//b[i]存储i后有那些数 d[i]表示i后有多少数
int n,a[N],cnt[N],b[N][N],mul[N][N],k[N],K[N];
//下标是序列长度,第一位是最小值,第二位是序列数
vector<pii> f,g;
//计算当前状态对应值
int calc(int i){
    int s=0;
    For(j,1,cnt[i]) s+=K[j]*mul[i][j];
    return s;
}
void update(pii &x,pii y){
    if(x.first>y.first)
        x=y;
    else if(x.first==y.first)
        x.second+=y.second;
}
int main()
{
    //freopen("test.in","r",stdin);
    //freopen("test.out","w",stdout);
    n=read();
    For(i,1,n) a[i]=read();
    //预处理变进制状态存储
    For(i,0,n){//0也要处理,不然1转移不了
        For(j,i+1,n) b[i][++cnt[i]]=a[j];
        sort(b[i]+1,b[i]+cnt[i]+1,less<int>());
        b[i][++cnt[i]]=n+1;
        int tot=1;
        For(j,1,cnt[i]) mul[i][j]=tot,tot*=b[i][j]-b[i][j-1];
        mul[i][cnt[i]+1]=tot;
    }
    f={{0,1}};
    For(i,1,n){
        g=vector<pii>(mul[i][cnt[i]+1],{inf,0});
        int nw=find(b[i-1]+1,b[i-1]+n+1,a[i])-b[i-1],S=0,sz=f.size()-1,delta;
        For(id,0,sz){
            int x=S;
            //计算出i-1时S对应状态
            Down(j,cnt[i-1],1) k[j]=x/mul[i-1][j],x=x%mul[i-1][j];
            //由S在i-1时的状态转移到在i时的状态
            delta=0;
            For(j,0,cnt[i]) K[j]=0;
            For(j,1,nw) K[j]=k[j];
            For(j,nw+1,cnt[i-1]) K[j-1]+=k[j],delta+=k[j];
            //不选当前数
            update(g[calc(i)],make_pair(f[id].first,f[id].second));
            //选当前数
            K[nw]++;
            update(g[calc(i)],make_pair(f[id].first+delta,f[id].second));
            S++;
        }
        swap(f,g);
    }
    For(i,1,n) printf("%lld %lld\n",f[i].first,f[i].second);
    return 0;
}

参考资料

masterhuang的题解

posted @ 2025-07-24 20:20  XiaoZi_qwq  阅读(5)  评论(0)    收藏  举报