Loading

[NOI2019] 斗主地

题面

题面

得分情况

100pts: 16
>40pts: 26
40pts: 159
30pts: 149

题解

10pts: \(n\leq 10\)

暴搜

30pts: \(n\leq 80\)

dp
\(f_{i,j}\)为左边\(i\)张牌,右边\(j\)张牌的概率。
讨论移左边还是移右边就行了。

参考代码:

for(int i=1;i<=m;i++){
    memset(f,0,sizeof(f));memset(h,0,sizeof(h));
    f[a[i]][n-a[i]]=1;
    for(int j=1;j<=a[i];j++) l[j]=g[j];
    for(int j=1;j<=n-a[i];j++) r[j]=g[j+a[i]];
    for(int j=a[i];~j;j--)
        for(int k=n-a[i];~k;k--){
            if(!f[j][k]) continue;
            if(j>0){
                ll tmp=f[j][k]*j%mod*inv(j+k)%mod;
                f[j-1][k]+=tmp;f[j-1][k]%=mod;
                h[n-j-k+1]+=tmp*l[j]%mod;h[n-j-k+1]%=mod;
            }
            if(k>0){
                ll tmp=f[j][k]*k%mod*inv(j+k)%mod;
                f[j][k-1]+=tmp;f[j][k-1]%=mod;
                h[n-j-k+1]+=tmp*r[k]%mod;h[n-j-k+1]%=mod;
            }
        }
    for(int j=1;j<=n;j++) g[j]=h[n-j+1];
}

40pts: 所有\(A_i\)相同

前30pts采用dp暴力做。
对于\(A_i\)全相同的10pts,算出系数,做矩乘即可。

参考代码:

#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
using namespace std;
const int N=105,MAXN=5e5+10;
const int M=998244353,mod=M;
int n,m,type,Q,f[N],A[MAXN],sc[N][N],cir[N],pw[N];
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
inline void Add(int&a,const int&b){a=a+b>=mod?a+b-mod:a+b;}
inline void Dec(int&a,const int&b){a=a>=b?a-b:a-b+mod;}
inline void Mul(int&a,const int&b){a=(ll)a*b%mod;}
inline int ftp(int b,int p){
    int r=1;
    while(p){
        if(p&1) r=mul(r,b)%M;
        b=1ll*mul(b,b)%M;
        p>>=1;
    }
    return r;
}
struct MAT{
    int mat[N][N],h,l;
    MAT(){h=l=0;memset(mat,0,sizeof mat);}
    friend MAT operator *(const MAT &a,const MAT &b){
        MAT ret;ret.h=a.h;ret.l=b.l;
        for(int i=1;i<=a.h;i++)
            for(int j=1;j<=b.l;j++)
                for(int k=1;k<=a.l;k++)
                    Add(ret.mat[i][j],mul(a.mat[i][k],b.mat[k][j]));
        return ret;
    }
    friend inline MAT operator^(MAT a,int p){
        MAT ret;ret.h=a.h;ret.l=a.l;
        for(int i=1;i<=ret.h;i++) ret.mat[i][i]=1;
        while(p){
            if(p&1) ret=ret*a;
            a=a*a;
            p>>=1;
        }
        return ret;
    }
};
bool check(){
    for(int i=1;i<=m;i++) if(A[i]!=A[1]) return 0;
    return 1;
}
void all_A_is_same(){
    int n1=A[1],n2=n-A[1];
    MAT S,B;
    B.h=1,B.l=n;for(int i=1;i<=n;i++) B.mat[1][i]=f[i];
    S.h=S.l=n;
    for(int i=n1;~i;i--) for(int j=n2;~j;j--) if(i+j>0){
        if(i+j==n1+n2) sc[i][j]=1;
        else sc[i][j]=mul(cir[i+j+1],add(mul(i^n1?i+1:0,sc[i+1][j]),mul(j^n2?j+1:0,sc[i][j+1])));
        if(i) Add(S.mat[i][i+j],mul(sc[i][j],mul(cir[i+j],i)));
        if(j) Add(S.mat[n1+j][i+j],mul(sc[i][j],mul(cir[i+j],j)));
    }
//    for(int i=1;i<=n1;i++){
//        for(int j=1;j<=n2;j++) cout<<sc[i][j]<<" ";
//        cout<<endl;
//    }

    B=B*(S^m);
    scanf("%d",&Q);
    for(int i=1,x;i<=Q;i++){
        scanf("%d",&x);
        printf("%d\n",B.mat[1][x]);
    }
}
int main(){
//    freopen(".in","r",stdin);
//    freopen("123.out","w",stdout);
    scanf("%d%d%d",&n,&m,&type);
    for(int i=1;i<=n;i++) f[i]=mul(i,type==1?1:i);
    for(int i=1;i<=m;i++) scanf("%d",&A[i]);
    cir[1]=1;for(int i=2;i<=n;i++) cir[i]=1ll*cir[M%i]*(M-M/i)%M;
    if(check()){all_A_is_same();}
    return 0;
}

100pts idea1:

\(f(i)=i\space or\space i^2\)
可以猜出结论:一次函数洗牌后的期望还是一次函数,二次函数洗牌后的期望还是二次函数
即:设洗牌前\(a_i\)\(k\)次多项式,则洗牌后\(a_i^{'}\)仍为\(k\)次多项式
这里先假设该结论成立,之后再证明。
那么设某一次洗牌后第\(i\)张(从上往下数)的期望为\(x_i^{'}\),洗牌前第\(i\)张的期望为\(x_i\)
可得:

\[x_1^{'}=\frac{A}{n}x_1+\frac{n-A}{n}x_{A+1}\\ x_n^{'}=\frac{A}{n}x_A+\frac{n-A}{n}x_n\\ x_2^{'}=\frac{A}{n}(\frac{A-1}{n-1}x_2+\frac{n-A}{n-1}x_{A+1})+\frac{n-A}{n}(\frac{A}{n-1}x_1+\frac{n-A-1}{n-1}x_{A+2}) \]

这里举\(x_1^{'}\)为例证明上述式子。
由与洗牌前后同一堆内的牌先后顺序不变,所以\(x_1^{'}\)显然由\(x_1\)\(x_{A+1}\)得来。
考虑系数怎么计算。
\(\frac{A}{n}x_1\)为例:
想象一个取数的操作序列,若要使\(x_1\)为最后取的第一张牌,那么\(x_1\)必然在该长度为\(n\)的序列的最后一个位置,那么在此之前有\(n-1\)张,与\(x_1\)来自同一堆的牌有\(A-1\)张。
那么\(x_1\)为最后一张的操作序列有\(C_{n-1}^{A-1}\)种,无限制的操作序列总共有\(C_n^A\)种,故\(x_1\)的系数为\(\frac{C_{n-1}^{A-1}}{C_n^A}=\frac{A}{n}\).
其他同理。

那么我们就得到了\(x_i\)的递推式。
同时,\(x_i\)可以表示成\(ai^2+bi+c\)的形式。
最开始,根据题目数据,\(x_i=i\space or\space i^2\),即\(a=c=0,b=1\)\(a=1,b=c=0\)
根据递推式可以得到\(x_i^{'}\),根据结论,\(x_i^{'}\)仍可以表示成\(a^{'}i^2+b^{'}i+c^{'}\)的形式,通过拉格朗日插值或待定系数解放方程可以得到新的\(a^{'}\),\(b^{'}\),\(c^{'}\)
以此类推,我们就得到了某次洗牌后期望的多项式表达式。
每次询问\(O(1)\)计算输出即可。

参考代码如下:(这里采用了拉格朗日插值求系数)

#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
using namespace std;
const ll M=998244353ll;
const int N=1e7+5;
int m,tp,Q;ll n,a,b,c,f[N];
ll ftp(ll b,ll p){
    b=(b%M+M)%M;
    ll r=1;
    while(p){
        if(p&1) r=r*b%M;
        b=b*b%M;
        p>>=1;
    }
    return r%M;
}
ll calc(ll x){
    return ((a*x%M+b)%M*x%M+c)%M;
}
int main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    scanf("%lld%d%d",&n,&m,&tp);
    if(tp==1) a=c=0,b=1;
    else a=1,b=c=0;
    ll npw=1ll*n*n%M;
    ll _=ftp(n,M-2),__=ftp(n-1,M-2),___=ftp(npw-3ll*n+2ll,M-2);
    while(m--){
        ll t,t1,t2,t3;
        scanf("%lld",&t);
        t1=(t*calc(1)%M+(n-t+M)%M*calc(t+1)%M)*_%M;
        t2=(((t-1ll+M)%M*calc(2)%M+(n-t+M)%M*calc(t+1)%M)%M*t%M+(t*calc(1)%M+(n-t-1ll+M)%M*calc(t+2)%M)%M*((n-t+M)%M)%M)%M*_%M*__%M;
        t3=(t*calc(t)%M+(n-t+M)%M*calc(n)%M)%M*_%M;
        a=(((n-2+M)%M*t1%M+(1-n+M)%M*t2%M)%M+t3)%M*___%M;
        b=(((n+2ll)*((2ll-n+M)%M)%M*t1%M+(n+1ll)*((n-1ll+M)%M)%M*t2%M)%M+(M-3ll)*t3%M)%M*___%M;
        c=(((2ll*n%M*((n-2ll+M)%M)%M)*t1%M+(1ll-n+M)%M*n%M*t2%M)%M+2ll*t3%M)%M*___%M;
    }
    for(int i=1;i<=n;i++) f[i]=(calc(i)+M)%M;
    scanf("%d",&Q);
    while(Q--){
        int x;scanf("%d",&x);
        printf("%lld\n",f[x]);
    }
    return 0;
}

对于结论:一次函数洗牌后的期望还是一次函数,二次函数洗牌后的期望还是二次函数,有一种感性且扯蛋的证明方式。

设洗牌前第\(i\)张的期望是\(a_i\),洗牌后为\(a_i^{'}\),考虑洗牌后的第\(i\)张是洗牌前的第\(j\)张的概率。
这里假设\(j<=A\),容易得到其对\(a_i^{'}\)的贡献为:

\[\frac{1}{\tbinom{n}{A}}\sum_{j=1}^Aa_j\tbinom{i-1}{j-1}\tbinom{n-i}{A-j} \]

这个式子本质上和之前推导\(x_i\)的式子相同。
由于\(k\)次下降幂多项式和\(k\)次多项式一一对应,这里将\(a_j\)代入为\((j-1)^{\underline{k}}\)
得:

\[\frac{1}{\tbinom{n}{A}}\sum_{j=1}^Aa_j\tbinom{i-1}{j-1}\tbinom{n-i}{A-j} \\ =\frac{1}{\tbinom{n}{A}}\sum_{j=1}^A(j-1)^{\underline{k}}\tbinom{i-1}{j-1}\tbinom{n-i}{A-j}\\ =\frac{1}{\tbinom{n}{A}}\sum_{j=1}^Ak!\tbinom{j-1}{k}\tbinom{i-1}{j-1}\tbinom{n-i}{A-j}\\ =\frac{k!}{\tbinom{n}{A}}\sum_{j=1}^{A}\tbinom{j-1}{k}\tbinom{i-1}{j-1}\tbinom{n-i}{A-j} \]

\(\tbinom{a}{b}\tbinom{c}{a}=\tbinom{c}{b}\tbinom{c-b}{a-b}\),令\(j-1=a,k=b,i-1=c\)得:

\[\frac{k!}{\tbinom{n}{A}}\sum_{j=1}^{A}\tbinom{j-1}{k}\tbinom{i-1}{j-1}\tbinom{n-i}{A-j}\\ =\frac{k!}{\tbinom{n}{A}}\sum_{j=1}^{A}\tbinom{i-1}{k}\tbinom{i-1-k}{j-1-k}\tbinom{n-i}{A-j}\\ =\frac{k!}{\tbinom{n}{A}}\tbinom{i-1}{k}\sum_{j}\tbinom{i-1-k}{j-1-k}\tbinom{n-i}{A-j}\\ =\frac{k!}{\tbinom{n}{A}}\tbinom{i-1}{k}\sum_{j}\tbinom{i-1-k}{j}\tbinom{n-i}{A-j-1-k} \]

(上式最后一行把\(j\)\(j+1+k\)代替)
\(\sum_{j}\tbinom{n}{j}\tbinom{m}{k-j}=\tbinom{n+m}{k}\),令\(i-1-k=n,n-i=m,A-1-k=k\),得:

\[\frac{k!}{\tbinom{n}{A}}\tbinom{i-1}{k}\sum_{j}\tbinom{i-1-k}{j}\tbinom{n-i}{A-j-1-k}\\ =\frac{k!}{\tbinom{n}{A}}\tbinom{i-1}{k}\tbinom{i-1-k+n-i}{A-1-k}\\ =\frac{k!}{\tbinom{n}{A}}\tbinom{i-1}{k}\tbinom{n-k-1}{A-k-1}\\ =k!\frac{(i-1)!}{k!(i-1-k)!}\frac{\tbinom{n-k-1}{A-k-1}}{\tbinom{n}{A}}\\ =\frac{(i-1)!}{(i-1-k)!}\frac{\tbinom{n-k-1}{A-k-1}}{\tbinom{n}{A}}\\ =(i-1)^{\underline{k}}\frac{\tbinom{n-k-1}{A-k-1}}{\tbinom{n}{A}} \]

发现当\(a_j\)\(k\)次下降幂时,这一部分也是\(k\)次下降幂,对于\(j>A\)的部分同理,所以\(a_i^{'}\)也是\(k\)次下降幂。
进一步的说,若\(a_i\)\(k\)次多项式时,\(a_i^{'}\)是不多于\(k\)次的多项式。
于是我们就证明了之前的结论。
考场上还是打表猜结论吧。

100pts idea2:

正着做有点难,考虑倒着做。
发现其逆过程就是等概率拎两个序列出来,把一个序列放在最前面。
这样的操作进行\(m\)轮,就是Base为\(2\)的基数排序。
那么\(E[i]\)就是某个数基排之后在这个数前面的概率之和;\(E[i^2]\)就是选两个数基排之后在前你前面的概率之和。
DP即可。

posted @ 2022-04-27 10:00  Cafard  阅读(40)  评论(0)    收藏  举报  来源