题目的大概意思是,首先给一个正整数N,给一个序列ai,这个序列共N个数字,并且每个数字都在[-N,N]这个区间上。
一次针对ai的操作的作用是,使得ai到aN所有数字都加上c,c是任意并且可正可负。
问最多执行两次操作,也可以不操作,操作的对象也是任意的,问这个序列最多可以有多少个数字落在[1,N]这个区间上。
先说明我的做法的复杂度是O(N*N*logN),跑了1700+MS水过;
不知道跑了200+MS的大牛是怎么做的,Orz
为了方便计算,我在输入的时候先将每个数字加上N+1,这样数字的范围就变成了[1,2N+1]。
对于不操作的情况,那就是直接统计。
只操作一次的话,就是分两段,前面一段没改变的和后面一段被改变的,然后枚举断点取最大值即可。
而操作两次,设两次操作的位置分别是i和j,i<j,因为c是可以任意的,那么无论前面i操作的c是多少,j位置的c都可以根据前面的情况进行调整。
所以这种情况分为三段:前面未被改变的 、 ai到aj-1 和 aj到aN。然后还是枚举断点取最大值。
剩下的问题就是怎么求某个区间的最优值。
因为一次操作使得某个区间上的数字的改变量是一样的,我们的目标是使得较多的数字落在[1,N]上,其实也就是把ai到aj上的数字全部放到数轴上,然后在数轴上找一段长度为N-1的,并且落在上面的数字最多,通过c使得它平移到跟[1,N]重合。
这里我用线段树做统计。
解释下线段树中结点o的定义:
l[o]和r[o],是区间的左右端点;
这里要解释的是,对于这里的线段上的点,代表的是一个长度为N-1的区间。
比如N=2的时候,经过前面的修正,ai的范围应该是[1,5],假设当前数字是1和4
那么,我们要找的答案的范围应该就是[1,2]、[2,3]、[3,4]和[4,5]这样四个区间,这四个区间对应的个数就是1,0,1,1;
取它们的左端点的值1,2,3,4作为端点保存到线段树中,这样就可以通过左端点记录对应的区间的个数,从而求最值,对应的线段树的结构如下,右边是对应结点的最大值:
s[o]保存当前结点的最值,f[o]是懒惰标记。
这样当插入一个新的ai时,因为ai可以落到很多个长度为N的区间上,比如增加一个数字是3,3落在[2,3]和[3,4]两个区间,相当于对应的起点都加1,这些起点还是连续的,这就是个区间修改问题(上面的例子就是线段树区间[2,3]加1),修改子节点维护好父节点信息,就可以直接从根节点取最大值。
只是我的做法必须枚举所有的i和j,
具体处理是,可以先从右边往左扫,可以先计算出所有的ai到aN的值,保存到dp[i]中。
然后就是for循环扫过去枚举中间的ai和aj,每次都固定ai,清空线段树之后,不断增加新的aj进去。
所以复杂度是O(N*N*logN),N只有3000,估着大概卡着时间能过吧,实际运行也差不多,2000MS的题目跑了1700+MS。高效的就不知道怎么写了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 3010
#define LEFT(o) ((o)<<1)
#define RIGHT(o) (((o)<<1)|1)
#define MID(a,b) (((a)+(b))>>1)
int n, i, j, ans, cur, re, dp[MAXN], a[MAXN], r[MAXN<<2], l[MAXN<<2], s[MAXN<<2], f[MAXN<<2];
inline void getnum(int& x){
x=0;
bool mk=0;
char c=getchar();
while(c<48 || c>57){
if(c=='-') mk=1;
c=getchar();
}
while(c>=48 && c<=57){
x=x*10+c-48;
c=getchar();
}
if(mk) x=-x;
}
void build(int o, int ll, int rr){
l[o]=ll; r[o]=rr;
if(ll<rr){
int m=MID(ll,rr);
build(LEFT(o),ll,m);
build(RIGHT(o),m+1,rr);
}
}
void maintain(int o){
s[o] = max(s[LEFT(o)], s[RIGHT(o)]);
}
void update(int o, int ll, int rr, int v){
if(l[o]==ll && r[o]==rr){
f[o]+=v;
s[o]+=v;
}
else{
int lc=LEFT(o), rc=RIGHT(o);
int m=MID(l[o],r[o]);
if(f[o]){
update(lc,l[o],m,f[o]);
update(rc,m+1,r[o],f[o]);
f[o]=0;
}
if(rr<=m) update(lc,ll,rr,v);
else if(ll>m) update(rc,ll,rr,v);
else{
update(lc,ll,m,v);
update(rc,m+1,rr,v);
}
maintain(o);
}
}
int main(){
while(~scanf("%d", &n)){
memset(s,0,sizeof(s));
for(i=1; i<=n; i++){
getnum(a[i]);
a[i]+=n+1;
}
build(1,1,n+1);
memset(s,0,sizeof(s));
memset(f,0,sizeof(f));
for(i=n; i>=1; i--){
update(1, max(1,a[i]-n+1), min(n+1, a[i]),1);
dp[i]=s[1];
}
re=0;
ans = 0;
for(i=1; i<=n; i++){
memset(s,0,sizeof(s));
memset(f,0,sizeof(f));
ans = max(ans, re+dp[i]);//只操作一次
for(j=i+1; j<=n; j++){//枚举操作两次
update(1, max(1,a[j-1]-n+1), min(n+1, a[j-1]),1);
ans = max(ans, re+dp[j]+s[1]);
}
re += (a[i]>n+1?1:0);
}
ans = max(ans, re);//不操作
printf("%d\n", ans);
}
return 0;
}