文章目录
题目传送门
题面:P12029 [USACO25OPEN] Election Queries G
题目描述
注意:本题时间限制为 3 秒,是默认时间的 1.5 倍。
农夫约翰有 N N N 头( 2 ≤ N ≤ 2 ⋅ 10 5 2 \leq N \leq 2 \cdot 10^5 2≤N≤2⋅105)编号从 1 1 1 到 N N N 的奶牛。农场正在举行选举,将选出两头新的领头牛。初始时,已知第 i i i 头奶牛会投票给第 a i a_i ai 头奶牛( 1 ≤ a i ≤ N 1 \leq a_i \leq N 1≤ai≤N)。
选举过程如下:
- 农夫约翰任意选择一个非空真子集 S S S(即至少包含一头牛但不包含所有牛)。
- 在 S S S 集合中,得票数最多的候选牛将被选为第一头领头牛 x x x。
- 在剩余奶牛组成的集合中,得票数最多的候选牛将被选为第二头领头牛 y y y。
- 定义两头领头牛的差异度为 ∣ x − y ∣ |x - y| ∣x−y∣。若无法选出两头不同的领头牛,则差异度为 0 0 0。
由于奶牛们经常改变主意,农夫约翰需要进行 Q Q Q 次( 1 ≤ Q ≤ 10 5 1 \leq Q \leq 10^5 1≤Q≤105)查询。每次查询会修改一头奶牛的投票对象,你需要回答当前状态下可能获得的最大差异度。
输入格式
第一行包含 N N N 和 Q Q Q。
第二行包含初始投票数组 a 1 , a 2 , … , a N a_1, a_2, \ldots, a_N a1,a2,…,aN。
接下来 Q Q Q 行,每行两个整数 i i i 和 x x x,表示将 a i a_i ai 修改为 x x x。
输出格式
输出 Q Q Q 行,第 i i i 行表示前 i i i 次查询后的最大可能差异度。
输入输出样例 #1
输入 #1
5 3
1 2 3 4 5
3 4
1 2
5 2
输出 #1
4
3
2
输入输出样例 #2
输入 #2
8 5
8 1 4 2 5 4 2 3
7 4
8 4
4 1
5 8
8 4
输出 #2
4
4
4
7
7
说明/提示
样例一解释:
第一次查询后, a = [ 1 , 2 , 4 , 4 , 5 ] a = [1,2,4,4,5] a=[1,2,4,4,5]。选择 S = { 1 , 3 } S = \{1,3\} S={1,3} 时:
- S S S 中:牛 1 1 1 得 1 1 1 票,牛 4 4 4 得 1 1 1 票 → \to → 可选择牛 1 1 1 或牛 4 4 4 作为第一头领头牛。
- 剩余牛中:牛 2 , 4 , 5 2,4,5 2,4,5 各得 1 1 1 票 → \to → 可选择牛 2 , 4 , 5 2,4,5 2,4,5 作为第二头领头牛。
最大差异度为 ∣ 1 − 5 ∣ = 4 |1-5| = 4 ∣1−5∣=4。
第二次查询后, a = [ 2 , 2 , 4 , 4 , 5 ] a = [2,2,4,4,5] a=[2,2,4,4,5]。选择 S = { 4 , 5 } S = \{4,5\} S={4,5} 时:
- S S S 中:牛 4 4 4 得 1 1 1 票,牛 5 5 5 得 1 1 1 票。
- 剩余牛中:牛 2 2 2 得 2 2 2 票。
最大差异度为 ∣ 5 − 2 ∣ = 3 |5-2| = 3 ∣5−2∣=3。
- 测试点 3 ∼ 4 3\sim4 3∼4: N , Q ≤ 100 N,Q \leq 100 N,Q≤100。
- 测试点 5 ∼ 7 5\sim7 5∼7: N , Q ≤ 3000 N,Q \leq 3000 N,Q≤3000。
- 测试点 8 ∼ 15 8\sim15 8∼15:无额外限制。
题解
问题核心
记
cnt
i
\text{cnt}_\text{i}
cnti为奶牛
i
i
i获得的票数,则若可以使奶牛
x
x
x,
y
y
y当选,显然应当将所有投票给
x
x
x的奶牛放入集合
S
\text S
S中,将投票给
y
y
y的奶牛归到“剩余牛”中。
那么,为了保证奶牛
x
x
x的票数
cnt
x
\text{cnt}_\text{x}
cntx为集合
S
\text S
S的众数,显然在
S
\text S
S里投给奶牛
z
z
z的票数
cnt
z
S
\text{cnt}_{\text{z}_\text S}
cntzS不可以超过
cnt
x
\text{cnt}_\text{x}
cntx。同理该奶牛
z
z
z在剩余奶牛中获得的票数不能超过
cnt
y
\text{cnt}_\text{y}
cnty。
因此,对于任意奶牛
z
z
z,其票数
cnt
z
=
cnt
z
S
+
cnt
z
S在全体奶牛中的补集
≤
cnt
x
+
cnt
y
\text{cnt}_\text{z}=\text{cnt}_{\text{z}_\text S}+\text{cnt}_{\text{z}_\text {S在全体奶牛中的补集}}\leq\text{cnt}_\text{x}+\text{cnt}_\text{y}
cntz=cntzS+cntzS在全体奶牛中的补集≤cntx+cnty
由此推广到全体奶牛,即
∀
z
∈
{
1
,
2
,
⋯
,
n
}
,
\forall z\in\lbrace 1,2,\cdots,n\rbrace,
∀z∈{1,2,⋯,n},我们都有上式,所以可得
⇒
cnt
x
+
cnt
y
≥
max
k
=
1
n
cnt
k
\huge \Rightarrow \text{cnt}_\text{x}+\text{cnt}_\text{y}\ge\max_{k=1}^n\text{cnt}_\text{k}
⇒cntx+cnty≥k=1maxncntk
思路
枚举候选对:
直接枚举所有 ( x , y ) (x, y) (x,y) 对不可行( O ( N 2 ) O(N^2) O(N2))。利用性质:
不同 cnt \text{cnt} cnt 值的种类数 d = O ( N ) d = O(\sqrt{N}) d=O(N)(因为 ∑ cnt [ i ] = N \sum \text{cnt}[i] = N ∑cnt[i]=N,最坏情况 cnt \text{cnt} cnt 值互异时 d ≈ 2 N d \approx \sqrt{2N} d≈2N)。
只需考虑不同的 cnt \text{cnt} cnt 值,而非每个奶牛索引。
维护数据结构:
st [ v ] \text{st}[v] st[v]:set 存储得票数为 v v v 的奶牛索引。
mxi [ v ] \text{mxi}[v] mxi[v] 和 mni [ v ] \text{mni}[v] mni[v]:得票数为 v v v 的奶牛索引的最大值和最小值。
S S S:全局 set 存储存在的非零票数值(用于快速访问不同 cnt \text{cnt} cnt)。
更新投票:
修改投票时,更新 cnt \text{cnt} cnt 数组和上述数据结构(删除旧票数对应索引,添加新票数对应索引)。
计算最大差异度:
特判:
若最小票数 v min v_{\min} vmin 满足 2 v min ≥ mx 2v_{\min} \geq \text{mx} 2vmin≥mx( mx = max k cnt [ k ] \text{mx} = \max_k \text{cnt}[k] mx=maxkcnt[k]),则用 mxi [ v min ] − mni [ v min ] \text{mxi}[v_{\min}] - \text{mni}[v_{\min}] mxi[vmin]−mni[vmin] 更新答案(同票数奶牛索引差)。
双指针遍历 S S S:
指针 itl 从小到大遍历 S S S(小票数)。
指针 itr 从大到小移动(大票数),维护累积变量 mxp(满足 cnt [ x ] + cnt [ y ] ≥ mx \text{cnt}[x] + \text{cnt}[y] \geq \text{mx} cnt[x]+cnt[y]≥mx 的奶牛索引最大值)和 mnp(索引最小值)。
对于每个 itl,更新答案 max ( mxp − mni [ itl ] , mxi [ itl ] − mnp ) \max(\text{mxp} - \text{mni}[\text{itl}], \text{mxi}[\text{itl}] - \text{mnp}) max(mxp−mni[itl],mxi[itl]−mnp),覆盖 x x x 小 y y y 大或 x x x 大 y y y 小的情况。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, q;
int a[N]; // a[i]: 奶牛i投给的奶牛的编号
int cnt[N]; // cnt[i]: 奶牛i获得的票数
int mni[N]; // mni[x]: 票数为x的奶牛编号最小值
int mxi[N]; // mxi[x]: 票数为x的奶牛编号最大值
set<int> id[N]; // id[x]: 统计所有票数为x的奶牛编号
set<int> S; // S: 维护所有存在的票数值
// i 的得票变成 x 了
void addc(int i, int x)
{
if (!x) return; // 若此奶牛无票则不用处理
id[x].insert(i); // 票数为x的奶牛多了奶牛i这头
// 更新票数为x的奶牛坐标最值
mxi[x] = max(mxi[x], i);
mni[x] = min(mni[x], i);
// 若x为此票数第一个,则还需更新S
if (id[x].size() == 1) S.insert(x);
}
// i 的得票不再是 x 了
void delc(int i, int x)
{
if (!x) return; // 若此奶牛无票则不需要处理
// 票数为x的奶牛里不再有奶牛i了
id[x].erase(i);
// 如果这种票数的奶牛没了
if (id[x].empty()) {
S.erase(x); // 维护 S
mxi[x] = 0; // 重置最大值至0
mni[x] = n + 1; // 重置最小值至无限大
}
// 如果这种奶牛的票数还有,则根据set更新最大最小
else {
mxi[x] = *(--id[x].end());
mni[x] = *id[x].begin();
}
}
signed main()
{
freopen("election.in", "r", stdin);
freopen("election.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
++cnt[a[i]];
mxi[i] = 0;
mni[i] = n + 1;
}
for (int i = 1; i <= n; ++i) addc(i, cnt[i]);
while (q--) {
int i;
cin >> i;
/*
修改过程分为2×2步:
I: 处理a[i]->a[i]-1的过程(奶牛i原来投给的奶牛)
I.1 把a[i]的信息删除
I.2 添加a[i]-1的信息
II: 处理a[i]'->a[i]'+1的过程(奶牛i更改后投给的奶牛)
II.1 把a[i]'的信息删除
II.2 添加a[i]'+1的信息
*/
delc(a[i], cnt[a[i]]--);
addc(a[i], cnt[a[i]]);
cin >> a[i];
delc(a[i], cnt[a[i]]++);
addc(a[i], cnt[a[i]]);
// 最难懂的,也是题解描述最含糊的滑动数组原理
int maxvote = *(--S.end()); // 最大票数值
int mxidx = 0, mnidx = n + 1; // 可以与当前奶牛匹配的最大与最小奶牛编号
int ans = 0; // 最大差异
/* 入选要求:cnt[x]+cnt[y]>=最大票数值 */
// 特判最小票数值满足入选要求
if (*S.begin() + *S.begin() >= maxvote)
ans = mxi[*S.begin()] - mni[*S.begin()]; // 注意这里后式不可能是负的,不需要max
// 双指针
set<int>::iterator itl = S.begin(); // 从小到大遍历票数值
set<int>::iterator itr = --S.end(); // 从大到小遍历票数值
for (; itl != S.end(); ++itl) {
/*
FAQ: itr会不断减小,每种票数值只遍历一次,会不会影响后续?
ANS: 不会。因为随着itl变大,必然保证它能与前面的itl'符合入选条件的itr匹配,
因此可以直接不断更新mx/mn idx
*/
while (itr != S.begin() && (*itr) + (*itl) >= maxvote) { // 符合入选条件
// 更新可以和票数值为itl的奶牛匹配的奶牛的编号最值
mxidx = max(mxidx, mxi[*itr]);
mnidx = min(mnidx, mni[*itr]);
--itr;
}
ans = max(ans, max(mxidx - mni[*itl], mxi[*itl] - mnidx));
}
// 输出最大差值
cout << ans << '\n';
}
return 0;
}

被折叠的 条评论
为什么被折叠?



