莫队算法讲解及例题

莫队的思想

对于区间查询数字出现次数的问题,我们可以使用莫队。
比如,当询问区间是 1 1 1~ n n n时,可以通过 O ( N ) O(N) O(N)的复杂度解决。
那接下来再询问 2 2 2~ n n n或者 1 1 1~ n n n − - 1 1 1时,就可以通过 O ( 1 ) O(1) O(1)的复杂度解决这个询问。
所以,当有m个询问的时候,我们就可以根据第 i i i个询问的结果推出第 i i i + + + 1 1 1个询问的结果。这样就不用每一次询问都用 O ( N ) O(N) O(N)去跑了。
但是这可以被卡:

1 1
n n
1 1
n n
……

于是就产生了优化版:对左端点进行排序。
但是这依旧可以被卡:

1 n
2 1
3 n
4 1
……

所以我们就可以对左端点进行分块(后面发文章讲)
2025年3月7日签到:分块文章已发布—>链接
经过
精密的计算
,可以得出每个块大小设为 n \sqrt{n} n 时,可以使时间复杂度最优。
说多了也没用,来看道例题:

例题

1.小B的询问(模版题)

题目传送门
莫队采用的是离线操作。
所以我们先用一个数组 q q q记录询问的区间。
接着,我们对 q q q数组按一下规则进行排序。

bool cmp(node a,node b){
	return (a.l/s)<(b.l/s)||(a.l/s==b.l/s&&a.r<b.r);
}

先按照块排序,在同一块内则按右端点排序。
接着来看计算的地方
( x + 1 ) 2 = x 2 + 2 x + 1 (x+1)^2=x^2+2x+1 (x+1)2=x2+2x+1,这是一个众所周知的等式。
所以,当 c i c_i ci的值增加 1 1 1时,总数应该增加 2 ∗ c i + 1 2*c_i+1 2ci+1
同理,当 c i c_i ci的值减少 1 1 1时,总数应该减少 2 ∗ c i − 1 2*c_i-1 2ci1(注意和增加时不同)
我们用数组 b b b记录元素出现的次数,那么……
增加

void add(int x){
	ans+=2*(b[x]++)+1;
}

减少

void del(int x){
	ans-=2*(b[x]--)-1;
}

接着就是从当前询问转到下一个询问的事情:

//上一个区间比当前区间大,缩小区间范围
while(l1<f[i].l)del(a[l1++]);
while(r1>f[i].r)del(a[r1--]);
//上一个区间比当前区间小,扩大区间范围
while(l1>f[i].l)add(a[--l1]);
while(r1<f[i].r)add(a[++r1]);
answer[f[i].id]=ans;//存储答案,注意不是answer[i]

到这里,核心代码就完了

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,k;
int a[N];
struct node{
	int l,r,id;
}f[N];
int b[N];
int s;
bool cmp(node a,node b){
	return (a.l/s)<(b.l/s)||(a.l/s==b.l/s&&a.r<b.r);
}
int ans;
void add(int x){
	ans+=2*(b[x]++)+1;
}
void del(int x){
	ans-=2*(b[x]--)-1;
}
int answer[N];
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	s=sqrt(n);
	for(int i=1;i<=m;i++){
		cin>>f[i].l>>f[i].r;
		f[i].id=i;
	}
	sort(f+1,f+1+m,cmp);
	int l1=1,r1=0;
	for(int i=1;i<=m;i++){
		while(l1<f[i].l)del(a[l1++]);
		while(r1>f[i].r)del(a[r1--]);
		while(l1>f[i].l)add(a[--l1]);
		while(r1<f[i].r)add(a[++r1]);
		answer[f[i].id]=ans;
	}
	for(int i=1;i<=m;i++)cout<<answer[i]<<'\n';
}
### EM算法的原理详解 EM算法(期望最大化算法)是一种迭代算法,主要用于含有隐变量的概率参数模型的最大似然估计或极大后验概率估计。其核心思想是通过不断求解对数似然函数下界的极大化来逼近求解对数似然函数的极大化[^1]。 在EM算法中,隐变量的引入可以简化问题,将复杂的概率模型转换为更易处理的形式。具体来说,隐变量的作用在于将难以直接处理的问题分解成两个步骤:E步和M步[^4]。 #### E步(Expection-Step) 在E步中,根据当前的参数估计,计算隐变量的条件期望。这一过程通常涉及计算隐变量的后验分布,并基于此分布得到目标函数的期望值。 #### M步(Maximization-Step) 在M步中,使用E步计算出的期望,优化模型参数以使得目标函数达到最大化。这一步通常通过求导并令其等于零来找到最优参数[^2]。 由于EM算法保证每次迭代之后对数似然函数都会增加,因此该算法最终会收敛到一个稳定点。然而,需要注意的是,EM算法找到的通常是局部最优解,而非全局最优解,且最终解与初始值的选择密切相关[^3]。 --- ### 示例题讲解 假设我们有一个硬币抛掷实验,其中包含两枚硬币A和B,每枚硬币的正面出现概率分别为\( \theta_A \)和\( \theta_B \)。我们进行了若干次实验,但并未记录每次实验使用的是哪一枚硬币。以下是具体的实验数据: | 实验编号 | 抛掷结果(H表示正面,T表示反面) | |----------|-----------------------------------| | 1 | HHHHTTTTTT | | 2 | HHHHHHHHTT | | 3 | HHHHHHHH | | 4 | HTTTT | | 5 | HHHTTTTTT | 我们需要通过EM算法估计两枚硬币正面出现的概率\( \theta_A \)和\( \theta_B \)。 #### 初始设定 假设初始值为: \[ \theta_A^{(0)} = 0.6, \quad \theta_B^{(0)} = 0.5 \] #### 迭代过程 **E步**:对于每个实验,计算该实验由硬币A或硬币B生成的概率。例如,对于第一个实验“HHHHTTTTTT”,我们可以计算: \[ P(\text{实验由A生成}) = \frac{P(\text{实验}|\text{A})P(A)}{P(\text{实验}|\text{A})P(A) + P(\text{实验}|\text{B})P(B)} \] 其中: \[ P(\text{实验}|\text{A}) = \theta_A^{(i)}{}^{\text{正面次数}} (1-\theta_A^{(i)})^{\text{反面次数}} \] **M步**:更新参数\( \theta_A \)和\( \theta_B \)。例如,对于\( \theta_A \),可以通过以下公式更新: \[ \theta_A^{(i+1)} = \frac{\sum_{j=1}^N P(\text{实验j由A生成}) \cdot \text{实验j正面次数}}{\sum_{j=1}^N P(\text{实验j由A生成}) \cdot \text{实验j总次数}} \] 重复上述E步和M步直到参数收敛。 --- ### Python代码实现 ```python import numpy as np # 初始化参数 theta_A = 0.6 theta_B = 0.5 data = ["HHHHTTTTTT", "HHHHHHHHTT", "HHHHHHHH", "HTTTT", "HHHTTTTTT"] def em_algorithm(data, theta_A, theta_B, iterations=10): for _ in range(iterations): # E步:计算每个实验属于硬币A或B的概率 counts_A = np.zeros(len(data)) counts_B = np.zeros(len(data)) for i, experiment in enumerate(data): heads = experiment.count('H') tails = len(experiment) - heads likelihood_A = theta_A**heads * (1-theta_A)**tails likelihood_B = theta_B**heads * (1-theta_B)**tails prob_A = likelihood_A / (likelihood_A + likelihood_B) prob_B = likelihood_B / (likelihood_A + likelihood_B) counts_A[i] = prob_A * heads counts_B[i] = prob_B * heads # M步:更新参数 total_heads_A = sum(counts_A) total_heads_B = sum(counts_B) total_flips_A = sum([p*len(d) for p, d in zip(counts_A/sum(counts_A), data)]) total_flips_B = sum([p*len(d) for p, d in zip(counts_B/sum(counts_B), data)]) theta_A = total_heads_A / total_flips_A theta_B = total_heads_B / total_flips_B return theta_A, theta_B theta_A_final, theta_B_final = em_algorithm(data, theta_A, theta_B) print(f"Final theta_A: {theta_A_final}, Final theta_B: {theta_B_final}") ``` --- ### 总结 EM算法通过交替执行E步和M步,逐步逼近目标函数的最优解。尽管其收敛性得到了理论保障,但由于其局部最优特性,实际应用中需要谨慎选择初始值[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值