[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^{'}\)为例证明上述式子。
由与洗牌前后同一堆内的牌先后顺序不变,所以\(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^{'}\)的贡献为:
这个式子本质上和之前推导\(x_i\)的式子相同。
由于\(k\)次下降幂多项式和\(k\)次多项式一一对应,这里将\(a_j\)代入为\((j-1)^{\underline{k}}\)。
得:
由\(\tbinom{a}{b}\tbinom{c}{a}=\tbinom{c}{b}\tbinom{c-b}{a-b}\),令\(j-1=a,k=b,i-1=c\)得:
(上式最后一行把\(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\),得:
发现当\(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即可。

浙公网安备 33010602011771号