[USACO25OPEN] Election Queries G 题解

题目传送门

洛谷P12029

题面:P12029 [USACO25OPEN] Election Queries G

题目描述

注意:本题时间限制为 3 秒,是默认时间的 1.5 倍。

农夫约翰有 N N N 头( 2 ≤ N ≤ 2 ⋅ 10 5 2 \leq N \leq 2 \cdot 10^5 2N2105)编号从 1 1 1 N N N 的奶牛。农场正在举行选举,将选出两头新的领头牛。初始时,已知第 i i i 头奶牛会投票给第 a i a_i ai 头奶牛( 1 ≤ a i ≤ N 1 \leq a_i \leq N 1aiN)。

选举过程如下:

  1. 农夫约翰任意选择一个非空真子集 S S S(即至少包含一头牛但不包含所有牛)。
  2. S S S 集合中,得票数最多的候选牛将被选为第一头领头牛 x x x
  3. 在剩余奶牛组成的集合中,得票数最多的候选牛将被选为第二头领头牛 y y y
  4. 定义两头领头牛的差异度为 ∣ x − y ∣ |x - y| xy。若无法选出两头不同的领头牛,则差异度为 0 0 0

由于奶牛们经常改变主意,农夫约翰需要进行 Q Q Q 次( 1 ≤ Q ≤ 10 5 1 \leq Q \leq 10^5 1Q105)查询。每次查询会修改一头奶牛的投票对象,你需要回答当前状态下可能获得的最大差异度。

输入格式

第一行包含 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 ∣15∣=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 ∣52∣=3

  • 测试点 3 ∼ 4 3\sim4 34 N , Q ≤ 100 N,Q \leq 100 N,Q100
  • 测试点 5 ∼ 7 5\sim7 57 N , Q ≤ 3000 N,Q \leq 3000 N,Q3000
  • 测试点 8 ∼ 15 8\sim15 815:无额外限制。

题解

问题核心

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+cntyk=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} d2N )。

只需考虑不同的 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} 2vminmx 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(mxpmni[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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值