目录
例1:求出最大K
题目
给一个长度为N的数列A,然后进行若干次询问,每次给定一个整数T,求出最大的k,满足sum[k]<=T, 其中sum[i]=a[1]+a[2]+……a[k]。要求算法必须在线。
题解
用倍增的思想。
- 令 p=1, k = 0, sum = 0;
- 求出a的前缀和,记在s数组中。如果sum+s[k+p]-s[k]<=T,那么sum+=s[k+p]-s[k], k += p, p*=2 ,否则的话 p /= 2;
- 重复第二步,直到k=0为止。
代码
int n,a[maxn],t,s[maxn];
int main()
{
cin>>n>>t;
for(int i=1;i<=n;i++) {
read(a[i]);
s[i] = s[i-1]+a[i];
}
int p = 1, k = 0, sum = 0;
while(p) {
if(k+p<=n&&sum+s[k+p]-s[k]<=t) {
sum += s[k+p]-s[k];
k+=p;
p*=2;
}else {
p /= 2;
}
}
cout<<k<<endl;
return 0;
}
例2:Genius ACM
题目
题意
给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:
从集合 S 中取出 M 对数(即 2∗M 个数,不能重复使用集合中的数,如果 S 中的整 数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值 就称为集合 S 的“校验值”。
现在给定一个长度为 N 的数列 A 以及一个整数 T。我们要把 A 分成若干段,使得 每一段的“校验值”都不超过 T。求最少需要分成几段。
题解
倍增扩展区间,每个区间先排序,求出校验值,然后依次倍增扩展当前区间。
代码
ll t,n,m,K,a[maxn],tot,b[maxn];
bool calc(ll l,ll k,ll p) { //左区间为l,右区间为r,扩展长度为p,最右端为k+p
ll res = 0;
for(ll i=l;i<=k+p;i++)
b[i-l] = a[i];
ll len = p+k-l+1;
sort(b,b+len);
if(len>=2*m) { //取出m个数
for(ll i=0;i<m;i++) {
res += (b[i]-b[len-i-1])*(b[i]-b[len-i-1]);
}
}else {
ll temp = len/2;
for(ll i=0;i<temp;i++) {
res += (b[i]-b[len-i-1])*(b[i]-b[len-i-1]);
}
}
if(res<=K) return true;
return false;
}
int main()
{
read(t);
while(t--) {
tot = 0;
read(n),read(m),read(K);
for(ll i=0;i<n;i++) {
read(a[i]);
}
ll l = 0;
while(l<n) {
ll k = l, p = 1;
// cout<<k<<" "<<p<<endl;
while(p) { //枚举当前区间能扩展到的最大值
if(calc(l,k,p)&&k+p<n) {
k += p;
p *= 2;
}else {
p /= 2;
}
}
tot++; //区间数+1
l = k+1; //
}
cout<<tot<<endl;
}
return 0;
}
ST算法
题目
求l-r区间内最大的数字。
题解
用 f[i][j] 表示区间 [i,i+-1] 里最大的数, 递推边界显然是f[i][0] = a[i], 首先预处理,利用公式 f[i][j] = max(f[i][j-1], f[i+][j-1]);
在查询的时候,求出一个k,使得 <r-l+1< , 这样从l开始的个数和以r结尾的个数必然覆盖了整个区间,那么[l,r]区间必然被包含了,所有最大值即为所求。
代码
int n,f[N][N],a[maxn],q;
void st_work() {
for(int i=1;i<=n;i++)
f[i][0] = a[i];
int t = log(n)/log(2)+1;
for(int j=1;j<t;j++) {
for(int i=1;i<=n-(1<<j)+1;i++)
f[i][j] = max(f[i][j-1],f[i+ (1<<(j-1))][j-1]);
}
}
int query(int l,int r) {
int k = log(r-l+1)/log(2);
return max(f[l][k],f[r-(1<<k)+1][k]);
}