【倍增】P10264 [GESP202403 八级] 接竹竿|普及+
[GESP202403 八级] 接竹竿
题目描述
小杨同学想用卡牌玩一种叫做“接竹竿”的游戏。
游戏规则是:每张牌上有一个点数
v
v
v,将给定的牌依次放入一列牌的末端。若放入之前这列牌中已有与这张牌点数相
同的牌,则小杨同学会将这张牌和点数相同的牌之间的所有牌全部取出队列(包括这两张牌本身)。
小杨同学现在有一个长度为 n n n 的卡牌序列 A A A,其中每张牌的点数为 A i A_i Ai( 1 ≤ i ≤ n 1\le i\le n 1≤i≤n)。小杨同学有 q q q 次询问。第 i i i 次( 1 ≤ i ≤ q 1\le i\le q 1≤i≤q)询问时,小杨同学会给出 l i , r i l_i,r_i li,ri 小杨同学想知道如果用下标在 [ l i , r i ] [l_i,r_i] [li,ri] 的所有卡牌按照下标顺序玩“接竹竿”的游戏,最后队列中剩余的牌数。
输入格式
一行包含一个正整数 T T T,表示测试数据组数。
对于每组测试数据,第一行包含一个正整数 n n n,表示卡牌序列 A A A 的长度。
第二行包含 n n n 个正整数 A 1 , A 2 , … , A n A_1,A_2,\dots,A_n A1,A2,…,An,表示卡牌的点数 A A A。
第三行包含一个正整数 q q q,表示询问次数。
接下来 q q q 行,每行两个正整数 l i , r i l_i,r_i li,ri 表示一组询问。
输出格式
对于每组数据,输出 q q q 行。第 i i i 行( 1 ≤ i ≤ q 1\le i\le q 1≤i≤q)输出一个非负整数,表示第 i i i 次询问的答案。
样例 #1
样例输入 #1
1
6
1 2 2 3 1 3
4
1 3
1 6
1 5
5 6
样例输出 #1
1
1
0
2
提示
样例解释
对于第一次询问,小杨同学会按照 1 , 2 , 2 1,2,2 1,2,2 的顺序放置卡牌,在放置最后一张卡牌时,两张点数为 2 2 2 的卡牌会被收走,因此最后队列中只剩余一张点数为 1 1 1 的卡牌。
对于第二次询问,队列变化情况为:
{ } → { 1 } → { 1 , 2 } → { 1 , 2 , 2 } → { 1 } → { 1 , 3 } → { 1 , 3 , 1 } → { } → { 3 } \{\}\to\{1\}\to\{1,2\}\to\{1,2,2\}\to\{1\}\to\{1,3\}\to\{1,3,1\}\to\{\}\to\{3\} {}→{1}→{1,2}→{1,2,2}→{1}→{1,3}→{1,3,1}→{}→{3}。因此最后队列中只剩余一张点数为 3 3 3 的卡牌。
数据范围
子任务 | 分数 | T T T | n n n | q q q | max A i \max A_i maxAi | 特殊条件 |
---|---|---|---|---|---|---|
1 1 1 | 30 30 30 | ≤ 5 \le 5 ≤5 | ≤ 100 \le100 ≤100 | ≤ 100 \le100 ≤100 | ≤ 13 \le13 ≤13 | |
2 2 2 | 30 30 30 | ≤ 5 \le 5 ≤5 | ≤ 1.5 × 1 0 4 \le 1.5\times10^4 ≤1.5×104 | ≤ 1.5 × 1 0 4 \le 1.5\times10^4 ≤1.5×104 | ≤ 13 \le13 ≤13 | 所有询问的右端点等于 n n n |
3 3 3 | 40 40 40 | ≤ 5 \le 5 ≤5 | ≤ 1.5 × 1 0 4 \le 1.5\times10^4 ≤1.5×104 | ≤ 1.5 × 1 0 4 \le 1.5\times10^4 ≤1.5×104 | ≤ 13 \le13 ≤13 |
对于全部数据,保证有 1 ≤ T ≤ 5 1\le T\le 5 1≤T≤5, 1 ≤ n ≤ 1.5 × 1 0 4 1\le n\le 1.5\times 10^4 1≤n≤1.5×104, 1 ≤ q ≤ 1.5 × 1 0 4 1\le q\le 1.5\times 10^4 1≤q≤1.5×104, 1 ≤ A i ≤ 13 1\le A_i\le 13 1≤Ai≤13。
倍增
b[i] ,b[i] > i,a[i] == a[b[i]],且i最小。b[i]是消掉i的牌。
c[i][j] 记录a[i,i+2j-1]剩下的第一张牌,如果可以消掉所有牌,为-1。-2表示未计算。
f(i,len)求左闭右开空间a[i,i+len),第一张未消除的牌。其逻辑如下:
2j < len,且j最大。将区间分成左右两个区间,左区间长2j,余下的是右半区间。
情况一,x1 = c[i][j],如果x1=-1,返回f(右区间)
情况二,x2=b[x1] >= i+len,返回x1
情况三,x2 <i+len,返回f(x+1,len-(x+1-i))
情况二,直接返回结果,时间复杂度O(1);情况一和情况三长度至少减少一半,时间复杂度O(logn)。
两种特殊情况要处理:
len = min(len, N - i);
如果len <=0,返回-1。
如果len刚好是2的整数次幂,且c[i][j+1] 不为-2,则直接返回。
函数g(i,len)求左闭右开空间a[i,i+len)剩余的牌数。由于本题解是0开始,故每次查询的答案是g(Left-1,r-left+1)。
x = f(i,len) 如果x 是-1,返回0。
否则返回1+g(x+1,len-(x+1-i))
特殊处理:
len = min(len, N - i);
len <=0返回0。
由于只有13张牌,故只顶多调用g函数13次。每次查询的时间复杂度是O(logn13)
c[i]0]=i
j从1到15,i从0到n-1 计算c。
代码
核心代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include <bitset>
using namespace std;
template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}
template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) ;
return in;
}
template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}
template<class T = int>
vector<T> Read() {
int n;
scanf("%d", &n);
vector<T> ret(n);
for(int i=0;i < n ;i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
class Solution {
public:
vector<int> Ans(vector<int>& a, vector<pair<int, int>>& que) {
const int N = a.size();
vector<int> b(N, N), next(14, N);
for (int i = N - 1; i >= 0; i--) {
b[i] = next[a[i]];
next[a[i]] = i;
}
const int M = 15;
vector<vector<int>> c(N, vector<int>(M + 1, -2));
function<int(int, int)> F = [&](int i, int len) {
len = min(len, N - i);
if (len <= 0) { return -1; };
int j = 0;
while ((1 << j) < len) { j++; }
if (((1 << j) == len) && (-2 != c[i][j])) { return c[i][j]; }
j--;
auto x = c[i][j];
const auto& len1 = 1 << j;
if (-1 == x) { return F(i + len1, len - len1); }
if (b[x] >= i + len)return x;
return F(b[x] + 1, len - (b[x] + 1 - i));
};
for (int i = 0; i < N; i++) { c[i][0] = i; }
for (int j = 1; j <= M; j++) {
for (int i = 0; i < N; i++) { c[i][j] = F(i, 1 << j); }
}
function<int(int, int)> G = [&](int i, int len) {
len = min(len, N - i);
if (len <= 0) { return 0; }
int x = F(i, len);
if (-1 == x) { return 0; }
return 1 + G(x + 1, len - (x + 1 - i));
};
vector<int> ans;
for (const auto& [left, r] : que) {
ans.emplace_back(G(left - 1, r - left + 1));
}
return ans;
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
int T = 0;
cin >> T;
while(T--){
auto a = Read<int>();
auto que = Read<pair<int, int>>();
#ifdef _DEBUG
//printf("N=%d", n);
/* Out(a, "a=");
Out(que, ",que=");*/
#endif // DEBUG
auto res = Solution().Ans(a,que);
for (const auto& i : res)
{
cout << i << endl;
}
}
return 0;
}
单元测试
vector<int> a;
vector<pair<int, int>> que;
TEST_METHOD(TestMethod11)
{
a = { 1,2,2,3,1,3 }, que = { {1,3},{1,6},{1,5},{5,6} };
auto res = Solution().Ans(a,que);
AssertEx({ 1,1,0,2 }, res);
}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。