【題目鏈接】
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=36267
【解題報告】
題目大意:
給你一個長度爲N的01串,求長度>=L的子串中,含有1的個數比子串長度最大的子串的左右端點。
簡單來說,就是假如我維護一個前綴和sum[i]表示前i個數中1的個數,那麼求最大的( sum[i]-sum[j] ) / (i-j)
也就是說,給定的點集中,求最大斜率。
可以分析得到:sum[i]是單調遞增的,所以如果有:
k<j<i,sum[k],sum[j],sum[i]是一個上凸的點集。
那麼j一定是不可取的起點。
因爲kj的斜率大於ji,而i後面的點p,會落在i的右上角,所以kp仍然是kp,jp,ip中最大的斜率。我們只需要保留k和i點即可
由此可知,上凸的端點可以直接被捨棄掉(注意,我們捨棄它的時候一定已經更新過它作爲終點的答案),那麼我們每加入一個端點的時候,一定是把它插入到一個下凸的點集裏。每次我們用隊列尾部的線段來更新ans值即可。
注意每次查詢的時候要有一步操作,因爲我們每次是取隊首,但是對於一個i,它和隊列某點的切線值顯然是當前最大k,但這個切點不一定是隊首,所以要將切點前的點全部出隊。如果有兩條切線重合(確實存在這樣的數據),那麼我們應當取較短的切線,所以仍然要執行出隊操作。
代碼能力比較弱,寫這道題仍然覺得繁難(wa了11次……),參考了一篇題解,這裏是採用了定義了一個line類的方式,我覺得代碼頓時清晰了很多,所以要學習這種模板。能夠最大限度的來清晰的實現自己的思路。
http://blog.csdn.net/keshuai19940722/article/details/19629545?utm_source=tuicool&utm_medium=referral
————BearChild《uva 1451 - Average(數形結合)》
【參考代碼】
#include <cstdio>
#include <cstring>
#include<iostream>
#include<deque>
#include<cmath>
using namespace std;
int N,L;
deque<int>q;
char str[100000+100];
int sum[100000+100];
double get_K( int i, int j )
{
return (sum[i]-sum[j])*1.0/(double)( i-j );
}
bool eps( double a )
{
if( a<-1e-6 )return true;
else return false;
}
bool eql( double a )
{
if( fabs(a)<1e-6 )return true;
return false;
}
int main ()
{
// freopen( "uva1451.txt","r",stdin );
int T; scanf( "%d",&T );
while( T-- )
{
scanf( "%d%d",&N,&L );
q.clear();
scanf( "%s", &str[1] );
memset( sum, 0,sizeof sum );
for( int i=1; i<=N; i++ )
{
sum[i]=sum[i-1];
if( str[i]=='1' ) sum[i]+=1;
}
double ans=-1; //賦值爲0是不行的......
int left=0,right=N; //這裏把right也賦值爲0,wa了一次
for( int i=L; i<=N; i++ )
{
int j=i-L;
while( q.size()>1 ) // 把i-L推進去
{
int k=q[ q.size()-1 ], m=q[ q.size()-2 ];
// if( get_K( j,k ) <get_K( k,m ) )q.pop_back();
if( eps( get_K(j,k)-get_K(k,m) ) )q.pop_back();
else break;
}
q.push_back(j);
while( q.size()>1 )//找到i和隊列的切點
{
int k=q[0], m=q[1];
// if( get_K( i,k )>get_K(m,k) )q.pop_front();
if( eps( get_K(i,k)- get_K( i,m ) ) || eql( get_K(i,k)- get_K( i,m ) ) )q.pop_front(); //這裏,如果斜率相同應該彈出
else break;
}
if( eps(ans-get_K( i, q[0] )) || (eql(ans-get_K(i,q[0]))&&(i-q[0]<right-left)) ) //這裏如果不加eps判斷,那麼如果後面有相同的ans值就會更新。
{
//這裏的bug找了最久,用12 6 111000111001這組數據卡掉的,顯然如果後面有ans相同的,如果len更短那麼也應該更新
ans=get_K( i,q[0] );
left=q[0]; right=i;
}
}
printf( "%d %d\n",left+1,right );
}
return 0;
}