今天做了一道連續和的題,並且把相關的知識串了一下。
題目
這道題目是求下標,與我們往常見到的連續和不太一樣,所以這道用普通的方法就可以解出。
首先需要知道的一點是用快讀的話讀取不了文件的數據。
關於快讀
首先介紹一下快讀,getchar()相對於scanf(),cin這些讀入是最快的,快讀也是利用這個特點,但一般我們是遇到有很大數據讀入量的時候,就是出題人要卡讀入數據的時候,這時候快讀的優勢就出來了,這裏用到的快讀是簡單版,還有其他可以讀入浮點型等…但一般這個普通快讀就夠用了。
關於題目思路
看題目,要求的是求出下標,注意也有要求:如果有多組滿足條件的x和y,x 應儘量小。如果還有多解y也應儘量小。這裏我們就可以維護幾個變量,包括答案,還有記錄當前最大前綴和,和左下標temp,用sum表示前綴和。循環一次,每次循環讓sum加上當前a[i],然後進行判斷,如果前綴和比當前最大前綴和大的話,就更新左右下標還有當前最大前綴和;再進行另外的一個判斷,如果sum<=0的話,就重置sum=0,並更新temp。
//HRBUST - 1684
#include<cstdio>
#include<cstring>
//# define LOCAL //用快讀讀不了文件
using namespace std;
typedef long long ll;
const int maxn = 50005;
ll a[maxn];
inline int read(){
char ch=getchar();int x = 0,f = 1;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main(){
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int t = read();
while(t--){
memset(a,0,sizeof(a));
int n=read(),m=read();
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
while(m--){
int p=read(),q=read();
ll sum = 0;
ll m = -0x3f3f3f3f;//記錄上一個最大前綴和
int x = p,y = q,temp = p;
for(int i=p;i<=q;i++){
sum += a[i];
if(sum > m){
m = sum;
x = temp;
y = i;
}
if(sum <= 0){
sum = 0;
temp = i+1;
}
}
printf("%d %d\n",x,y);
}
}
return 0;
}
題外話1
我看到這道題的第一反應是想到的是求前綴和,也算是熟悉一下這個複雜度只有O(nlogn)求最大前綴和的算法,如下:
ll maxsum(int x,int y){
if(y-x==1) return a[x];
int m = x+(y-x)/2;
ll maxs = max(maxsum(x,m),maxsum(m,y));
ll v,L,R;
v = 0,L = a[m-1];
for(int i=m-1;i>=x;i--)
L = max(L,v+=a[i]);
v = 0,R = a[m];
for(int i=m;i<y;i++)
R = max(R,v+=a[i]);
return max(maxs,L+R);
}
這代碼用到了“賦值運算本身具有返回值”的特點,一定程度上簡化了代碼,而不會犧牲可讀性。
關於算法,這裏用到了分治法
- 劃分問題:把問題的實例劃分成子問題
- 遞歸求解:遞歸解決子問題
- 合併問題:合併子問題的解得到原問題的解
在上面代碼中,其實算是二分的思想,就是對區間不斷地劃分兩塊,最後只有一個元素的返回,分別對兩邊的區間求其的最大連續和,然後最後與總區間的最大連續和進行比較取最大。在這裏用到二分的一個技巧,取分界點的時候是用m = x + (y - x)/2;用來確保分界點總是靠近區間的起點。
題外話2
當然還有O(N^2)複雜度的算法;
就是利用遞推求得前綴和,再用二重循環更新前綴和的最大值:
s[0] = 0;
int best = 0;
for(int i=1;i<=n;i++)
s[i] = s[i-1] + a[i];
for(int i = 1;i <= n;i++)
for(int j = i;j <= n;j++)
best = max(best,s[j]-s[i-1]);