2019 Multi-University Training Contest 2--Keen On Everything But Triangle--hdu--6601

Keen On Everything But Triangle

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 3426    Accepted Submission(s): 806

 

Problem Description

N sticks are arranged in a row, and their lengths are a1,a2,...,aN.
There are Q querys. For i-th of them, you can only use sticks between li-th to ri-th. Please output the maximum circumference of all the triangles that you can make with these sticks, or print −1 denoting no triangles you can make.

Input

There are multiple test cases.

Each case starts with a line containing two positive integers N,Q(N,Q≤105).

The second line contains N integers, the i-th integer ai(1≤ai≤109) of them showing the length of the i-th stick.

Then follow Q lines. i-th of them contains two integers li,ri(1≤li≤ri≤N), meaning that you can only use sticks between li-th to ri-th.

It is guaranteed that the sum of Ns and the sum of Qs in all test cases are both no larger than 4×105.

Output

For each test case, output Q lines, each containing an integer denoting the maximum circumference.

Sample Input

5 3 2 5 6 5 2 1 3 2 4 2 5

Sample Output

13 16 16

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=6601

题意:

给你 n 根木棍的长度,然后有 Q 次询问,每一次询问给定 l , r 表示询问从第 l 根木棍到第 r 根木棍中能够组成的三角形的最大周长。

题解:

关于用木棍组成三角形的问题,有一个小知识,当木棍数量 >= 47 根时一定能组成三角形(极端情况是 47 根木棍的长度成斐波拉契数,这些数无法构成三角形,且容纳的边数最多。第 47 项超过1e9),所以对于最大周长,一定在前 47 根木棍中构成。。所以每次查询利用主席树暴力获得前 47 根木棍长度,然后算周长即可。时间复杂度 (Q * 50 * logn)。

反思:

比赛时候看到求区间第几大数值问题,但是没想到用主席树,尝试用另一种区间排序技巧做了下,直接tle就很难受。

这里记一下这个小技巧

一个数组,要求先对前n个数字排序(以方便后续操作);又要求对前n+i个数字排序;又要求对前n+j … 前n+k个数字排序(i、j、k的大小远小于n,且i、j、k间没有大小关系)。总之就是对一个不定的范围内数据要进行频繁的按大小顺序调用,但是这个范围边界变化不大,很多数据重叠,这样每次都对此次区间内数据排序,频繁排序的话很费时间。

例如一个数组{1,3,6,5,2,4,1,9,0},一共9个数字,下标0~8。要求: 
每次取一个区间,计算区间内(最大值−最小值)2+(次大值−次小值)2+(次次大值−次次小值)2+...的值。很容易想到对区间排个序,即可方便获得最大、次大值等。 
对1~5排序:{2,3,4,5,6} 
对1~6排序:{1,2,3,4,5,6} 
对2~5排序:{2,4,5,6} 
可以看出2、5、6始终在范围内,但每次都要针对所选区间重新排序,很麻烦。 
既然大部分数据一直出现在范围内,现在就希望能够一次排序,应对所有情况。

Key

继续使用上述的例子:Array={1,3,6,5,2,4,1,9,0} 
开个新数组作其索引:Index={0,1,2,3,4,5,6,7,8} 
令索引数组按照Array的大小关系排序,得Index={8,0,6,4,1,5,3,2,7}

对于区间[1, 5]:从左向右找出第一个在[1, 5]的下标即为最小值:8不符合、0不符合、6不符合,4符合,那么最小值就是Array[4]=2,次大值就是Array[1]=3 …

即每次只需检测排序后当前位的数字的下标是否在该区间内即可。

题目:https://hihocoder.com/problemset/problem/1384 
一道贪心的题,期间需要对下标i到j、i到j+k之间的数字分别排序。是别人家的代码(他的原文链接,虽然我也不知道他是不是转别人的),就是在这学到的技巧。注意观察judge函数与judge2函数的差异,judge2函数即实现了上述排序思想。

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <algorithm>  
#include <cmath>  
using namespace std;  
typedef long long int ll;  
ll m,n,k;  
ll a[500050];  
ll b[500050];  
int cnt=0;  
inline bool cmp(int x,int y)  
{  
    return a[x]<a[y];  
}  
bool judge(int l,int r)  
{  
    int pos=0;  
    while(l+pos<=r)b[pos]=a[l+pos],pos++;  
    sort(b,b+pos);  
    pos--;  
    int mid=(pos-1)/2;  
    ll res=0;  
    for(int i=0; i<=mid&&i<m; i++)  
    {  
        res+=(b[i]-b[pos-i])*(b[i]-b[pos-i]);  
        if(res>k)  
            return false;  
    }  
    return res<=k;  
}  
void init(int l,int r)  
{  
    cnt=0;  
    for(int i=l; i<=r; i++)b[cnt++]=i;  
    sort(b,b+cnt,cmp);  
}  
bool judge2(int r)  
{  
    int i,j,kk;  
    ll res=0;  
    for(i=0,j=cnt-1,kk=m; kk; i++,j--,kk--)  
    {  
        while(i<j&&b[i]>r)i++;  
        while(i<j&&b[j]>r)j--;  
        if(i>=j)break;  
        res+=(a[b[i]]-a[b[j]])*(a[b[i]]-a[b[j]]);  
        if(res>k)break;  
    }  
    return res<=k;  
}  
int main()  
{  
    int T;  
    scanf("%d",&T);  
    while(T--)  
    {  
        scanf("%lld%lld%lld",&n,&m,&k);  
        for(int i=0; i<n; i++)  
            scanf("%lld",&a[i]);  
        int ans=0;  
        int l=0;  
        while(l<n)  
        {  
            int kk=1;  
            while(kk+l<n&&judge(l,l+kk))  
                kk*=2;  
            int first=l+kk/2,last=l+kk-1<n?l+kk-1:n-1;  
            init(l,last);  
            int mid;  
            int pos=l;  
            while(first<=last)  
            {  
                if(judge2(mid=(first+last)/2))  
                {  
                    first=mid+1;  
                    pos=mid;  
                }  
                else  
                    last=mid-1;  
            }  
            l=pos+1;  
            ans++;  
        }  
        cout<<ans<<endl;  
    }  
    return 0;  
}

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+7;
const ll mod=998244353;
int a[N],aa[N],tmp[60];
int rt[N],t[N*40],ls[N*40],rs[N*40],tot=0;
int bd(int l,int r) {
	int now=++tot;
	t[now]=0;
	if(l==r) return now;
	int m=l+r>>1;
	ls[now]=bd(l,m);
	rs[now]=bd(m+1,r);
	return now;
}
int upd(int o,int l,int r,int x) {
	int now=++tot;
	t[now]=t[o]+1;
	if(l==r) return now;
	ls[now]=ls[o];
	rs[now]=rs[o];
	int m=l+r>>1;
	if(x<=m) ls[now]=upd(ls[o],l,m,x);
	else rs[now]=upd(rs[o],m+1,r,x);
	return now;
}
int que(int o,int oo,int l,int r,int k) {
	if(l==r) return (t[oo]-t[o]>=k)?l:0;
	int m=l+r>>1;
	int tmp=t[rs[oo]]-t[rs[o]];
	if(tmp>=k) return que(rs[o],rs[oo],m+1,r,k);
	else return que(ls[o],ls[oo],l,m,k-tmp);
}
int main() {
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF) {
		int nn=n;
		tot=0;
		for(int i=1; i<=n; i++) {
			scanf("%d",&a[i]);
			aa[i]=a[i];
		}
		sort(aa+1,aa+1+n);
		n=unique(aa+1,aa+1+n)-aa-1;
		rt[0]=bd(1,n);
		for(int i=1; i<=nn; i++) {
			a[i]=lower_bound(aa+1,aa+1+n,a[i])-aa;
			rt[i]=upd(rt[i-1],1,n,a[i]);
		}
		for(int i=1,l,r; i<=m; i++) {
			scanf("%d%d",&l,&r);
			ll ans=-1;
			for(int i=1; i<=50; i++) {
				tmp[i]=aa[que(rt[l-1],rt[r],1,n,i)];
			}
			for(int i=1,j=2,k=3; k<=50&&tmp[k]; i++,j++,k++) {
				if(0ll+tmp[j]+tmp[k]>tmp[i]) {
					ans=0ll+tmp[i]+tmp[j]+tmp[k];
					break;
				}
			}
			printf("%lld\n",ans);
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值