一、区间最长不下降子序列(不一定连续
题面
Tips:
- 子序列没说连续!!
- Hint:最长上升子序列只需要考虑区间内是否有0,1{0, 1}0,1子序列,考虑维护两个数组LastOne和FirstZero,为了使得该hint含蓄一些,数组含义请自己脑补qwq。最长不下降子序列只有三种情况{000…000},{111…111},{000…111},前两种情况利用前缀和维护即可,第三种情况可以枚举0,1的分界点,总复杂度为O(NM),会超时。考虑用ST表优化这一过程,做到O(1)查询,最终复杂度为O(NlogN+M)。
T两个点代码:
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
const int N = 1000005;
int n,m,opt,l,r;
int a[N],end0[N][20] = {0},end1[N][20] = {0};
int last1[N][20] = {0},first0[N][20] = {0};
int sum[N] = {0};
void get_inform(){
int tmp = log(n)/log(2);
for(int i=1;i<=tmp;i++)
for(int j=1;j+(1<<i)-1<=n;j++){
if(last1[j+(1<<(i-1))][i-1] < j+(1<<(i-1)))
last1[j][i] = last1[j][i-1];
else last1[j][i] = max(last1[j][i-1] , last1[j+(1<<(i-1))][i-1]);
if(first0[j][i-1] >= j+(1<<(i-1)))
first0[j][i] = first0[j+(1<<(i-1))][i-1];
else first0[j][i] = min(first0[j][i-1],first0[j+(1<<(i-1))][i-1]);
end0[j][i] = end0[j][i-1]+end0[j+(1<<(i-1))][i-1];
end1[j][i] = max(end0[j][i-1]+end1[j+(1<<(i-1))][i-1] ,
end1[j][i-1]+sum[j+(1<<i)-1]-sum[j+(1<<(i-1))-1]);
}
}
int findattack(int L,int R){
int tmp = log(R-L+1)/log(2);
int maxx;
if((1<<tmp) == R-L+1) {
return max(end0[L][tmp],end1[L][tmp]);
}
else maxx = max(end0[L][tmp]+findattack(L+(1<<tmp),R),end1[L][tmp]+sum[R]-sum[L+(1<<tmp)-1]);
return maxx;
}
int findgreat(int L,int R){
int tmp = log(R-L+1)/log(2);
int last ,first;
if(last1[R-(1<<tmp)+1][tmp] <= R-(1<<tmp))
last = last1[L][tmp];
else last = max(last1[L][tmp], last1[R-(1<<tmp)+1][tmp]);
if(first0[L][tmp] >= L+(1<<tmp))
first = first0[R-(1<<tmp)+1][tmp];
else first = min(first0[L][tmp], first0[R-(1<<tmp)+1][tmp]);
if(first > last) return 1;
return 2;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i] = sum[i-1]+a[i];
if(a[i]==0) {
last1[i][0] = i-1;
first0[i][0] = i;
end0[i][0] = 1;
end1[i][0] = 0;
}
else {
last1[i][0] = i;
first0[i][0] = i+1;
end0[i][0] = 0;
end1[i][0] = 1;
}
}
get_inform();//last0 & first1
for(int i=1;i<=m;i++){
scanf("%d%d%d",&opt,&l,&r);
if(opt == 1) printf("%d\n",findattack(l,r));
else printf("%d\n",findgreat(l,r));
}
}
是考虑用end0
和end1
(两个st表)来维护每个长为2k的区间以0和1结尾的最长子序列长度,这样做的好处是在预处理的时候可以很方便地处理好数据,缺点是在L,R查询的时候需要一定程度的递推([L+(1<<tmp),R]
的最长不下降子序列长度不能直接得出来,需要递推)。是从区间相互连接的端点进行考虑的。
T一个点代码:
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
const int N = 1000005;
int n,m,opt,l,r;
int a[N],st[N][20] = {0};
int last1[N][20] = {0},first0[N][20] = {0};
int sum[N] = {0};
void get_inform(){
int tmp = log(n)/log(2);
for(int i=1;i<=tmp;i++)
for(int j=1;j+(1<<i)-1<=n;j++){
if(last1[j+(1<<(i-1))][i-1] < j+(1<<(i-1)))
last1[j][i] = last1[j][i-1];
else last1[j][i] = max(last1[j][i-1] , last1[j+(1<<(i-1))][i-1]);
if(first0[j][i-1] >= j+(1<<(i-1)))
first0[j][i] = first0[j+(1<<(i-1))][i-1];
else first0[j][i] = min(first0[j][i-1],first0[j+(1<<(i-1))][i-1]);
st[j][i] = max(st[j][i-1]+sum[j+(1<<i)-1]-sum[j+(1<<(i-1))-1] ,
st[j+(1<<(i-1))][i-1]+(1<<(i-1))-(sum[j+(1<<(i-1))-1]-sum[j-1]));
}
}
int findattack(int L,int R){
int tmp = log(R-L+1)/log(2);
int maxx;
maxx = max(st[L][tmp]+sum[R]-sum[L+(1<<tmp)-1] ,
st[R-(1<<tmp)+1][tmp]+(R-(1<<tmp)-L+1)-(sum[R-(1<<tmp)]-sum[L-1]));
return maxx;
}
int findgreat(int L,int R){
int tmp = log(R-L+1)/log(2);
int last ,first;
if(last1[R-(1<<tmp)+1][tmp] <= R-(1<<tmp))
last = last1[L][tmp];
else last = max(last1[L][tmp], last1[R-(1<<tmp)+1][tmp]);
if(first0[L][tmp] >= L+(1<<tmp))
first = first0[R-(1<<tmp)+1][tmp];
else first = min(first0[L][tmp], first0[R-(1<<tmp)+1][tmp]);
if(first > last) return 1;
return 2;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i] = sum[i-1]+a[i];
if(a[i]==0) {
last1[i][0] = i-1;
first0[i][0] = i;
}
else {
last1[i][0] = i;
first0[i][0] = i+1;
}
st[i][0] = 1;
}
get_inform();//last0 & first1
for(int i=1;i<=m;i++){
scanf("%d%d%d",&opt,&l,&r);
if(opt == 1) printf("%d\n",findattack(l,r));
else printf("%d\n",findgreat(l,r));
}
}
昨天半夜想到的……闭上眼睛开始睡觉,突然意识到应该怎样O(1)查询:st[i][j]
存的是[i,i+(1<<j)-1]
区间最长不下降子序列长度,在合并或者查询的时候,对于大的区间分成两部分(可能有重叠区域)则0和1的分界点一定属于其中某一个区间,所以用max(st[左区间]+右边1数,st[右区间+左边0数])
可以完美考虑到所有的情况。是从0,1分界点的角度出发进行考虑的。
可惜还是T掉了一个点,咋整呢
经过一些测试证明是常数大了,要减小常数
AC代码:
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
const int N = 1000005;
int n,m,opt,l,r;
int a[N],st[N][20] = {0};
int last1[N][20] = {0},first0[N][20] = {0};
int sum[N] = {0};
void get_inform(){
int tmp = log(n)/log(2);
for(int i=1;i<=tmp;i++)
for(int j=1;j+(1<<i)-1<=n;j++){
if(last1[j+(1<<(i-1))][i-1] < j+(1<<(i-1)))
last1[j][i] = last1[j][i-1];
else last1[j][i] = max(last1[j][i-1] , last1[j+(1<<(i-1))][i-1]);
if(first0[j][i-1] >= j+(1<<(i-1)))
first0[j][i] = first0[j+(1<<(i-1))][i-1];
else first0[j][i] = min(first0[j][i-1],first0[j+(1<<(i-1))][i-1]);
st[j][i] = max(st[j][i-1]+sum[j+(1<<i)-1]-sum[j+(1<<(i-1))-1] ,
st[j+(1<<(i-1))][i-1]+(1<<(i-1))-(sum[j+(1<<(i-1))-1]-sum[j-1]));
}
}
void findattack(){
int tmp = log(r-l+1)/log(2);
int maxx = max(st[l][tmp]+sum[r]-sum[l+(1<<tmp)-1] ,
st[r-(1<<tmp)+1][tmp]+(r-(1<<tmp)-l+1)-(sum[r-(1<<tmp)]-sum[l-1])); //声明时赋值应该会更快
printf("%d\n",maxx);
}
void findgreat(){
int tmp = log(r-l+1)/log(2);
int last ,first;
if(last1[r-(1<<tmp)+1][tmp] <= r-(1<<tmp))
last = last1[l][tmp];
else last = max(last1[l][tmp], last1[r-(1<<tmp)+1][tmp]);
if(first0[l][tmp] >= l+(1<<tmp))
first = first0[r-(1<<tmp)+1][tmp];
else first = min(first0[l][tmp], first0[r-(1<<tmp)+1][tmp]);
if(first > last) printf("1\n");
else printf("2\n");
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i] = sum[i-1]+a[i];
if(a[i]==0) {
last1[i][0] = i-1;
first0[i][0] = i;
}
else {
last1[i][0] = i;
first0[i][0] = i+1;
}
st[i][0] = 1;
}
get_inform();//last0 & first1
for(int i=1;i<=m;i++){
scanf("%d%d%d",&opt,&l,&r);
if(opt == 1) findattack();//函数传参全部删了,还改成了void类型直接输出,减少参数传来传去的复杂度
else findgreat();
}
return 0;
}
修改点:
- 可以声明时赋值的都声明即赋值了
- 查询时用到的函数全部变成了void类型,直接在内部输出,进去的参数也删掉了,全部用全局变量,减少了传参的复杂度,竟然过了hhh
二、区间GCD
题面
GCD怎么用st表呢?因为GCD是可以合并的啊……数学不好一时间没想出来……
害 下面放代码
AC代码:
#include<iostream>
#include<cmath>
using namespace std;
const int N = 500005;
int n,m,L,R;
int a[N] = {0};
int st[N][20] = {0};
int gcd(int x,int y){
int r = x%y;
while(r){
x = y;
y = r;
r = x%y;
}
return y;
}
void getst(){
int tmp = log(n)/log(2);
for(int i=1;i<=tmp;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
st[j][i] = gcd(st[j][i-1],st[j+(1<<(i-1))][i-1]);
}
}
return ;
}
int binary1(int l,int r,int target){//找最大值
int mid;
while(l<=r){
mid = (l+r)>>1;
int tmp = log(mid-L+1)/log(2);
if(gcd(st[L][tmp],st[mid-(1<<tmp)+1][tmp]) >= target)
l = mid+1;
else r = mid-1;
}
return l-1;
}
int binary2(int l,int r,int target){//找最小值
int mid;
while(l<=r){
mid = (l+r)>>1;
int tmp = log(mid-L+1)/log(2);
if(gcd(st[L][tmp],st[mid-(1<<tmp)+1][tmp]) <= target)
r = mid-1;
else l = mid+1;
}
return r+1;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
st[i][0] = a[i];
}
getst();
for(int i=1;i<=m;i++){
scanf("%d%d",&L,&R);
int tmp = log(R-L+1)/log(2);
int ans = gcd(st[L][tmp],st[R-(1<<tmp)+1][tmp]);
printf("%d %d\n",ans,binary1(L,n,ans)-binary2(L,n,ans));
}
return 0;
}
思路