开篇碎碎念
好久没有更博客了(咸鱼瘫瘫),到现在还欠了最近的几场cf没补题(呜呜呜欠债upup),由于一道很显然的期望没有开出来所以最近补了几道期望,来总结一下
友情指路:sshwy的期望 洛谷题单
相关基础概念
-
首先是概率:常用 P(X) 表示X发生的概率,等价于X发生的可能性在全部事件的占比。
-
条件概率:用 P(B|A) 表示在A发生的情况下B发生的概率,等于AB同时发生在A事件发生的占比,也就是 P(B|A) = P(AB) / P(A)
-
独立事件:两个事件互不影响,满足P(AB)=P(A)P(B),E(AB)=E(A)E(B)
-
期望:用于描述随机变量的平均值或预期值。对于离散型随机变量,期望值 E(X) 的计算公式为:E(X) = Σ x * P(X = x)其中,x 表示随机变量可能取的值,P(X = x) 表示随机变量取值为 x 的概率。对于连续型随机变量,期望值 E(X) 的计算公式为:E(X) = ∫ x * f(x) dx 其中,f(x) 为随机变量的概率密度函数。
-
期望的线性:对于任意两个随机变量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;
}