Codeforces Round 869 (Div. 1)
A
题目链接
题目大意
给定n个数,m次询问,每次询问一个区间,区间内不能有连续三个递减的数,即ai>=ai-1>=ai-2。求每次询问的l,r区间中可以选择的最大子序列大小。
数据范围
1≤n,q≤2e5
思路
这种区间查询的问题,并且是不带修改的区间查询,我们通常可以使用离线的算法,如莫队,ST表等数据结构来实现,这里采用莫队来做。
做法
这题可以说是莫队的板子了,除了莫队的基本操作(按左端点分块,右端点排序(奇偶优化))以外,需要修改的部分应该就是四个while循环了,也就是移动l,r指针的时候如果维护答案。可以发现,是找子序列最大个数,所以只需比较最后一段是否是连续递减的即可,举r指针右移为例子,如果是添加的数和最后两个数为递减关系,那么不对答案产生贡献,如果不是,则对答案产生贡献+1,剩下三种情况也容易推出来。
标签
莫队,数据结构
代码实现
struct pp {
int l, r, idx, block;
bool operator<(const pp&x)const{
if (block != x.block) return block < x.block;
else {
if (block & 1) return r > x.r; //奇偶优化
else return r < x.r;
}
}
}q[maxn];
int a[maxn];
void solve() {
int n, m;
cin >> n >> m;
int len = sqrt(n); //块长
vector<int>res(m + 1);
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= m; ++i) {
cin >> q[i].l >> q[i].r;
q[i].idx = i; q[i].block = (q[i].l - 1) / len + 1;//分块
}
sort(q + 1, q + 1 + m); //排序
int l = 1, r = 0, cnt = 0;
for (int i = 1; i <= m; ++i) {
while (l < q[i].l) { //左指针右移
--cnt;
if (l + 2 > r) {
++l;
continue;
}
if (a[l] >= a[l + 1] && a[l + 1] >= a[l + 2]) ++cnt;
++l;
}
while (l > q[i].l) { //左指针左移
--l;
++cnt;
if (l + 2 > r) continue;
if (a[l] >= a[l + 1] && a[l + 1] >= a[l + 2]) --cnt;
}
while (r > q[i].r) {//右指针左移
--cnt;
if (l + 2 > r) {
--r;
continue;
}
if (a[r] <= a[r - 1] && a[r - 1] <= a[r - 2]) ++cnt;
--r;
}
while (r < q[i].r) { //右指针右移
++cnt;
++r;
if (l + 2 > r) continue;
if (a[r] <= a[r - 1] && a[r - 1] <= a[r - 2]) --cnt;
}
res[q[i].idx] = cnt;
}
for (int i = 1;i<=m;++i) { //离线查询处理结果
cout << res[i] << endl;
}
}
时间复杂度
O(n+m*sqrt(n))
Rating
1500
小结
最近刚刚学的莫队,虽然这题的最优解不是莫队,但是好在莫队简单且好写,并且最近也在学习莫队,那么尝试用莫队写写,虽然自己的第一想法是dp(事实上这题确实dp才是最优解)。
Codeforces Round 849 (Div. 4)
F
题目链接
题目大意
给定一个序列n和两种操作:
操作1,修改了l,r区间的序列,把区间内的数修改为它们的数位和。
操作2,查询下标为x的数
数据范围
1≤n,q≤2e5
思路
这题一眼就是要维护数据结构了。然而这题却异常简单,因为每个数的数位和最多进行两次累加后不会变化,因为最大为999999999=81(数位和)=9(数位和)=9=9...,所以修改只需要暴力的修改即可,我们需要维护的只有哪些下标不需要被修改,哪些下标需要被修改。这个可以用一个set来维护,然后我们把区间分成sqrt(n)个块,然后每个块都有一个set,set内存需要修改的数的下标,如果块内set为空,则表示没有需要修改的数。
做法
可以用分块来做,比较暴力,但能过。更好的方法有树状数组,线段树等,可以少一个根号的复杂度,但是这题给了2e5的数据范围,带根号的复杂度也能过,所以我们可以分块来做,对每个块进行暴力修改即可。如果不分块的话,需要遍历O(n),会超时。最后记得初始化!
标签
分块,数据结构
代码实现
int n,q,len;
int block[maxn],a[maxn];
set<int>st[500]; //块内大于等于10的数的下标
int cal(int x){
int s=0;
while(x){
s+=x%10;
x/=10;
}
return s;
}
void init(){
for(int i=1;i<=n;++i){
block[i]=0;
a[i]=0;
}
for(int i=1;i<=len+1;++i){
st[i].clear();
}
}
void update(int l,int r){
for (int i = l; i <= min(r, (block[l] * len)); i++) { //左散块
if(a[i]<10) continue;
a[i]=cal(a[i]);
if(a[i]<10) st[block[i]].erase(i);
}
if (block[l] != block[r]) {
for (int i = (block[r] - 1) * len + 1; i <= r; i++) {//右散块
if(a[i]<10) continue;
a[i]=cal(a[i]);
if(a[i]<10) st[block[i]].erase(i);
}
}
for (int i = block[l] + 1; i <= block[r] - 1; i++) { //整块
if(st[i].empty()) continue;
for(auto it=st[i].begin();it!=st[i].end();){
a[*it]=cal(a[*it]);
if(a[*it]<10) st[i].erase(it++);
else ++it;
}
}
}
void solve(){
cin>>n>>q;
len=sqrt(n);
init();
for(int i=1;i<=n;++i){
cin>>a[i];
block[i]=(i-1)/len+1;
if(a[i]>=10){
st[block[i]].insert(i);
}
}
vector<int>res;
while(q--){
int op;
cin>>op;
if(op==1){//修改操作
int l,r;
cin>>l>>r;
update(l,r);
}
else{//查询操作
int x;
cin>>x;
cout<<a[x]<<endl;
}
}
}
时间复杂度
O(n+sqrt(n)*log2n*n)
Rating
1500
小结
线段树跑的很快,但我不会!分块才是暴力之神!