Shopping in Mars is quite a different experience. The Mars people pay by chained diamonds. Each diamond has a value (in Mars dollars M$). When making the payment, the chain can be cut at any position for only once and some of the diamonds are taken off the chain one by one. Once a diamond is off the chain, it cannot be taken back. For example, if we have a chain of 8 diamonds with values M$3, 2, 1, 5, 4, 6, 8, 7, and we must pay M$15. We may have 3 options:
- Cut the chain between 4 and 6, and take off the diamonds from the position 1 to 5 (with values 3+2+1+5+4=15).
- Cut before 5 or after 6, and take off the diamonds from the position 4 to 6 (with values 5+4+6=15).
- Cut before 8, and take off the diamonds from the position 7 to 8 (with values 8+7=15).
Now given the chain of diamond values and the amount that a customer has to pay, you are supposed to list all the paying options for the customer.
If it is impossible to pay the exact amount, you must suggest solutions with minimum lost.
Input Specification:
Each input file contains one test case. For each case, the first line contains 2 numbers: N (≤105), the total number of diamonds on the chain, and M (≤108), the amount that the customer has to pay. Then the next line contains N positive numbers D1 ⋯ DN (Di ≤103 for all i=1,⋯,N) which are the values of the diamonds. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print i-j in a line for each pair of i ≤ j such that Di + … + Dj = M. Note that if there are more than one solution, all the solutions must be printed in increasing order of i.
If there is no solution, output i-j for pairs of i ≤ j such that Di + … + Dj >M with (Di + … + Dj −M) minimized. Again all the solutions must be printed in increasing order of i.
It is guaranteed that the total value of diamonds is sufficient to pay the given amount.
Sample Input 1:
16 15
3 2 1 5 4 6 8 7 16 10 15 11 9 12 14 13
Sample Output 1:
1-5
4-6
7-8
11-11
Sample Input 2:
5 13
2 4 5 7 9
Sample Output 2:
2-4
4-5
题目大意: 火星人用钻石链购物,输入链上钻石数N和需支付的金额M,随后给出N个钻石的价值。找出链上所有和等于M的连续钻石链,若没有,则找出所有大于且最接近M的连续钻石链,输出按链中第一个钻石序号升序输出。
一开始的想法就是以 i 为起点( i = 0,1,2,…,N),每次 j 从 i 遍历到N,找出并存储每次第一个和≥M的链,然后输出最小的链。如下:
#include<bits/stdc++.h>
using namespace std;
struct part
{
int i,j;
int value;
};
bool cmp(part a,part b)
{
if(a.value!=b.value)
return a.value<b.value;
else
return a.i<b.i;
}
int main()
{
int a[100010],N,M;
vector<part> ans;
int i,j,sum;
cin>>N>>M;
for(i=1;i<=N;i++)
scanf("%d",&a[i]);
for(i=1;i<=N;i++)
{
sum=0;
for(j=i;j<=N;j++)
{
sum+=a[j];
if(sum>=M) //第一个 ≥M的链
{
part t={i,j,sum};
ans.push_back(t);
}
}
}
sort(ans.begin(),ans.end(),cmp);
for(i=0;ans[i].value==ans[0].value;i++) //输出符合 ≥M的最小链
printf("%d-%d\n",ans[i].i,ans[i].j);
return 0;
}
这个在测试点2,3,5会超时,有两层循环,很明显该算法的时间复杂度为O( N2 ),
那么就要进行相应的优化。
可以看到在第二层循环中, i 不变,那么每次求出来的和是递增序列,那么就可以使用二分法来找到第一个令和 ≥M的 j 。将时间复杂度优化为O(NlogN)。
那么又有一个问题,因为二分法不是顺序遍历,每次都要对区间重新求和,若挨个相加又会增加时间。这里又要用到一个方法:用一个s[maxn] 数组来存储 1 ~ i 的和,那么i ~ j 的和就等于s[j]-s[i-1]。
最终代码如下:
#include<bits/stdc++.h>
using namespace std;
struct part
{
int i,j;
int value;
};
int s[100010],a[100010],N,M;;
int find_j(int L,int R,int i) //二分法找到第一个令和 ≥M的 j
{
while(L<R) //L==R时即退出,得到目标
{
int mid=(L+R)/2;
if(s[mid]-s[i-1]>=M)
R=mid;
else
L=mid+1;
}
if(s[L]-s[i-1]>=M) //注意,有可能区间内没有符合条件的
return L; //此时会有L==R==N,但并非正确值,所以还要进行判断。
else
return 0; //不符合返回0
}
bool cmp(part a,part b)
{
if(a.value!=b.value)
return a.value<b.value;
else
return a.i<b.i;
}
int main()
{
vector<part> ans;
scanf("%d %d",&N,&M);
int i,j;
s[0]=0;
for(i=1;i<=N;i++)
{
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];
}
for(i=1;i<=N;i++)
{
if(j=find_j(i,N,i))
{
part t={i,j,s[j]-s[i-1]};
ans.push_back(t);
}
}
sort(ans.begin(),ans.end(),cmp);
for(i=0;ans[i].value==ans[0].value;i++)
printf("%d-%d\n",ans[i].i,ans[i].j);
return 0;
}
总结: 利用二分法在有序序列中,可以寻找第一个满足某种条件的元素的位置。时间复杂度为O(logN)