C
题意
给定n个数,每次队首两个数比拼数大小,大的留在队头,小的去队尾,给定x , k 问初始序列时第x位在前k轮赢了多少次?
思路
维护每个数的第一个比它大的数的索引和每个数是否是前缀最大值
可以知道如果一个数前面有比他大的数,那么他的胜场只能为0,所以处理询问时如果a【x】不是前缀最大值就直接输出0
否则根据k的大小再来输出答案:设p为维护的比x第一个大的数的索引,可以知道第k轮涉及到前k+1个数的比对,所以p <= k + 1 时即当前比试的轮次x已经输过了,直接输出p和x的位置差即可,p > k + 1时,说明x还没有输或者还没有轮到x比大小,根据情况输出0 或者 k + 1个数和x的差值
需要注意的是
- x == 1这个位置非常特殊需要特判
- 维护第一个比x大的数的索引pos【x】暴力枚举是会超时的,我们需要用之前维护的值来加快这个维护,给定位置i
如果a【i-1】 < a【i】那么pos【i-1】直接就是i,否则我们可以跳到pos【i】因为如果a【i-1】> a【i】那么a【i-1】一定大于(i, pos【i】- 1)中的所有值。
代码实现
int a[N];
int pos[N];
bool win[N];
void solve()
{
int n, q;
cin >> n >> q;
memset (a, 0, sizeof a);
memset (pos, 0, sizeof pos);
memset (win, false, sizeof win);
for (int i = 1 ; i <= n ; i ++) cin >> a[i];
int ma = a[1];
win[1] = a[1] > a[2];
for (int i = 2 ; i <= n ; i ++)
{
if (ma > a[i]) win[i] = false;
else
{
ma = a[i];
win[i] = true;
}
}
pos[n] = INF;
for (int i = n ; i >= 2 ; i --)
{
if (a[i - 1] < a[i]) pos[i - 1] = i;
else
{
int p = pos[i];
while (p != INF and a[i - 1] > a[p])
p = pos[p];
pos[i - 1] = p;
}
}
while (q --)
{
int x, k;
cin >> x >> k;
if (!win[x]) cout << 0 << endl;
else
{
int p = pos[x];
if (p <= k + 1)
{
if (x == 1) cout << p - x - 1 << endl;
else cout << p - x << endl;
}
else
{
int y = max (1ll, x - 1);
cout << max(0ll , k - y + 1) << endl;
}
}
}
}
D
题意
给定一个序列,每次可以选择一个区间[ l , r ],使区间中所有数异或上一个任意值,花费是区间长度 / 2向上取整,询问最少需要多少代价能使序列中所有数变成0
思路
可以发现每次只取长度为2的区间和取长度大于2的区间的总操作区间 和花费都是一样的。且每次只操作一个数必定能花费n使所有数变为0.
所以我们每次只操作长度为1或2的区间,从1到n操作,寻找是否能减少操作次数,即用一次操作将两个数都变成0
我们发现我们在之前的操作可以让Ai 这个数变成【1 - i】任意组合的异或后缀和,也就是说,我们当处理到i位置的时候,根据需求,这个Ai可能为1 - i任意异或后缀和,如果此时有Ai+1 等于某个异或后缀和,那么我们可以用1的花费将i, i+1两个位置变成0
代码实现
p 表示 当前异或后缀和,set里存的是之前所有异或后缀(当成功一次消除两个时需要清空set和p,因为之前的异或后缀已经用不上了)
int n;
cin >> n;
int res = 0;
set<int> s;
int p = 0;
s.insert (0);
for (int i = 1 ; i <= n ; i ++)
{
int x;
cin >> x;
if (x == 0 or s.count (x ^ p))
{
s.clear();
s.insert (0);
p = 0;
}
else
{
s.insert (p);
p ^= x;
res ++;
}
}
cout << res << endl;