前天打了codeforces703div3的比赛,这场比赛我一个小时做出了前6题,第7题在场内因为看错了题意,很遗憾没能AK,从200名掉到了800多名,在算排名的人里面排了200多名,涨了差不多100分吧。我codeforces有两个账号, 一个1800分,一个1600多分。今天的cf我没有打,先休息一天吧,下周在继续冲击更高的目标。
G题:(单调栈 + 二分 + 数学推导)
前面六个题都很基础,就不讲解了,重点讲一下G题吧。
G题是让你求一个长度为n的循环序列至少多少项的前缀和大于等于x。
n是200000,询问也是200000。
很显然,我们要先对长度为n的一个循环节求前缀和。对于一个可能很大的x我们还要考虑这个x至少需要多少个循环节才能达到题目要求。
比如一个长度为n的序列我们设为:
-1 2 4 -1 11
我们求它的前缀和数组为:
-1 1 5 4 15
我们发现4这个数,只需要3个数的前缀和就可以达到了,所有4应该是一个不可达状态。
就是说,对于当前前缀和为x,向后遍历的时候,前缀和为y,只有当y > x才是一个应该被记录的状态。
维护这种需要,我们可以考虑一个数据结构:单调栈。
我们把上述序列的-1, 1, 5, 15存到一个单调栈中。
我们维护好一个单调栈就可以对题目中的x进行查找了,注意:(题目中没每一个询问都是>=1的,这其实很重要):
我们可以先对x在我们维护好的单调栈中进行二分查找,比如x是6,在[-1, 1, 5, 15]单调栈中,至少得15这个状态可以满足条件。如果能查找到答案,直接输出即可。
否则,如果一个循环节的前缀和sum[n]是小于等于0的话,我们不可以通过多次循环节的方式去找到答案,所以输出-1。
最后还有一种情况就是一个循环节的sum[n] > 0,x在第一个循环节中不能找到答案。这种情况,需要多次循环节进行完成,我们需要找到这个x至少需要多少个循环节才能满足条件,同时求出最后一个循环节中至少要保证大于等于几,在最后一个循环节(也就是对一个循环节开的单调栈)中进行一下二分查找即可。此处的数学推导要注意细节,不然会wrong answer。
(比赛的时候,不知到什么原因,把题目里的大于等于想成等于了,因此得出的办法是不能通过本题的)
代码如下:
#pragma GCC optimize("-Ofast","-funroll-all-loops")
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 1e18;
int _;
int n, m;
ll a[200010];
ll sum[2000010];
vector< pair<ll, int> > stk;
void solve(){
cin >> _;
while(_--){
cin >> n >> m;
sum[0] = 0;
for(int i = 1; i <= n; i++){
cin >> a[i];
sum[i] = sum[i-1] + a[i];
}
ll maxx = -inf;
stk.clear();
for(int i = 1; i <= n; i++){
if(sum[i] > maxx){
stk.push_back({sum[i], i - 1});
maxx = sum[i];
}
}
ll d = sum[n];
while(m--){
ll x;
cin >> x;
int k = lower_bound(stk.begin(), stk.end(), make_pair(x, 0)) - stk.begin();
if(k < stk.size()){
cout << stk[k].second << " ";
continue;
}
if(d <= 0){
cout << "-1" << " ";
continue;
}
ll tim = (x - maxx - 1)/d + 1;
k = lower_bound(stk.begin(), stk.end(), make_pair(x-tim*d, 0)) - stk.begin();
cout << stk[k].second + tim*n << " ";
}
cout << "\n";
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}