POJ_2566_Bound_Found_(尺取法+前缀和)

描述


http://poj.org/problem?id=2566

给出一个整数序列,并给出非负整数t,求数列中连续区间和的绝对值最接近k的区间左右端点以及这个区间和的绝对值.

 

Bound Found
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 2592 Accepted: 789 Special Judge

Description

Signals of most probably extra-terrestrial origin have been received and digitalized by The Aeronautic and Space Administration (that must be going through a defiant phase: "But I want to use feet, not meters!"). Each signal seems to come in two parts: a sequence of n integer values and a non-negative integer t. We'll not go into details, but researchers found out that a signal encodes two integer values. These can be found as the lower and upper bound of a subrange of the sequence whose absolute value of its sum is closest to t.

You are given the sequence of n integers and the non-negative target t. You are to find a non-empty range of the sequence (i.e. a continuous subsequence) and output its lower index l and its upper index u. The absolute value of the sum of the values of the sequence from the l-th to the u-th element (inclusive) must be at least as close to t as the absolute value of the sum of any other non-empty range.

Input

The input file contains several test cases. Each test case starts with two numbers n and k. Input is terminated by n=k=0. Otherwise, 1<=n<=100000 and there follow n integers with absolute values <=10000 which constitute the sequence. Then follow k queries for this sequence. Each query is a target t with 0<=t<=1000000000.

Output

For each query output 3 numbers on a line: some closest absolute sum and the lower and upper indices of some range where this absolute sum is achieved. Possible indices start with 1 and go up to n.

Sample Input

5 1
-10 -5 0 5 10
3
10 2
-9 8 -7 6 -5 4 -3 2 -1 0
5 11
15 2
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
15 100
0 0

Sample Output

5 4 4
5 2 8
9 1 1
15 1 15
15 1 15

Source

分析


尺取法.

一眼看过去就是用尺取法找和t绝对值相差最小的区间和,但是这道题里的序列并不是非负的,这意味着固定左端点,移动右端点时,区间和不是单调递增的.尺取法的模板题中区间和是单调的,所以找到大于某一值的区间右端点就可以固定.而这道题中因为存在负值,所以区间的变化不是单调的,不能按照普通的方法解决.我们考虑一段区间和,不仅可以认为是a[l]+a[l+1]+...+a[r],还可以看作是sum[r]-sum[l-1],而这道题中要求的区间和的绝对值,就可以看作是max(sum[r],sum[l-1])-min(sum[r],sum[l-1]).这样求一个前缀和,进行排序,双指针确定区间左右端点,这样sum[r]-sum[l](排序后的编号)就代表一个区间和的绝对值,这样的区间就是单调的.如此一来,确定左端点,移动右端点,sum[r]-sum[l]就是单增的,找到sum[r]-sum[l]>t的位置即可停止,然后l++,sum[r-1]-sum[l]比之前的sum[r-1]-sum[l]更小(减数增大),本来就比t小,现在小得更多了,对于任意r'<=r-1都是如此,就不必考虑,从sum[r]-sum[l]开始继续即可.

 

注意:

1.sum[r]-sum[l-1],因为1<=l<=r,所以l-1>=0,注意sum[0].s=0,sum[0].num=0,要参与排序,并且每一次都要重新赋值,因为上一组数据排序后sum[0].s不一定是0,这将影响到语句"sum[i]=point(sum[i-1].s,i)" .

2.对于区间右端点表示的状态,最好是用来表示当前右端点的位置,然后每一次更新状态时都要判断,与ans比较,看是否更新最优解.

3.由于r>=l所以r>l-1,也就是说两个区间端点不能是同一个点,当l追上r时,r++.

4.其实尺取法的写法可以优化,现在的写法是两层循环,l改变后进行一次操作,然后对于同一个l改变r,每次进行一次操作,这样在一个外层循环内部要写两遍操作.其实对于l和r的改变是一样,都是区间状态的改变,所以只要一层循环即可.

  while (l<=n&&r<=n&&ans!=0) 

 

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<algorithm>
 4 using std :: sort;
 5 using std :: min;
 6 using std :: max;
 7 
 8 const int maxn=100005,INF=0x7fffffff;
 9 
10 int a[maxn],s[maxn];
11 int n,q,t;
12 
13 struct point
14 {
15     int s,num;
16     point() {}
17     point(int a,int b) : s(a) , num(b) {}
18 }sum[maxn];
19 
20 bool comp(point x,point y) { return x.s<y.s; }
21 
22 int value(int l,int r) { return abs(sum[r].s-sum[l].s); }
23 
24 int L(int l,int r) { return min(sum[l].num,sum[r].num)+1; }
25 
26 int R(int l,int r) { return max(sum[l].num,sum[r].num); }
27 
28 
29 void solve(int t)
30 {
31     int ans=INF,res,idxl,idxr;
32     int r=1;
33     for(int l=0;l<=n;l++)
34     {    
35         if(l==r) r++;
36         if(r>n) break;
37         int now=value(l,r);
38         int d=abs(now-t);
39         if(d<=ans)
40         {
41             ans=d;
42             res=now;
43             idxl=L(l,r);
44             idxr=R(l,r);
45         }
46         while(r<n&&now<t)
47         {
48             r++;
49             now=value(l,r);
50             int d=abs(now-t);
51             if(d<=ans)
52             {
53                 ans=d;
54                 res=now;
55                 idxl=L(l,r);
56                 idxr=R(l,r);
57             }
58         }
59         if(now<t) break;        
60         if(now==t)
61         {
62             break;
63         }
64     }
65     printf("%d %d %d\n",res,idxl,idxr);
66 }
67 
68 void init()
69 {
70     while(scanf("%d%d",&n,&q)&&(n!=0||q!=0))
71     {
72         sum[0].s=0;
73         sum[0].num=0;
74         for(int i=1;i<=n;i++)
75         {
76             scanf("%d",&a[i]);
77             sum[i]=point(sum[i-1].s+a[i],i);
78         }
79         sort(sum,sum+n+1,comp);
80         for(int i=1;i<=q;i++)
81         {
82             scanf("%d",&t);
83             solve(t);
84         }
85     }
86 }
87 
88 int main()
89 {
90     freopen("bound.in","r",stdin);
91     freopen("bound.out","w",stdout);
92     init();
93     fclose(stdin);
94     fclose(stdout);
95     return 0;
96 }
View Code

 

转载于:https://www.cnblogs.com/Sunnie69/p/5423228.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值