P4168 [Violet] 蒲公英题解

《P4168 [Violet] 蒲公英题解》

在这里插入图片描述
题目入口

一、题目概述

  1. 核心任务:给定一个长度为 (n) 的蒲公英种类序列 ({a_1, a_2, \cdots, a_n}),需要对 (m) 次询问做出回应。每次询问一个区间 ([l, r]),要找出该区间内出现次数最多的蒲公英种类编号;若多种蒲公英出现次数相同,则输出编号最小的那个。并且要求算法在线处理询问,即每次询问都要基于之前询问的结果来处理当前输入。
  2. 输入处理:输入包含 (n)(蒲公英数量)、(m)(询问次数)以及序列 ({a_i})。询问区间 ((l_0, r_0)) 是加密的,需根据上次询问结果 (x)(首次询问 (x = 0))进行解密,通过公式 (l = ((l_0 + x - 1) \bmod n) + 1) 和 (r = ((r_0 + x - 1) \bmod n) + 1) 得到真实询问区间 ([l, r]),若 (l > r),则交换 (l) 和 (r)。

二、代码思路剖析

1. 全局变量

int a[40010], b[40010];
int L[410], R[410];
int pos[40010];
int p[410][410];
int tong[40010];
int f[410][40010];
int n, m;
  • a 数组:存储输入的蒲公英种类编号。
  • b 数组:辅助离散化,在离散化过程中存储 a 数组的副本并进行排序。
  • LR 数组:记录分块后的每一块的左右边界。
  • pos 数组:记录每个位置所属的块编号。
  • p 数组:用于记录块与块之间的统计信息,方便快速查询跨块区间的结果。
  • tong 数组:临时统计蒲公英种类的出现次数。
  • f 数组:记录前缀和信息,辅助计算不同块内蒲公英种类的出现次数。
  • nm:分别表示蒲公英数量和询问次数。

2. 输入输出优化函数

int in() {
    int k = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') k = k * 10 + c - '0', c = getchar();
    return k * f;
}

void out(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x < 10) putchar(x + '0');
    else out(x / 10), putchar(x % 10 + '0');
}
  • in 函数:通过 getchar 逐字符读取输入,实现比 scanf 更快的整数输入读取,同时处理正负号。
  • out 函数:通过递归方式实现比 printf 更快的整数输出。

3. 读取输入函数

void read() {
    n = in();
    m = in();
}

使用 in 函数读取蒲公英数量 n 和询问次数 m

4. 离散化函数

void lisanhua() {
    for (int i = 1; i <= n; i++) {
        a[i] = in();
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    tot = unique(b + 1, b + n + 1) - b - 1;
    for (int i = 1; i <= n; i++) {
        a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
    }
}
  • 首先将输入的蒲公英种类编号读入 a 数组,并复制到 b 数组。
  • b 数组排序,然后用 unique 函数去除重复元素,tot 记录去重后的元素个数。
  • 通过 lower_bound 函数将 a 数组中的每个元素映射到离散化后的编号,以减小数据范围,方便后续处理。

5. 分块预处理函数

void yu() {
    int len = sqrt(n);
    int len_ll = len;
    // 分块,确定每块边界
    for (int i = 1; i <= len; i++) {
        L[i] = (i - 1) * len + 1;
        R[i] = i * len;
    }
    if (R[len] < n) {
        while (n - R[len] >= len_ll) {
            len++;
            L[len] = R[len - 1] + 1;
            R[len] = R[len - 1] + len_ll;
        }
        if (R[len] < n) {
            len++;
            L[len] =  R[len - 1] + 1;
            R[len] = n;
        }
    }
    // 记录位置所属块编号,统计块内蒲公英出现次数前缀和
    for (int i = 1; i <= len; i++) {
        for (int j = L[i]; j <= R[i]; j++) {
            pos[j] = i;
            tong[a[j]]++;
        }
        for (int j = 1; j <= tot; j++) {
            f[i][j] = f[i - 1][j] + tong[j];
        }
        for (int j = L[i]; j <= R[i]; j++) {
            tong[a[j]] = 0;
        }
    }
    // 预处理块与块之间的信息
    for (int i = 1; i <= len; i++) {
        int res = 0;
        for (int j = i; j <= len; j++) {
            for (int k = L[j]; k <= R[j]; k++) {
                tong[a[k]]++;
                if (tong[a[k]] > tong[res] || (tong[a[k]] == tong[res] && a[k] < res)) {
                    res = a[k];
                }
            }
            p[i][j] = res;
        }
        memset(tong, 0, sizeof(tong));
    }
}
  • 分块操作:根据序列长度 (n) 计算块大小 len(取 (\sqrt{n})),确定每一块的左右边界 L[i]R[i],并处理最后一块可能不足 len 大小的情况。
  • 块内预处理:遍历每一块,记录每个位置所属的块编号 pos[j],统计每块内每种蒲公英的出现次数 tong,并据此计算前缀和 f,之后将 tong 数组清零以便后续使用。
  • 块间预处理:通过两层循环遍历块与块之间的范围,统计每个块间组合内出现次数最多且编号最小的蒲公英种类编号,记录到 p 数组。

6. 询问处理函数

int take(int l, int r) {
    int l1 = pos[l], r1 = pos[r];
    if (r1 - l1 <= 1) {
        int res = 0;
        for (int i = l; i <= r; i++) {
            tong[a[i]]++;
            if (tong[a[i]] > tong[res] || (tong[a[i]] == tong[res] && res > a[i])) {
                res = a[i];
            }
        }
        for (int i = l; i <= r; i++) {
            tong[a[i]] = 0;
        }
        return res;
    } else {
        int res = 0, sum = 0;
        // 统计两端非完整块的蒲公英出现次数
        for (int i = l; i <= R[l1]; i++) {
            tong[a[i]]++;
        }
        for (int i = L[r1]; i <= r; i++) {
            tong[a[i]]++;
        }
        // 计算两端非完整块内的结果
        for (int i = l; i <= R[l1]; i++) {
            if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum) {
                sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
                res = a[i];
            } else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum) {
                res = min(res, a[i]);
            }
        }
        for (int i = L[r1]; i <= r; i++) {
            if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum) {
                sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
                res = a[i];
            } else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum) {
                res = min(res, a[i]);
            }
        }
        // 比较中间完整块的结果
        if (sum < f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ]) {
            sum = f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ];
            res = p[l1 + 1][r1 - 1];
        } else if (sum == f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ]) {
            res = min(res, p[l1 + 1][r1 - 1]);
        }
        // 清零统计数组
        for (int i = l; i <= R[l1]; i++) tong[a[i]] = 0;
        for (int i = L[r1]; i <= r; i++) tong[a[i]] = 0;
        return res;
    }
}
  • 情况一:询问区间在相邻或同一一块内:直接遍历区间内元素,用 tong 数组统计每种蒲公英出现次数,更新出现次数最多且编号最小的蒲公英种类编号 res,最后将 tong 数组清零并返回 res
  • 情况二:询问区间跨越多个块
    • 先统计左右两端非完整块内每种蒲公英的出现次数。
    • 结合前缀和数组 f,计算两端非完整块内出现次数最多且编号最小的蒲公英种类编号 res 及其出现次数 sum
    • 比较中间完整块的结果(通过 p 数组获取),若中间完整块内某种蒲公英出现次数更多或相等且编号更小,则更新 res
    • 最后将 tong 数组清零并返回 res

7. 主询问函数

void ask() {
    int ans = 0;
    while (m--) {
        int l0, r0;
        l0 = in();
        r0 = in();
        int l = (l0 + ans - 1) % n + 1;
        int r = (r0 + ans - 1) % n + 1;
        if (l > r) {
            swap(l, r);
        }
        ans = b[take(l, r)];
        out(ans);
        putchar('\n');
    }
}
  • 循环处理 (m) 次询问,每次读取加密的询问区间 ((l_0, r_0))。
  • 根据上次询问结果 ans 解密得到真实询问区间 ([l, r]),若 (l > r) 则交换它们。
  • 调用 take 函数获取询问区间内出现次数最多且编号最小的蒲公英种类编号,将其还原为离散化前的编号(通过 b 数组),输出结果并换行。

8. 主函数

int main() {
    read();
    lisanhua();
    yu();
    ask();
    return 0; 
}

依次调用 read 函数读取输入,lisanhua 函数进行离散化,yu 函数进行分块预处理,ask 函数处理询问并输出结果。

三、总体代码

#include <bits/stdc++.h>

using namespace std;

int a[40010], b[40010];
int L[410], R[410];
int pos[40010];
int p[410][410];
int tong[40010];
int f[410][40010];
int n, m;

int in()
{
    int k=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
    return k*f;
}

void out(int x)
{
    if(x<0)putchar('-'),x=-x;
    if(x<10)putchar(x+'0');
    else out(x/10),putchar(x%10+'0');
}

void read()
{
	n = in();
	m = in();
}

int tot;

void lisanhua()
{
	for (int i = 1; i <= n; i++)
	{
		a[i] = in();
		b[i] = a[i];
	}
	sort(b + 1, b + n + 1);
	tot = unique(b + 1, b + n + 1) - b - 1;
	for (int i = 1; i <= n; i++)
	{
		a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
	}
}

void yu()
{
	int len = sqrt(n);
	int len_ll = len;
	for (int i = 1; i <= len; i++)
	{
		L[i] = (i - 1) * len + 1;
		R[i] = i * len;
	}
	if (R[len] < n)
	{
		while (n - R[len] >= len_ll)
		{
			len++;
			L[len] = R[len - 1] + 1;
			R[len] = R[len - 1] + len_ll;
		}
		if (R[len] < n)
		{
			len++;
			L[len] =  R[len - 1] + 1;
			R[len] = n;
		}
		
	}
	for (int i = 1; i <= len; i++)
	{
		for (int j = L[i]; j <= R[i]; j++)
		{
			pos[j] = i;
			tong[a[j]]++;
		}
		for (int j = 1; j <= tot; j++)
		{
			f[i][j] = f[i - 1][j] + tong[j];
		}
		for (int j = L[i]; j <= R[i]; j++)
		{
			tong[a[j]] = 0;
		}
	}
	for (int i = 1; i <= len; i++)
	{
		int res = 0;
		for (int j = i; j <= len; j++)
		{
			for (int k = L[j]; k <= R[j]; k++)
			{
				tong[a[k]]++;
				if (tong[a[k]] > tong[res] || (tong[a[k]] == tong[res] && a[k] < res))
				{
					res = a[k];
				}
			}
			p[i][j] = res;
		}
		memset(tong, 0, sizeof(tong));
	}
}

int take(int l, int r)
{
	int l1 = pos[l], r1 = pos[r];
	if (r1 - l1 <= 1)
	{
		int res = 0;
		for (int i = l; i <= r; i++)
		{
			tong[a[i]]++;
			if (tong[a[i]] > tong[res] || (tong[a[i]] == tong[res] && res > a[i]))
			{
				res = a[i];
			}
		}
		for (int i = l; i <= r; i++)
		{
			tong[a[i]] = 0;
		}
		return res;
	}
	else
	{
		int res = 0, sum = 0;
		for (int i = l; i <= R[l1]; i++)
		{
			tong[a[i]]++;
		}
		for (int i = L[r1]; i <= r; i++)
		{
			tong[a[i]]++;
		}
		for (int i = l; i <= R[l1]; i++)
		{
			if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum)
			{
				sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
				res = a[i];
			}
			else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum)
			{
				res = min(res, a[i]);
			}
		}
		for (int i = L[r1]; i <= r; i++)
		{
			if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] > sum)
			{
				sum = tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]];
				res = a[i];
			}
			else if (tong[a[i]] + f[r1 - 1][a[i]] - f[l1][a[i]] == sum)
			{
				res = min(res, a[i]);
			}
		}
		if (sum < f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ])
		{
			sum = f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ];
			res = p[l1 + 1][r1 - 1];
		}
		else if (sum == f[r1 - 1][ p[l1 + 1][r1 - 1] ] - f[l1][ p[l1 + 1][r1 - 1] ])
		{
			res = min(res, p[l1 + 1][r1 - 1]);
		}
		for (int i = l; i <= R[l1]; i++) tong[a[i]] = 0;
		for (int i = L[r1]; i <= r; i++) tong[a[i]] = 0;
		return res;
	}
}

void ask()
{
	int ans = 0;
	while(m--)
	{
		int l0, r0;
		l0 = in();
		r0 = in();
		int l = (l0 + ans - 1) % n + 1;
		int r = (r0 + ans - 1) % n + 1;
		if (l > r)
		{
			swap(l, r);
		}
		ans = b[take(l, r)];
		out(ans);
		putchar('\n');
	}
}

int main()
{
	read();
	lisanhua();
	yu();
	ask();
	return 0; 
}

注:以防大家看着代码不便,我把代码格式改了一下

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值