QOJ.ac

QOJ

Type: Editorial

Status: Open

Posted by: tangzirui1016

Posted at:

Last updated:

Back to Problem

QOJ1180 题解:期望与观察

题目链接:The Halfwitters
题目来源:神人模拟赛 B 题
定位:区分题

题意

三种操作:花费代价 $a$ 交换两个相邻位置,花费代价 $b$ 翻转整个排列,花费代价 $c$ 使得当前排列等概率变成任意排列。
$T$ 组数据,每次给定 $n,a,b,c$,然后有 $q$ 组询问一个排列 $p_i$,问变成上升排列的最优期望值。$T,q\le 10^5,n\le 16$。

思路

一般的操作流程是要么只用操作一,要么翻转一次后用操作一,所以这些操作只和排列的逆序对数有关系,所以可以把排列压缩成逆序对。

然后可以写出一个 dp(设 $dp(i)$ 表示将逆序对为 $i$ 的排列变成上升的最优期望),边界 $dp(0)=0$,其余的为 $dp(i)=min(dp(i-1)+a,dp(\frac{n(n-1)}{2}-i)+b,\sum_{j=0}^{\frac{n(n-1)}{2}}dp(j)P(j)+c)$,$P(j)$ 表示逆序对为 $j$ 的排列出现的概率。

似乎这个 dp 不能直接做,因为转移有环。但是我们可以做出以下观察:对于一些逆序对比较少的排列,只可能用操作一;对于一些逆序对比较多的,只会用操作二后用操作一;然后之间一段连续的部分,直接让它随机选,直到跳出中间的区间。

所以每次询问我们可以分两种情况: - 要么当前逆序对不再中间段,直接算; - 否则枚举这个左右边界,然后中间段的期望都是一样的,可以写出一个方程: $E_{mid}=P_{mid}E_{mid}+E_{left}+E_{right}+c$,解得 $E_{mid}=\frac{E_{left}+E_{right}+c}{1-P{mid}}$,然后对于所有的端点取 min 即可,单次询问 $O(n^4)$。

然后就是一些无聊的优化技巧,上面的式子可以改写成 $E_{mid}=\frac{E_{left}+E_{right}+c}{P_{left}+P_{right}}$。把 $(P_{left},E_{left})$ 和 $(P_{right},E_{right})$ 看做一个点,那么就是要求最小化 $\frac{Y+c}{X}$,可以看作是最小化 $(0,c)$ 到 $(X,Y)$ 的斜率,说明最优的点一定在下凸包上。所以我们可以先对 left,right 分别求出下凸包,然后再用Minkowski 和合并两个凸包(实现上就是按照斜率归并排序),这些都可以做到线性,所以单次查询复杂度为 $O(n^2)$。但是实际上 $O(n^4)$ 的常数很小,可以通过所有数据。

代码

#include<bits/stdc++.h>
#define fi first
#define se second
#define pll pair<long long,long long>
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long ll;
const int N=16;
const ll INF=(ll)1e14;
int t,n,m,q;
int p[N],cnt[N];
ll a,b,c;
ll f[N][N*N],val[N*N];
ll preX[N*N],preY[N*N],sufX[N*N],sufY[N*N];
void calc(){
    f[0][0]=1;
    for(int i=1;i<N;i++){
        for(int lst=0;lst<i*i;lst++){
            for(int j=0;j<=i;j++) f[i][lst+j]+=f[i-1][lst];
        }
    }
}
bool operator< (pll a,pll b){ //???С?? 
    if(a.se<0) a.fi=-a.fi,a.se=-a.se;
    if(b.se<0) b.fi=-b.fi,b.se=-b.se;
    if(b.se==0) return true;  
    return a.fi*1.0/a.se<b.fi*1.0/b.se; 
}
pll operator+ (pll a,pll b){
    return {a.fi*b.se+b.fi*a.se,a.se*b.se};
}
pll Min(pll a,pll b){
    return a<b?a:b;
}
pll mul(pll a,pll b){return {a.fi*b.fi,a.se*b.se};}
pll div(pll a,pll b){
    assert(b.fi!=0 && b.se!=0);
    return mul(a,{b.se,b.fi});
}
pll yue(pll a){
    if(a.se<0) a.fi=-a.fi,a.se=-a.se;
    ll d=__gcd(a.fi,a.se);
    a.fi/=d,a.se/=d;
    return a;
}
int top;
pll stk[N*N];
vector<pll>A,B;
double slope(pll a,pll b){
    if(a.fi==b.fi) return 1e9;
    return (a.se-b.se)*1.0/(a.fi-b.fi);
}
void solve(){
    ll sum=0,res=0;
    cin>>n>>a>>b>>c>>q;
    m=n*(n-1)/2;
    for(int i=0;i<=m;i++) val[i]=min(a*i,b+(m-i)*a),sum+=f[n-1][i];
    preX[0]=f[n-1][0],preY[0]=val[0]*f[n-1][0];
    for(int i=1;i<=m;i++) preX[i]=preX[i-1]+f[n-1][i],preY[i]=preY[i-1]+val[i]*f[n-1][i];
    sufX[m]=f[n-1][m],sufY[m]=val[m]*f[n-1][m];
    for(int i=m-1;i>=0;i--) sufX[i]=sufX[i+1]+f[n-1][i],sufY[i]=sufY[i+1]+val[i]*f[n-1][i]; 
    for(int Turn=1;Turn<=q;Turn++){
        for(int i=0;i<n;i++) cin>>p[i];
        int tot=0; 
        for(int i=0;i<n;i++){
            for(int j=p[i];j<n;j++) tot+=cnt[j];
            cnt[p[i]-1]++;
        } 
        pll ans={val[tot],1};
        stk[top=1]={0,0};
        for(int l=0;l<tot;l++){
            pll now={preX[l],preY[l]};
            while(top>=2 && slope(stk[top],now)<slope(stk[top-1],stk[top])) top--;
            stk[++top]=now;
        }
        A.resize(top);
        for(int i=1;i<=top;i++) A[i-1]=stk[i];
        stk[top=1]={0,0};
        for(int r=m;r>tot;r--){
            pll now={sufX[r],sufY[r]};
            while(top>=2 && slope(stk[top],now)<slope(stk[top-1],stk[top])) top--;
            stk[++top]=now;
        }
        B.resize(top);
        for(int i=1;i<=top;i++) B[i-1]=stk[i];
        int posA=0,posB=0;
        while(posA+1<A.size() || posB+1<B.size()){
            if(posA==A.size()-1) posB++;
            else if(posB==B.size()-1) posA++;
            else if(slope(A[posA],A[posA+1])<slope(B[posB],B[posB+1])) posA++;
            else posB++;
            ans=Min(ans,{c*sum+A[posA].se+B[posB].se,A[posA].fi+B[posB].fi});
        }
        A.clear(),B.clear();
        ans=yue(ans);
        cout<<ans.fi<<'/'<<ans.se<<'\n';
        for(int i=0;i<n;i++) cnt[i]=0;
    }
    for(int i=0;i<=m;i++) val[i]=0;
} 
int main(){
    freopen("perm.in","r",stdin);
    freopen("perm.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    calc();
    cin>>t;
    while(t--) solve();
    return 0;
}

总结

==本题考察了对期望的一些理解和数学能力,以及结论观察进而优化,难度大致在 NOIP T2 到 T3。==

后记

莫忘来时路,不负少年心。

Comments

No comments yet.