题意:有n(1<=n<=10^5)个机器人,每个机器人有m(1<=m<=5)个属性值(0<=属性值<=10^8),
然后你的大炮可以攻击k次,每次可以将所有机器人的同一个属性值-1(如果它大于0的话)
问如何选择可以使得连续被摧毁的机器人最多(所有属性值为0就看做被摧毁)。
得到 摧毁一个给定区间所需的最小k值,就是对于m个属性值,在区间内求最大值,这个可以用线段树做到。
关键是如何遍历所有的区间,来找出k炮可以消灭的最长区间。
CF上的题解是这么说的:对于区间[L,R),若可以摧毁,则让R++,若不可以则让L++,这样,记录沿途经过的所有区间,求里面的最大值就行了。
算是这么算,但是这样不太好理解。
我是这么理解的,遍历每一个L,来求以L为左端点,所能摧毁的最长区间[L,R].
求完之后,轮到L+1的时候,因为已知[L,R]是可以摧毁的,所以[L+1,R]一定是可以摧毁的,所以[L+1,r ] ( r<R) 的所有区间都不可能是最优解,所以不需要考虑。
这样实际上在L++之后R根本不需要从L开始,而是从前一个R开始,于是就是前面所说的双指针,L与R都是单调递增,所以时间复杂度为O(n)*线段树的O(log2 n)。
总时间复杂度O(n*log2n) (m太小,忽略)
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> using namespace std; int n,m,k; struct A{ int a[5]; A(){memset(a,0,sizeof(a));} void Input(){ for(int i=0;i<m;++i) scanf("%d",&a[i]); } A max(A t1,A t2){ for(int i=0;i<m;++i) a[i]=t1.a[i]>t2.a[i]?t1.a[i]:t2.a[i]; return *this; } int Sum(){ int sum=0; for(int i=0;i<m;++i) sum+=a[i]; return sum; } void Show(){ for(int i=0;i<m;++i){ if(i) printf(" "); printf("%d",a[i]); } printf("\n"); } }; A st[100007 <<2]; void PushUp(int rt){ st[rt].max(st[rt<<1],st[rt<<1|1]); } void Build(int l,int r,int rt){ if(l==r){ st[rt].Input(); return; } int m=(l+r)>>1; Build(l,m,rt<<1); Build(m+1,r,rt<<1|1); PushUp(rt); } A Query(int L,int R,int l,int r,int rt){ if(L <= l && r <= R){return st[rt];} int m=(l+r)>>1; A X; if(L <= m) X.max(X,Query(L,R,l,m,rt<<1)); if(R > m) X.max(X,Query(L,R,m+1,r,rt<<1|1)); return X; } int main(void) { while(~scanf("%d%d%d",&n,&m,&k)){ Build(1,n,1); int L=1,R=1;//区间 [L..R) A ANS;int Length=0; A Cur; while(L <=n){ if(L==R){ if(R==n+1) break; else {++R;continue;} } Cur=Query(L,R-1,1,n,1); int S=Cur.Sum(); if(S <=k&&R-L>Length){ Length=R-L; ANS=Cur; } if(S <=k&&R <=n) ++R; else ++L; } ANS.Show(); } return 0; }