题解:P5012 水の数列
user100566 · · 题解
题目分析
给定一个长度为
定义区间数
将
Num 中所有\le x 的数标记后,连续标记的区间的数量(\text{cnt}(x) )与这些区间的长度的平方和(\text{score}(x) )。下文称这样的区间为标记连续段。
有 -1 -1。
解题思路
我们发现
那么如何对所有
显然考虑满足
初始化
由于
考虑当前从
由于标记没有先后顺序之分,我们先考虑只给某个
-
-
+ 原 $\text{score}(x)=\dots+S^2 - 新
\text{score}(x)=\dots+(S+1)^2=\dots+S^2+(1+2\times S)
- 新
-
+ 原 $\text{score}(x)=\dots+{S_1}^2+{S_2}^2 - 新
\text{score}(x)=\dots+(S_1+1+S_2)^2=\text{score}(x)=\dots+{S_1}^2+{S_2}^2+(1+2\times S_1+2\times (S_1+1)\times S_2)
- 新
不失一般性,我们可以将上述过程理解为以下等价步骤:
- 在
Num_i 处新建一个长度为1 的标记连续段,\text{cnt}(x), \text{score}(x) 均增大1 。 - 将
Num_{i-1} 所在的标记连续段(如果存在)与Num_i 所在的标记连续段合并。 - 将
Num_{i+1} 所在的标记连续段(如果存在)与Num_i 所在的标记连续段合并。
每次合并两个长度分别为
上述算法需要快速查询某个位置所在标记连续段的大小,并合并两个标记连续端,这实际上就是一个加权并查集的操作,而且大小
根据题意可知,所有对答案有贡献的
当然,也需要快速知道
定义二元组
需要注意可能有多个
枚举到某个
处理询问
有多种方式可以处理本题的 RMQ 询问,这里以 ST 算法为例说明。
设上面的预处理得到了
注意,一个长度为
定义
但是本题目空间限制较严,有以下方式可以减少空间使用:
- 极限化
dp 的长度。
前面提到A 的可能下标范围为[0, 500000] ,因此dp 第一维长度只需要定义500001 。
因为2^{18}=262144, 2\times262144=524288>500000 ,因此第二维的可能下标为[0, 18] ,第二维长度只需要定义19 。 -
如果我们直接存 $\text{score}(x), x$,前者需要一个 $\text{int32}$,后者需要一个 $\text{int64}$,总计需要 $12B$ 的大小,但是我们可以只存最优解的**映射**,例如在 $A$ 中的下标(显然最优解必然存在于 $A$ 中),那么只需要 $4B$ 的大小即可。上面对 $B$ 排序也可以采用同样的方法优化。
注意询问区间可能越界,前面提到
下面的代码维护了一个 max_set,表示可能的最多标记连续段数
无解对应的状态应该严格小于任何有效解,一种方法是定义无解为
比较问题
题目中说"如果有多解则输出选择的数最大的一组",但是测试数据似乎并没有体现这一点,严谨起见我们考虑这一点,以下是关于这一点的 hack 数据:
输入
3 1
1 4 9
0 0 1 1
答案
4 4
1 1 0
比较的第一关键词为
比较第一关键词时,有以下比较方法:
- 直接用浮点数存储分数结果,注意引入
\text{eps} 来正确判断浮点数相等。 - 将除法比较转换为乘法比较,例如
[\frac{\text{score}(x)}{x}<\frac{\text{score}(x')}{x'}]=[\text{score(x)}\times x'<\text{score}(x)\times x] 。
注意运算过程中是否需要强制类型转换,例如计算浮点数除法结果时,需要先转为浮点数。
注意第一关键词相等时要比较第二关键词。
题目特色
我们已经完成了题目的主要问题,但是接下来的题目特色部分坑点较多,这里展开阐述。
l = (a * lastans + x - 1) % n + 1;
r = (b * lastans + y - 1) % n + 1;
if (l > r) swap(l, r);
其中
其中 a * lastans, b * lastans 可能超出
- 用
\text{int128} (__int128)存储这一步的中间结果。 - 根据模运算的性质,计算时先对
lastans 取模N 。 - 在更新
lastans 时,直接对lastans 取模N ,即lastans=(\text{score}(x)\times x)\mod N ,这样做在输出lastans \mod N 时就可以直接输出lastans 。
代码
AC 记录
云剪贴板存档
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long int64;
constexpr int MAX_N = 1000000;
constexpr int MAX_NUM = 1000000;
constexpr int MAX_STSIZE = 18;
int n, T, Num[MAX_N+1];
struct stu1{
int x;
int64 score;
// 无解情况,即默认的 score/x 需要小于 1/MAX_NUM
// 这里令 score 的默认值为 0 表示无解状态
inline stu1(int x_ = 1, int64 score_ = 0) : x(x_), score(score_){};
inline friend bool operator<(const stu1& a, const stu1& b){
int64 tmp1 = a.score*b.x, tmp2 = b.score*a.x;
// 如果有多解则输出选择的数最大的一组
return (tmp1==tmp2 && a.x<b.x) || tmp1<tmp2;
};
inline void update(const stu1& other){ if(*this<other) *this = other; };
inline bool valid()const{ return score>0; };
} A[(MAX_N+1)/2+1];
int max_set; // 最多标记连续端数,用于对 r 剪枝
int Log2[(MAX_N+1)/2]; // 用于 O(1) ST 查询
int dp[(MAX_N+1)/2+1][MAX_STSIZE+1]; // ST 表,存对应的 A[] 下标
// 并查集,注意初始状态为 fa[x]=0,表示 x 暂未被标记,不属于任何一个标记连续段
int fa[MAX_N+1], size[MAX_N+1];
int find_set(int x){ return fa[x]==x?x:fa[x]=find_set(fa[x]); }
inline void merge(int x, int y, int& set_cnt, int64& score){ // 注意引用 &
x = find_set(x);
y = find_set(y);
--set_cnt;
score += 2ll*size[x]*size[y];
size[y] += size[x]; // 只需要更新根的 size
fa[x] = y;
}
// B[i] = (i, Num[i]) 只存储 i
int Num_map[MAX_N];
inline bool cmp_Num_map(int a, int b){
return Num[a]<Num[b];
}
void init(){
for(int i=1; i<=n; ++i) Num_map[i-1] = i;
sort(Num_map, Num_map+n, cmp_Num_map);
max_set = 0;
int set_cnt = 0; // 标记连续段数 cnt(x)
int64 score = 0; // 分数 score(x)
for(int i=0, j=0; i<n; i=j){
int x = Num[Num_map[i]];
while(j<n && Num[Num_map[j]]==x) ++j; // 用双指针维护,统一处理所有相等值
for(int k=i; k<j; ++k){
int id = Num_map[k];
// 以自己的编号为集合的编号创建新集合
fa[id] = id;
size[id] = 1;
++set_cnt;
++score;
// 与左边的连续段合并
// id-1>=1 可省略,因为 fa[0] 始终为 0
if(id-1>=1 && fa[id-1]) merge(id, id-1, set_cnt, score);
// 与右边的连续段合并
// 如果 fa[] 开到 1000002 也可省略 id+1<=n
if(id+1<=n && fa[id+1]) merge(id, id+1, set_cnt, score);
}
max_set = max(max_set, set_cnt);
A[set_cnt].update(stu1(x, score));
}
// Log2[] 最多用到 max_set-1
Log2[0] = Log2[1] = 0;
for(int i=2; i<max_set; ++i) Log2[i] = Log2[i>>1]+1;
for(int i=1; i<=max_set; ++i) dp[i][0] = i;
int maxk = Log2[max_set-1];
for(int k=1; k<=maxk; ++k){
int maxi = max_set-(1<<k)+1;
for(int i=1; i<=maxi; ++i){
int l = dp[i][k-1], r = dp[i+(1<<(k-1))][k-1];
dp[i][k] = (A[l]<A[r] ? r : l);
}
}
}
const stu1& query(int l, int r){
r = min(r, max_set); // 对 r 剪枝
if(l>r) return A[0]; // 说明 l>max_set,无解,A[0] 表示一个无解状态
// ST 查询
int k = Log2[r-l];
int a = dp[l][k], b = dp[r-(1<<k)+1][k];
return A[a]<A[b] ? A[b] : A[a];
}
int main(){
scanf("%d%d", &n, &T);
for(int i=1; i<=n; ++i) scanf("%d", Num+i);
init();
int lastans = 0; // 实际上是 lastans%n,正确性由模运算性质保证
while(T--){
int a, b, x, y;
scanf("%d %d %d %d", &a, &b, &x, &y);
int l = ((int64)a*lastans+x-1)%n+1;
int r = ((int64)b*lastans+y-1)%n+1;
if(l>r) swap(l, r);
const stu1& ans = query(l, r);
if(ans.valid()){
printf("%lld %d\n%d %d %d\n", ans.score, ans.x, l, r, lastans);
lastans = ans.score*ans.x % n;
}else{
printf("-1 -1\n%d %d %d\n", l, r, lastans);
lastans = 1;
}
}
return 0;
}