【数学】概率&期望小总结

Posted on 2024-03-07 23:48  木易meow  阅读(37)  评论(0编辑  收藏  举报

开篇碎碎念

好久没有更博客了(咸鱼瘫瘫),到现在还欠了最近的几场cf没补题(呜呜呜欠债upup),由于一道很显然的期望没有开出来所以最近补了几道期望,来总结一下
友情指路:sshwy的期望 洛谷题单

相关基础概念

  1. 首先是概率:常用 P(X) 表示X发生的概率,等价于X发生的可能性在全部事件的占比。

  2. 条件概率:用 P(B|A) 表示在A发生的情况下B发生的概率,等于AB同时发生在A事件发生的占比,也就是 P(B|A) = P(AB) / P(A)

  3. 独立事件:两个事件互不影响,满足P(AB)=P(A)P(B),E(AB)=E(A)E(B)

  4. 期望:用于描述随机变量的平均值或预期值。对于离散型随机变量,期望值 E(X) 的计算公式为:E(X) = Σ x * P(X = x)其中,x 表示随机变量可能取的值,P(X = x) 表示随机变量取值为 x 的概率。对于连续型随机变量,期望值 E(X) 的计算公式为:E(X) = ∫ x * f(x) dx 其中,f(x) 为随机变量的概率密度函数。

  5. 期望的线性:对于任意两个随机变量X,Y,E(X+Y)=E(X)+E(Y)。

一些黄绿蓝题目

1. 红包发红包

是个少见的连续型随机变量,但是积分式子很简单。化简之后写个快速幂就好啦。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
void sol(){
	int w,n,k;
	cin>>w>>n>>k;
	int fm=qpow(2,k);
	cout<<w*qpow(fm,mod-2)%mod<<endl;
}
signed main(){
	int t;
	t=1;
	while(t--){
		sol();
	}
	return 0;
}

2. 游走

· 最一开始想的是求出长度为1、2、3...的路径的占比,结果发现没推出来,回顾了一下期望的定义:用于描述随机变量的平均值或预期值 发现:平均不就是总和除以个数嘛,所以这个题目最后的求解就是 总路程长/总条数。

· 用len[i]表示以i为结尾的路径总长度,cnt[i]表示以i为结束的总路径数量。
· 看到有向无环图考虑拓扑排序。
· 路径长度的变化可以想象为原先到u的路延申到v,所以易得len[v]=len[v]+len[u]+cnt[u];路径条数等于自身的和延申过来的,即cnt[v]=cnt[u]+cnt[v];

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
const int mod=998244353;
#define int long long
vector<int>e[MAXN];
int tot[MAXN],len[MAXN],cnt[MAXN];
int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
void sol(){
	int n,m,u,v;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>u>>v;
		tot[v]++;
		e[u].push_back(v);
	}
	queue<int>q;
	for(int i=1;i<=n;i++){
		cnt[i]=1;
		if(tot[i]==0){
			q.push(i);
		}
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(auto v:e[u]){
			len[v]=(len[v]+len[u]+cnt[u])%mod;
			cnt[v]=(cnt[u]+cnt[v])%mod;
			tot[v]--;
			if(tot[v]==0){
				q.push(v);
			}
		}
	}
	int tlen=0,tcnt=0;
	for(int i=1;i<=n;i++){
		tlen=(tlen+len[i])%mod;
		tcnt=(tcnt+cnt[i])%mod;
	}
	// cerr<<tlen<<' '<<tcnt<<endl;
	int ans=tlen%mod;
	int inv=qpow(tcnt,mod-2);
	ans=ans*inv%mod;
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	t=1;
	while(t--){
		sol();
	}
	return 0;
}

3.错选单位

· 刚开始看到前面国家集训队的title慌了一下下(不管,咱就是刚!大不了就是WA一发嘛),于是开一开试试。
· 这个和前面不同,同样是期望,这里用到的是期望的线性性,也就是每个题做对的概率求和,本以为是n个1/a相加,突然发现不同题目的可选项不同...
· 每个题作对的概率为 前一个题的答案和这个题相同,总共有 a[i] * a[i-1]种答案组合,而相同的话只有min(a[i],a[i-1])种。逐个求解并相加即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
// #define int long long
const int MAXN=1e7+10;
typedef long long ll;
int a[MAXN];
void sol(){
	int n,A,B,C;
	scanf("%d%d%d%d%d", &n, &A, &B, &C, a + 1);
	for (int i = 2; i <= n; i++)
		a[i] = ((long long) a[i - 1] * A + B) % 100000001;
	for (int i = 1; i <= n; i++)
		a[i] = a[i] % C + 1;
	if(n==1){
		cout<<"1.000"<<endl;
		return ;
	}
	double ans=0;
	ll k;
	for(int i=1;i<n;i++){
		k=(ll)a[i]*a[i+1];
		ans=ans+min(a[i],a[i+1])*1.0/(k*1.0);
	}
	k=(ll)a[1]*a[n];
	ans=ans+min(a[n],a[1])*1.0/(k*1.0);
	printf("%.3lf",ans);
}
int main(){
	int t;
	t=1;
	while(t--){
		sol();
	}
	return 0;
}

4. 绿豆蛙的归宿

· 好!有向无环图!拓扑启动!!!好叭还是先看眼题目(((
· 问的是从起点走到终点的路径总长度期望,这里是用的到终点的每条路的长度x发生概率进行求和。
· 对于一条 u->v 的边来说,假设u的出边总共有tot[u]条,那么就有 1/tot[u] 的概率走向v 走到u的路程长度期望为dp[u],那么这一部分给v的贡献就是 dp[v]+=(len[u->v]+dp[u])*(1/tot[u])

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
int tot[MAXN],k[MAXN];
double dp[MAXN];
vector<pair<int,int> >e[MAXN];
void sol(){
	int n,m;
	cin>>n>>m;
	int u,v,w;
	for(int i=1;i<=m;i++){
		cin>>u>>v>>w;
		e[v].push_back({u,w});
		tot[u]++;
	}
	for(int i=1;i<=n;i++){
		k[i]=tot[i];
	}
	queue<int>q;
	q.push(n);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(auto i:e[u]){
			int v=i.first,w=i.second;
			dp[v]=(1.0*dp[v])+(1.0/k[v])*(dp[u]+w*1.0);
			tot[v]--;
			if(tot[v]==0){
				q.push(v);
			}
		}
	}
	printf("%.2lf",dp[1]);
}
signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	t=1;
	while(t--){
		sol();
	}
	return 0;
}

有一个小憨憨n个点m条边 循环了n次加边获得了愉快的10pts

5. OSU_easy OSU!

· OSU背景的题目)))看得我都想找找然后去玩了(大雾特雾)有的是得分为极大长度的平方,有的是立方,有的是更改了每个点击的成功概率,总的来说没有太太太大的差别qwq 所以就打包一起丢在这里好啦。
· 首先的话我们通常分析的都是每一个对结果的影响,而这里给出的是每一段长度的影响,所以我们通过做差法来求解每新增击中一个的贡献。
· 发现长度对于答案也有影响,所以我们也要记录长度的期望变化

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=3e5+10;
#define double long double
double ans[MAXN];
void sol(){
	int n;
	cin>>n;
	string s;
	cin>>s;
	s=' '+s;
	double len=0;
	for(int i=1;i<=n;i++){
		if(s[i]=='o'){
			ans[i]=ans[i-1]+2*len+1;
			len++;
		}
		else if(s[i]=='x'){
			ans[i]=ans[i-1];
			len=0;
		}
		else{
			ans[i]=(ans[i-1]+2*len+1+ans[i-1])*0.5;
			len++;
			len/=2;
		}
		// cerr<<i<<' '<<ans[i]<<endl;
	}
	printf("%.4Lf",ans[n]);
}
signed main(){
	int t;
	t=1;
	while(t--){
		sol();
	}
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=2e5+10;
int dp[MAXN][2];
double p[MAXN],s[MAXN],s2[MAXN];
double ans[MAXN];
void sol(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>p[i];
	}
	for(int i=1;i<=n;i++){
		s[i]=(s[i-1]+1)*p[i];
		s2[i]=(s2[i-1]+2*s[i-1]+1)*p[i];
		ans[i]=ans[i-1]+(3*s2[i-1]+3*s[i-1]+1)*p[i];
	}
	printf("%.1lf",ans[n]);
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	t=1;
	while(t--){
		sol();
	}
} 

6. 优惠券Coupons 百事世界杯之旅 收集邮票 FAVDICE

· 本组题目建议开题顺序 4 1&2 3
· 其实题目抽象出来的话大致相同,区别一个是输出的方式,另一个是次数的代价

· 先看前三个,对于每一抽,只有两种结果 要不就是抽到过的,要不就是没抽到过的
· 期望转移方程由于终点的已知性,部分是已经 i...还需要...这一类的表示(指路 李煜东的回答)所以这里我们定义的dp数组就是dp[i]表示已经抽到了i张的情况下凑齐n张的期望。
· 在抽到i张的情况下,有i/n的概率抽到已有的,有(n-i)/n的概率抽到没有的。所以可以得到dp[i]=(i/n)xdp[i]+[(n-i)/n]xdp[i+1]+1;不要忘记加上本次抽的次数作为贡献加进来喔
· 1和2的题目输出比较别致,这里我的处理的话就是每次分别记录了分子和分母,然后模拟分数加法的过程进行通分相加。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4;
double dp[MAXN];
void sol(){
	int n;
	cin>>n;
	if(n==1){
		cout<<"1.00"<<endl;
		return ;
	}
	for(int i=n-1;i>=0;i--){
		dp[i]=dp[i+1]+1.0*n/(1.0*(n-i));
	}
	printf("%.2lf",dp[0]);
}
signed main(){
	int t;
	cin>>t;
	while(t--){
		sol();
	}
	return 0;
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=2e5+10;
double f[MAXN],g[MAXN]; 
int gcd(int a,int b){
	if(b==0){
		return a;
	}
	else return gcd(b,a%b);
}
void sol(int n){ 
	int fz=0,fm=1,gd;
	for(int i=n-1;i>=0;i--){
		f[i]=f[i+1]+1.0*n/(1.0*(n-i));
		fz=fz*(n-i)+fm*n;
		fm=fm*(n-i);
		gd=gcd(fz,fm);
		fz/=gd;fm/=gd;
	}
	if(fz%fm==0)cout<<fz/fm<<endl;
	else {
		int it=fz/fm,cnt=0;
		while(it){
			it/=10;
			cnt++;
		}
		int fzz=fz%fm;
		int cntt=0;
		while(fzz){
			fzz/=10;
			cntt++;
		}
		int cnttt=0;
		int fmm=fm;
		while(fmm){
			cnttt++;
			fmm/=10;
		}
		cntt=max(cntt,cnttt);
		for(int i=1;i<=cnt;i++){
			cout<<' ';
		}
		cout<<' ';
		cout<<fz%fm<<endl;
		cout<<fz/fm<<' ';
		for(int i=1;i<=cntt;i++){
			cout<<'-';
		}
		cout<<endl;
		for(int i=1;i<=cnt;i++){
			cout<<' ';
		}
		cout<<' '<<fm<<endl;
	}
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	// t=1;
	while(cin>>t){
		sol(t);
	}
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=2e5+10;
double f[MAXN],g[MAXN]; 
int gcd(int a,int b){
	if(b==0){
		return a;
	}
	else return gcd(b,a%b);
}
void sol(){
    int n;cin>>n;
	int fz=0,fm=1,gd;
	for(int i=n-1;i>=0;i--){
		f[i]=f[i+1]+1.0*n/(1.0*(n-i));
		fz=fz*(n-i)+fm*n;
		fm=fm*(n-i);
		gd=gcd(fz,fm);
		fz/=gd;fm/=gd;
	}
	if(fz%fm==0)cout<<fz/fm<<endl;
	else {
		int it=fz/fm,cnt=0;
		while(it){
			it/=10;
			cnt++;
		}
		int fzz=fz%fm;
		int cntt=0;
		while(fzz){
			fzz/=10;
			cntt++;
		}
		int cnttt=0;
		int fmm=fm;
		while(fmm){
			cnttt++;
			fmm/=10;
		}
		cntt=max(cntt,cnttt);
		for(int i=1;i<=cnt;i++){
			cout<<' ';
		}
		cout<<fz%fm<<endl;
		cout<<fz/fm;
		for(int i=1;i<=cntt;i++){
			cout<<'-';
		}
		cout<<endl;
		for(int i=1;i<=cnt;i++){
			cout<<' ';
		}
		cout<<fm<<endl;
	}
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	t=1;
	while(t--){
		sol();
	}
}

· 然后再来说最后一个收集邮票这里的麻烦在于所以皮皮购买第 k 次邮票需要支付 k 元钱 所以我们还需要处理一下我们究竟是抽了几次,这里我开了两个数组分别表示次数和总花费
· f[i]表示已经抽到i张牌,抽完n张牌还需要的期望次数;g[i]表示已经抽到i张牌,抽完n张还需要的期望花费

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=2e5+10;
double f[MAXN],g[MAXN]; 
void sol(){ 
	int n;
	cin>>n;
	for(int i=n-1;i>=0;i--){
		f[i]=f[i+1]+1.0*n/(1.0*(n-i));
		g[i]=g[i+1]+f[i+1]+(1.0*i)/(1.0*(n-i))*f[i]+(1.0*n)/(1.0*(n-i));
	}
	printf("%.2lf",g[0]);
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	t=1;
	while(t--){
		sol();
	}
}

7.换教室

· 其实开始dp之后我就发现自己有个小小的问题(((,在遇到洛谷板子需要优化的最长公共子序列之后,有时候就不敢升维( 然后这个问题在这里表现得淋漓尽致
· 观察这个题目发现有几个要素分别是,第几节课、换了几次课、是否换成功。所以很显然是一个dp[n][m][2]的三维dp(好叭实际上就是两维半hhh)
· 那就很显然啦,分别表示我们进行到第几节课,目前已经换了多少次课,和这节课是否申请更换 时候的最少体力消耗
dp[i][j][0]=min(dp[i-1][j][1]+P[i-1]xdis1+(1-P[i-1])xdis2,dp[i-1][j][0]+dis3)
dp[i][j][1]=min(dp[i-1][j-1][0]+p1xdis1+(1.0-p1)xdis2,dp[i-1][j-1][1]+p1xp2xdis3+p2(1.0-p1)dis4+(1.0-p2)xp1xdis1+(1.0-p1)x(1.0-p2)xdis2))
· 至于最短路的话,反正就三百个教室,floyd/dij都行吧

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=305;
const int MAXF=2005;
const int inf=1e16;
vector<pair<int,int> >e[MAXN];
int dis[MAXN][MAXN];
double dp[MAXF][MAXF][2];
int cl[MAXF][2];
double P[MAXF];
int n,m,c,k;
void dij(){
	int vis[MAXN];
	for(int i=1;i<=c;i++){
		dis[i][i]=0;
		for(int j=1;j<=c;j++){
			vis[j]=1;
		}
		int cur=i;
		while(vis[cur]){
			// cerr<<cur<<endl;
			vis[cur]=0;
			for(auto k:e[cur]){
				int v=k.first,w=k.second;
				dis[i][v]=min(dis[i][v],dis[i][cur]+w);
			}
			int mi=inf;
			for(int j=1;j<=c;j++){
				if(vis[j] && dis[i][j]<mi){
					mi=dis[i][j];
					cur=j;
				}
			}
		}
	}
}
void sol(){
	cin>>n>>m>>c>>k;
	for(int i=1;i<=c;i++){
		for(int j=1;j<=c;j++){
			dis[i][j]=inf;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			dp[i][j][0]=inf,dp[i][j][1]=inf;
		}
	}
	dp[1][0][0]=dp[1][1][1]=0;
	for(int i=1;i<=n;i++){
		cin>>cl[i][0];
	}
	for(int i=1;i<=n;i++){
		cin>>cl[i][1];
	}
	for(int i=1;i<=n;i++){
		cin>>P[i];
	}
	int u,v,w;
	for(int i=1;i<=k;i++){
		cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	dij();
	// cerr<<dis[1][2]<<endl;
	for(int i=1;i<=n;i++){
		dp[i][0][0]=dp[i-1][0][0]+dis[cl[i-1][0]][cl[i][0]];
		for(int j=1;j<=min(m,i);j++){
			int ls1=cl[i-1][0],ls2=cl[i-1][1],c1=cl[i][0],c2=cl[i][1];
			double p=P[i-1],pp=P[i];
			dp[i][j][0]=dp[i-1][j][0]+dis[ls1][c1];
			dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][1]+p*dis[ls2][c1]+(1.0-p)*dis[ls1][c1]);
			dp[i][j][1]=dp[i-1][j-1][0]+pp*dis[ls1][c2]+(1.0-pp)*dis[ls1][c1];
			dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][1]+p*pp*dis[ls2][c2]+p*(1.0-pp)*dis[ls2][c1]+(1.0-p)*pp*dis[ls1][c2]+(1.0-pp)*(1.0-p)*dis[ls1][c1]);
		}
	}
	double ans=inf;
	for(int i=0;i<=m;i++){
		ans=min(ans,dp[n][i][0]);
		ans=min(ans,dp[n][i][1]);
	}
	printf("%.2lf",ans);
}
signed main(){
	int t;
	t=1;
	while(t--){
		sol();
	}
	return 0;
}

8. Little_Pony_and_Expected_Maximum

· 其实感觉像是个排列组合题bushi
· 考虑最大值的情况,最大值为1的情况下每个骰子都只能是1,最大值为2的话那么就是每个骰子都不能超过2然后减去都不到2的情况...同理,最大值为i的概率为 P (每个骰子都不超过i) - P(每个骰子都不到i)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=2e5+10;
void sol(){
	int n,m;
	cin>>m>>n;
	double ans=0;
	double p=pow(1.0/m,n);
	ans=p;
	for(int i=2;i<=m;i++){
		// cerr<<pow(i*1.0/m,n)-pow((i-1)*1.0/m,n)<<endl;
		double pp=pow(i*1.0/m,n);
		ans+=i*(pp-p);
		p=pp;
	}
	printf("%.5lf\n",ans);
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
	// cin>>t;
	t=1;
	while(t--){
		sol();
	}
}

9. JXOI游戏

· 组合数学+期望,其实是当时写组合数学的时候开的这个题
· 考虑检查了什么样得办公室之后才会全部都开始认真工作,由于是当i被通知的时候每个i的倍数都会被通知,所以有且仅有所有的基本数(没有非本身的因子在这个范围内的) 所以就需要关注最后一个基本数什么时候被检查、
· 基本数的筛取:参考线性筛质数
· 假如总共有k个基本数,则t(p)min=k,t(p)max=n;当t(p)=k+i时,等价于从前k+i-1个位置里面挑选k-1个位置放基本数,然后把所有可以放基本数的位置提取出来,在这k个位置里面放基本数,然后剩下n-k个位置里面放非基本数,都是全排列

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e7+1;
const int p=1e9+7;
int vis[MAXN];
int inv[MAXN];
int jc[MAXN];
int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}
int calc(int a,int b){
	if(b>a)return 0;
	return jc[a]*inv[a-b]%p*inv[b]%p;
}
int cala(int a,int b){
	return jc[a]*inv[a-b]%p;
}
signed main(){
	int l,r;
	cin>>l>>r;
	jc[0]=1;
	for(int i=1;i<=r;i++){
		jc[i]=jc[i-1]*i%p;
	}
	inv[r]=qpow(jc[r],p-2);
	for(int i=r-1;i>=0;i--){
		inv[i]=inv[i+1]*(i+1)%p;
	}
	int cnt=0;
	for(int i=l;i<=r;i++){
		if(!vis[i]){
			cnt++;
			for(int j=i<<1;j<=r;j+=i){
				vis[j]=1;
			}
		}
	}
	int n=r-l+1,ans=0;
	for(int k=cnt;k<=n;k++){
		ans+=calc(k-1,cnt-1)*cala(cnt,cnt)%p*cala(n-cnt,n-cnt)%p*k%p;
		ans%=p;
	}
	cout<<ans<<endl;
	return 0;
}

10. bag_of_mice

· 很典型的一个概率dp,最初是从 b站董晓算法 那里看到的这个题,然后开了开,写了写
· 其实就是按轮次模拟过程,进行分类讨论。A和B各进行一次并且跑出来一只老鼠算作一轮

点击查看代码
#include<iostream>
using namespace std;
const int MAXN=1005;
double f[MAXN][MAXN];
int main(){
	int w,b;
	cin>>w>>b;
	for(int i=1;i<=w;i++){
		f[i][0]=1;
	}
	for(int j=1;j<=b;j++){
		f[0][j]=0;
	}
	for(int i=1;i<=w;i++){
		for(int j=1;j<=b;j++){
			f[i][j]+=(double)i/(i+j);
			if(j>=2 && i>=1){
				f[i][j]+=(double)j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2)*f[i-1][j-2];
			}
			if(j>=3){
				f[i][j]+=(double)j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2)*f[i][j-3];
			}
		}
	}
	printf("%.9lf",f[w][b]);
} 

11. Vasya_and_Magic_Matrix

· 矩阵有权值,只能移动到严格小于当前点的点,那么就能知道终点有哪些,第一反应是根据移动的性质进行连边,发现光建图时间可能就不够,遂止。
然后发现每一个点能到的地方都是已知的,根据权值进行排序,进行转移,假如对于第i个点有tot个权值小于i的,那么这个点能出发的就是 1/tot x 距离平方和,距离可以进行化简,发现可以前缀和。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
const int MAXN=1e6+10;
struct node{
	int a,x,y;
}ND[MAXN];
bool cmp(struct node x,struct node y){
	return x.a<y.a;
}
int xf[MAXN],yf[MAXN],xx[MAXN],yy[MAXN];
int ans[MAXN],ff[MAXN];
int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int gcd(int x,int y){
	if(y==0)return x;
	else return gcd(y,x%y);
}
void sol(){
	int n,m;
	int wx,wy;
	cin>>n>>m;
	int k,cnt=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>k;
			ND[++cnt].a=k;
			ND[cnt].x=i;
			ND[cnt].y=j;
		}
	}
	cin>>wx>>wy;
	sort(ND+1,ND+cnt+1,cmp);
	for(int i=1;i<=cnt;i++){
		int x=ND[i].x,y=ND[i].y;
		xx[i]=(xx[i-1]+x)%mod;
		xf[i]=(xf[i-1]+x*x)%mod;
		yy[i]=(yy[i-1]+y)%mod;
		yf[i]=(yf[i-1]+y*y)%mod;
	}
	int lst=0;
	for(int i=1;i<=cnt;i++){
		if(ND[i].a!=ND[i-1].a){
			lst=i-1;
		}
		int x=ND[i].x,y=ND[i].y;
		ans[i]=(ans[i]+xf[lst])%mod;
		ans[i]=(ans[i]-2*xx[lst]*x%mod+mod)%mod;
		ans[i]=(ans[i]+yf[lst])%mod;
		ans[i]=(ans[i]+lst*x%mod*x%mod)%mod;
		ans[i]=(ans[i]-2*yy[lst]*y%mod+mod)%mod;
		ans[i]=(ans[i]+lst*y*y%mod)%mod;
		ans[i]=(ans[i]+ff[lst])%mod;
		ans[i]=ans[i]*qpow(lst,mod-2)%mod;
		ff[i]=(ff[i-1]+ans[i])%mod;
		if(ND[i].x==wx && ND[i].y==wy){
			cout<<ans[i]<<endl;
			return ;
		}
	}
}
signed main(){
	int t;
	t=1;
	while(t--){
		sol();
	}
	return 0;
}