二分與三分(精度類型)

二分:傳送門
三分:傳送門
(注意,是五舍六入,不是四捨五入,在2018年10月23日前是這樣的)
話說一本通上不是有講嘛,做法自己看吧。。。(但是我太弱了,精度版看不懂QWQ)。

簡單講一下二分與三分吧。

二分:必須滿足單調性:
在這裏插入圖片描述

非增或非減就叫單調性(如果就好幾個數相同,一般會用二分來找第一個數或最後一個數)。

我們用兩個數字l與r來代表搜索範圍,而mid代表中間的位置的值,來跳來跳去,看情況來寫。

這題

int  l=1,r=n,mid,ans=0;
while(l<=r)
{
    mid=(l+r)/2;
    if(a[mid]<=m)l=mid+1,ans=mid;
    else  if(a[mid]>m)r=mid-1;
}
printf("%d\n",ans);

當然,還可以用二分來二分答案,比如你要用某種方法,但是缺少一種條件,你又意外發現這個條件越大或越小,會讓你方法越容易達到目標(也就是發現了二分性),就可以二分這個條件,不斷丟給這個方法,讓他運行。

這題

//發現這道題如果二分總和,總和越大,分的段數就越少,越容易小於等於m,也就越容易達到目標,因此得出做法
#include<cstdio>
#include<cstring>
using  namespace  std;
int  a[210000],b[210000],n,m;
bool  pd(int  x)
{
	int  kk=1/*自行理解*/,ans=0;
	for(int  i=1;i<=n;i++)
	{
		if(a[i]>x)return  false;//如果有一個數超過了,就退出
		if(ans+a[i]<=x)ans+=a[i];//加上
		else
		{
			kk++;/*統計答案*/if(kk>m)return  false;//分的段數過多,退出
			ans=a[i];//重置
		}
	}
	return  true;
}
int  main()
{
	int  sum=0;
	scanf("%d%d",&n,&m);
	for(int  i=1;i<=n;i++)scanf("%d",&a[i]),sum+=a[i];
	int  l=1/*這裏可以改成a數組的最大值)*/,r=sum/*統計所有的和*/,mid,x;
	while(l<=r)
	{
		mid=(l+r)/2;
		if(pd(mid)==true)//如果可以,代表可以讓r再收攏一點
		{
			r=mid-1;x=mid;
		}
		else  l=mid+1;//不行,則擴寬l的限制
	}
	printf("%d\n",x);
	return  0;
}

(我不是標題狗)

但是開頭的那道二分題,是要用精度的!!!

看別人的代碼,也都是大把大把的double,醜陋的代碼:

#include<cstdio>
#include<cstring>
using  namespace  std;
double  a[210000],sum[210000],b[210000];
int  n,L;
double  mymin(double  x,double  y){return  x<y?x:y;}
double  mymax(double  x,double  y){return  x>y?x:y;}
bool  check(double  x)//這個上網搜搜都是有的
{
	double  min_val=999999999.0,ans=-99999999.0;
	for(int  i=1;i<=n;i++)b[i]=a[i]-x,sum[i]=b[i]+sum[i-1];
	for(int  i=L;i<=n;i++)//用DP搜索一段長度大於等於L的子串的最大和
	{
		min_val=mymin(min_val,sum[i-L]);
		ans=mymax(ans,sum[i]-min_val);
	}
	return  ans>=0.0;//猴子已經死機。。。上網搜吧。。。
}
int  main()
{
	scanf("%d%d",&n,&L);
	for(int  i=1;i<=n;i++)scanf("%lf",&a[i]);
	double  l=-1e6,r=1e6,mid,ans=0.0,jie=1e-5;//猴子已死機。。。
	while(r-l>jie)
	{
		mid=(l+r)/2;
		if(check(mid))ans=mid,l=mid;
		else  r=mid;
	}
	printf("%d\n",int(r*1000.0));
	return  0;
}



還是上網弄了一個下來

這題可以用斜率優化做也可以用二分做,我用的是二分做法。

題意:給你n個牛的自身價值,讓你找出連續的且數量大於等於F的一段區間,使這段區間內的牛的平均價值最大。

思路:用二分枚舉平均值ave,每個牛的價值都減去ave,看是否有連續的超過f長度的區間使得這段區間的價值大於等於0,如果能找到,那麼說明這個平均值可以達到。先每個a[i]減去ave得到b[i],用dp[i]表示以i爲結尾區間連續長度大於等於f的最大連續區間和,maxx[i]表示以i爲結尾的最大連續區間和,sum[i]表示1~i的價值總和那麼maxx[i]=max(maxx[i-1]+b[i],b[i]),dp[i]=maxx[i-f+1]+sum[i]-sum[i-f+1],判斷是否有一個i(i>=f)滿足dp[i]>=0.

作者:Herumw
來源:CSDN
原文:https://blog.csdn.net/kirito_acmer/article/details/48716719
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!





如果你是神犇看懂了=_=。哥哥,我們不約

好的,如果你不是神犇,那麼請:
在這裏插入圖片描述

不過,有時,我們不一定要用double,我們乘以一個1000轉成int,然後在check再暫時轉回double

代碼(摘自我機房大佬CLB的代碼):

//Sorry,之前放錯代碼了
#include<cstdio>
#include<cstring>
#define INF 2147483647
using namespace std;
int N,L,a[110000];
long long sum[110000];
inline bool check(int x)
{
	for(int i=1;i<=N;i++) sum[i]=sum[i-1]+(a[i]-x);//減去平均值,看看總和是否能爲非負數 
	long long minn=INF;
	for(int i=L;i<=N;i++)
	{
		if(sum[i-L]<minn) minn=sum[i-L];
		if(sum[i]-minn>=0) return true;//這裏是把拖後腿那一部分減掉,如果爲正數,就表示此方案可行 
	}
	return false;//此方案不行 
	
}
int main()
{
	scanf("%d%d",&N,&L);
	for(int i=1;i<=N;i++) scanf("%d",&a[i]),a[i]*=1000;//最後結果要乘1000,爲了方便計算(不算小數),就在開始直接乘了 
	int l,r,mid,ans;
	l=0;r=2000000;
	while(l<=r)//二分平均值 
	{
		mid=(l+r)>>1;
		if(check(mid)==true) l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d\n",ans);
	return 0;
}

到三分了,三分呢,主要解決單峯問題(求單峯),不過遞增或遞減也可以喲不過只是求第一個數或最後一個數

所謂三分,肯定是把區間用兩個數(記作m1與m2,m1<=m2)分成三個部分(除了某些特殊情況:n=2…),然後將這兩個數比較,然後l跳到m1+1,r跳到m2-1,然後當l>r退出,由於這裏比二分還複雜,所以猴子還沒找到直接記錄答案的方法,只能最後比較l與r,雖然一次只縮小1/31/3

但是總比某退火的玄學複雜度快吧。

舉個栗子(二次函數:開口向上):

在這裏插入圖片描述

以x軸做三分,那麼m1(A點)的y小於m2(B點)的y,那麼我們就讓r跳到比m2小一點的地方(按題目來定),反之讓l跳到比m1大一點點的位置,來達到我們將l與r縮小的目的。
至於突然退化成一次函數的二次函數(某毒瘤出題人乾的),與二次函數的情況一樣,不過l或r有一個不變罷了。。。

僞例題:

一個序列,其中有一個數,這個數左邊的序列嚴格遞增,左邊嚴格遞減,右邊嚴格遞增。

一個整數n
n個數字

輸出這個數字:

樣例輸入:
5
1 2 3 2 1
樣例輸出:
3

int  l=1,r=n,m1,m2;
while(l<=r)
{
	m1=l+(r-l+1/*+1不+1都可以*/)/3/*爲什麼不+1?如果l==r,m1就跳出去了,m2也是同理*/;m2=r-(r-l+1)/3;
	if(a[m1]<=a[m2]/*<與<=都可以*/)r=m2-1;
	else  l=m1+1;//縮小範圍
}
if(a[l]<a[r])printf("%d\n",a[l]);
else  printf("%d\n",a[r]);

至於如果有一段的值相等,這種情況我認爲是可以的,歡迎大家再我的下方評論,畢竟三分剛學不久。。。

如這種情況
比如上圖。。。

然後,又到了開頭的那道三分了。。。

又是精度問題!

至於單峯性。。。


題目:給你n條開口向上的二次曲線Si(a>0),定義F(x) = max(Si(x)),求F(x)的最小值。

分析:三分。F(x)是一個單峯函數,先單調遞減後單調遞增,利用三分求最小值。

        首先,證明兩個二次函數構造的F2(x)爲單峯函數;

        (如果不成立,則存在兩個連續的波谷,交點處一個函數遞增另一個遞減,矛盾,不會取遞減函數)

        然後,用數學歸納法證明,Fi(x)爲單峯函數,則Fi+1 = max(Fi(x),Si+1(x))也是單峯函數;

        (如果存在兩個(或更多)連續的波谷,交點處一個函數遞增另一個遞減,矛盾,所以只有一個波谷)

        結論,綜上所述得證結論,只存在一個波谷。

作者:小白菜又菜
來源:CSDN
原文:https://blog.csdn.net/mobius_strip/article/details/45618095
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!


看不懂自己YY吧,啊啊啊啊。

難道又要動用我們毒瘤可愛的double了?
不,我拒絕!!!

既然保留四位小數,又五舍六入,那麼乘100000啦!

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  ll;
ll  a[200000],b[200000],c[200000],n;
inline  double  mymax(double  x,double  y){return  x>y?x:y;}
inline  double  cai(ll  x)//從所有函數中選最大值 
{
	double  xx=x/100000.0;//變回double
	double  mmax=-999999999;
	for(ll  i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
	return  mmax;
}
int  main()
{
	ll  T;scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld",&n);
		for(int  i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
		ll  l=0,r=1e8/*1*10的8
		次方*/,m1,m2,ans;//三分日常操作 
		while(l<=r)
		{
			m1=l+(r-l+1)/3;m2=r-(r-l+1)/3;
			if(cai(m1)<=cai(m2))r=m2-1;
			else  l=m1+1;
		}
		if(cai(l)<cai(r))printf("%.4lf\n",cai(l));
		else  printf("%.4lf\n",cai(r));//統計答案 
	}
	return  0;
}

在這裏插入圖片描述

。。。
作者可能學了個假的三分。。。

不過,如果乘以一百萬(多乘了個10)。。。
在這裏插入圖片描述
猴子(作者)想了一個壞想法,於是我用了高精度1e10與1e11,終於,在1e11時卡精度AC了!

AC代碼:

//猴子將double*1e8將他轉爲long  long
#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  ll;
ll  a[200000],b[200000],c[200000],n;
inline  double  mymax(double  x,double  y){return  x>y?x:y;}
inline  double  cai(ll  x)//轉回long long;
{
	double  xx=x/100000000.0;
	double  mmax=-999999999;
	for(ll  i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
	return  mmax;
}
int  main()
{
	ll  T;scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld",&n);
		for(int  i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
		ll  l=0,r=1e11,m1,m2,ans;
		while(l<=r)
		{
			m1=l+(r-l+1)/3;m2=r-(r-l+1)/3;
			if(cai(m1)<cai(m2))r=m2-1;
			else  l=m1+1;//三分
		}
		if(cai(l)<cai(r))printf("%.4lf\n",cai(l));
		else  printf("%.4lf\n",cai(r));
	}
	return  0;
}

所以,帶精度的二分與三分是可以轉成long long來做的,在check裏再轉會double就行了,不過三分可能要多乘一點。

終於寫完了。

每日笑話:

追到我的女神 我用了三個辦法 辦法一 堅持 辦法二 不要臉 辦法三 堅持不要臉 她帶我回家 她爸爸很無禮地跟我說 我養了我女兒二十年 我憑什麼把她嫁給你 我回答 你養她二十年 我要養她四十年 還要照顧你三十年 你憑什麼不把她嫁給我
--------來源

在這裏插入圖片描述

--------來源

感謝大家觀看。

就怪了!

難道大家沒發現三分會比二分慢嗎?
啊啊啊啊啊!
但是,我們可以將m1=(l+r)/2;m2=m1+1;
這樣子,如果比較完後,l=m2或者r=m1,正確性的話雖然m1與m2靠的很近,不過依舊可以達到單峯在l與r之間,就對了,不過是l<r不是l<=r,因爲這樣分m1與m2嚴格m1<m2且m1與m2屬於[l,r],所以只能用l<r,不過這樣有個好處,就是由於l或r都是等於m1或m2的,所以不會出現r<l的情況,最後一定l==r,所以答案就是l或r,而且一次縮小的區間變大了,常數也就小了!

代碼:

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  ll;
ll  a[200000],b[200000],c[200000],n;
inline  double  mymax(double  x,double  y){return  x>y?x:y;}
inline  double  cai(ll  x)
{
	double  xx=x/100000000.0;
	double  mmax=-999999999;
	for(ll  i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
	return  mmax;
}
int  main()
{
	ll  T;scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld",&n);
		for(int  i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
		ll  l=0,r=1e11,m1,m2,ans;
		while(l<r)//常數小的三分 
		{
			m1=(l+r)>>1;m2=m1+1;
			if(cai(m1)<cai(m2))r=m1;
			else  l=m2;
		}
		printf("%.4lf\n",cai(r/*可以換成l*/));
	}
	return  0;
}
//開心! 

是不是意味着我們可以出毒瘤卡常題目了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章