题目大意
有一空数组a,进行n次操作。有两种操作类型,1:在数组后插入一个数x ( 1≤x≤n ),2:将数组复制x (1≤x≤10^9)份后接入数组后面。有q次询问,每次询问输出第k位数字是什么。
1<=n,q<=10^5, 1≤k≤min(10^18,c), 其中 c 是完成所有 n 操作后的数组大小
解法1:模拟
思路
将操作看成是一轮一轮进行的,每一轮是先进行多次操作1之后再进行一次操作2,即先加数再复制。每一轮至少复制一次,最多有10^18个数字,故最多有(约==60)。
num[i],记录进行完第i轮之后能得到多少个数字。
rep[i],记录第i轮得到的序列有多少个相同的序列(循环了几次),即复制的x次j加上本身的1次
vector<int>v[N],v[i],表示第i轮新增加的数。
每次询问,从后往前找,在第几轮第一次达到了 k 位数,假设在第 i 轮达到了 k 位数,则先计算该轮中每次循环几个数,即 p=num[i] / rep[i],再求余 t=x%p(t 表示处在循环里的第几个数),当 t==0(即最后一个数)时,为方便查找让 t=p 。再判断 t 是否小于等于num [ i-1 ],若为真则表示要找的这个数不是这一轮加入的,让 x=t,继续循环,直到找到这个数是在某一轮加入的,然后输出该数;若为假,则表示这个数在上一轮中还不存在,是这一轮加入的,则从 v[i] 中找到这个数(v [i] [ t- num [ i ]- 1]),输出。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 1e18;
int num[100];//记录进行完第i轮之后能得到多少个数字
int rep[100];
//记录第i轮得到的序列有多少个相同的序列(循环了几次)
//即复制的x次再加上本身的1次
vector<int>v[100];//v[i],表示第i轮新增加的数
signed query(int x, int i) {
while (1) {
while (num[i - 1] >= x) i--;//找在哪一轮达到了第x位数
int k = num[i] / rep[i];//一个循环里面有几个数
int t = x % k;//处在第几个
if (t == 0) t = k;//==0,表示处在最后一个数,即第k个数,为方便输出让t=k
if (t <= num[i - 1]) x = t;//不是在这一轮加入的,继续往前找
else return v[i][t - num[i - 1] - 1];//是在这一轮加入的,则直接输出
}
}
void solve() {
int n, q;
cin >> n >> q;
//初始化,最多不超过60轮
for (int i = 1; i <= 65; ++i) {
v[i].clear();
num[i] = 0;
rep[i] = 1;
//rep初始值一定要为1,当 n 次操作不以完整的一轮结尾时,即以操作1结尾,
// 当rep=0时,在算这一轮有几个数 p= num[i] / rep[i] 时会出错,
// 所有初始值要为1,表示本身就是一次循环
}
int b, x;
int now = 1;
while (n--) {
cin >> b >> x;
if (num[now - 1] >= INF) continue;
//查询的k最大到10^18,当超过这个范围就不管了
//复制的份数x,(1<=x<=10^9),当num[i-1]足够大时会超
if (b == 1) {
v[now].push_back(x);//v[i]表示第i轮新增的数
num[now] = num[now - 1] + v[now].size();
//为防止第n次操作不以操作2结尾时,新加入的这些数直接形成新的一轮
}
else {
int add = x;//表示实际需要复制的次数
if (num[now - 1]) {
add = min(x, INF / num[now - 1]);
}
//当复制后得到的数超过所需的10^18个时,取后者,将a的大小限制在10^18的范围内
num[now] = (num[now - 1] + v[now].size()) * (add + 1);
//当前轮的个数=(上一轮的个数+新增)乘以复制的次数加1
rep[now] = add + 1;//表示有几个相同的序列,即复制数加上本身
now++;
}
}
while (q--) {
int k = 1;
cin >> k;
cout << query(k, now) << " ";
}
cout << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
解法2:二分
思路
看每一次操作,num[i] 表示第 i 次操作后能得到多少个数字。last[i] 表示第 i 次操作后的最后一个数是谁。
对于每次询问,在 num 中找第一个大于他的数。分情况讨论,若当前轮是操作 2 得到的,且 x % num[ i - 1] != 0: 答案就是前 i -1次操作中的第 x % num[ i -1]大的数字,若 x % num[ i - 1] == 0 则答案是上一轮的最后一个数,即 last[ i -1];若当前轮是操作 1 得到的,因为进行的是操作1,故 num[ i ]=num[[ i -1]+1, 又因为找的是num 中找第一个大于他的数,故找到的是num[ i ],而答案即为 i-1 轮的最后一个数 last[ i-1] ,并且此时 x%num[ i -1]==0 (x正好等于num[i-1])。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10, INF = 1e18;
int num[N], last[N];
int n, q;
signed query(int x) {
while (1) {
int pos = upper_bound(num + 1, num + n + 1, x) - num;
//求出第一次数组中的数字数量大于x需要的操作数量
int k = x % num[pos - 1];
if (!k) return last[pos - 1];
//如果第pos次操作类型是1: 答案就是做完pos-1次操作的结果
//如果第pos次操作类型是2并且x % num[pos - 1] == 0:
// 答案就是做完pos-1次操作的最后一个数字
x %= num[pos - 1];
//如果第pos次操作类型是2并且x % num[pos - 1] != 0:
// 答案就是前pos-1次操作中,第x % num[pos-1]大的数字
}
}
void solve() {
cin >> n >> q;
int op, x;
for (int i = 1; i <= n; ++i) {
cin >> op >> x;
if (op == 1) {
num[i] = num[i - 1] + 1;
last[i] = x;
}
else {
int add = x;//表示实际需要复制的次数
if (num[i - 1]) {
add = min(x, INF / num[i - 1]);
//当复制后得到的数超过所需的10^18个时,取后者,将a的大小限制在10^18的范围内
}
num[i] = num[i - 1] * (add + 1);
last[i] = last[i - 1];
}
}
int k;
while (q--) {
cin >> k;
cout << query(k) << " ";
}
cout << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}