【題目大意】
給出一個整數列,求一段子序列之和最接近所給出的t。輸出該段子序列之和及左右端點。
【思路】
……前綴和比較神奇的想法。一般來說,我們必須要保證數列單調性,才能使用尺取法。
預處理出前i個數的前綴和,和編號i一起放入pair中,然而根據前綴和大小進行排序。由於abs(sum[i]-sum[j])=abs(sum[j]-sum[i]),可以忽視數列前綴和的前后關系。此時,sum[r]-sum[l]有單調性。
因此我們可以先比較當前sum[r]-sum[l]與t的差,並更新答案。
如果當前sum[r]-sum[l]<t,說明和還可以更大,r++。
同理,如果sum[r]-sum[l]>t,說明和還可以更小,l++。
如果sum[r]-sum[l]=t,必定是最小答案。
【注意點】
由於序列不能為空,即l<>r,如果l=r則r++。
我們更新答案的時候左右區間端點為亂序,輸出的時候調整一下。
就OK了!
*本來想要尺取弄一個合集,然而這道題做的時候想了半天。還是單獨拿出來吧orz
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 const int MAXN=1e5+5; 8 const int INF=2147483640; 9 pair<int,int> sum[MAXN]; 10 int n,k,t; 11 12 void init() 13 { 14 sum[0]=make_pair(0,0); 15 int tmp=0; 16 for (int i=1;i<=n;i++) 17 { 18 int x; 19 scanf("%d",&x); 20 tmp+=x; 21 sum[i]=make_pair(tmp,i); 22 } 23 sort(sum,sum+n+1); 24 } 25 26 void solve() 27 { 28 scanf("%d",&t); 29 int l=0,r=1,minans=INF,ans,ansl,ansr; 30 while (r<=n && minans)//這里一開始寫成了ans,以后變量名不要取那么相像orz 31 { 32 int delta=sum[r].first-sum[l].first; 33 if (abs(delta-t)<=minans) 34 { 35 minans=abs(delta-t); 36 ans=delta; 37 ansl=sum[l].second; 38 ansr=sum[r].second; 39 } 40 if (delta<t) r++; 41 if (delta>t) l++; 42 if (l==r) r++;//☆注意序列不能為空! 43 } 44 if (ansl>ansr) swap(ansl,ansr);//注意排序后是無序的,左右區間要調整回有序 45 printf("%d %d %d\n",ans,ansl+1,ansr); 46 } 47 48 int main() 49 { 50 while (scanf("%d%d",&n,&k)!=EOF) 51 { 52 init(); 53 for (int i=1;i<=k;i++) solve(); 54 } 55 return 0; 56 }