プログラム設計の最終試験レビュートピック2:2つのポイント

2番目のトピックは、二分法アルゴリズムです。

基本的な使い方

二分法アルゴリズムは、順序付けられた配列の各半分を使用して、特定の要素を見つけるための判断を下すことです(要素は直接指定されたデータである場合もあれば、バイナリ検索で必要な答えである場合もあります)。

時間の複雑さ:O(logn)。

一般的な1次元配列の場合、指定された要素を検索するための基本的なアルゴリズム

int binarySearch(int a[],int n,int k)
{
	int l=0,r=n-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid]==k)
		    return mid;
		else if(a[mid]>k)
		    r=mid-1;
		else l=mid+1;
	}
	return -1;
}

上記は最も一般的な記述方法です。配列内に連続する要素が見つかる場合、上記のアルゴリズムはこれらの連続する要素の位置を考慮しません。要素が見つかる限り、その要素の添え字が返されます。例:5 5 5 5 6 7 8 9 10 11から要素5を検索すると、返されるインデックスは2になります。

次の2つの簡単な記述方法は、連続要素に一致する最小または最大の添え字を返すことができます。

検索する連続要素の最小座標を返します

ここでは、ansレコードの中央にある添え字に注意してください。mid=(l + r + 1)/ 2、r = midを同時に使用するか、0添え字を同時に使用しない場合、またはさまざまな無限ループを回避する他の方法も良くない。もっと例を試してください(例:5 5 5 6 7 find 5; 1 2 3 5 5 find 5;など)。したがって、ansレコードの中央にあるインデックスを直接使用し、rとlを変更するときにmidを考慮しないことをお勧めします。これにより、無限ループを完全に回避できます。(この方法は、結果と一致する最大座標を探すときにも使用されます)

int binarySearch1(int a[],int n,int k)
{
	int l=0,r=n-1,ans=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid]>=k)
		    r=mid-1,ans=mid;
		else l=mid+1;
	}
	return ans;
}

検索する連続要素の最大座標を返します

int binarySearch2(int a[],int n,int k)
{
	int l=0,r=n-1,ans=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid]<=k)
		    l=mid+1,ans=mid;
		else r=mid-1;
	}
	return ans;
}

以前は、二分法アルゴリズムの理解は最も一般的な検索アルゴリズムに限定されていましたが、二分法アルゴリズムは、1次元配列の特定の要素を検索するよりもはるかに多く使用されています。

上記は二分法の単純なアイデアとアプリケーションです。

二分法の一般的な考え方は、サイズnの問題をより小さいサイズのk(通常は2)のサブ問題分解することです。これらのサブ問題は互いに独立しており、元の問題と同じ性質を持っています。副問題の解決策を見つけてから、副問題を通じて元の問題の解決策を取得します。分岐アルゴリズムに属しています。

拡張使用法

1. 方程式の根を見つけます

一般に、この種の問題には、たとえばx ^ 3 + x ^ 2 + x + 23 = 0のような方程式が与えられます。この方程式の解を見つけ(または範囲を指定)、小数点以下の正確な桁数を指定します。

例:x ^ 6 + x-2014 = 0 in

0から正の無限大までの解については、小数点以下5桁が予約されています。

解決策:最初に、結果を見つけるために二分探索法を使用する必要があるため、この範囲内の関数式の単調性を判断する必要があります。単調増加関数であると判断された場合、バイナリ検索結果が見つかったときに判断関数を設定する必要があります。f(mid)* f(r)<= 0:解はmid〜rの間にあり、それ以外の場合はl〜midの間にあります。間。このようにして、左と右の境界を変更できます。さらに、この種の問題を行う場合は、自分で判断して一般的なスコープを設定するのが最善です。例[1,10]

コード:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const double EPS=1e-8;

double f(double x)
{
	return x*x*x*x*x*x + x - 2014;
}

int main()
{
	double l=1,r=10,mid;
	while(abs(r-l)>EPS)
	{
		mid=(l+r)/2;
		if(f(r)*f(mid)<=0)
		    l=mid;
		else r=mid;
	}
	printf("%.5f\n",l);
	return 0;
}

上記の質問の精度は小数点以下8桁まで正確であり、5桁のみが出力されます。得られた結果は3.55262です。実際に計算すると、結果は10の7乗と異なることがわかります。結果が正しいことを証明します。

2. バイナリ回答(最小最大または最小最大)

(1)ケーブルはn本あり、長さはlen [i]です。同じ長さのk本のケーブルがn本のケーブルから切断された場合、k本のケーブルの最長の長さはいくつですか?

例:n:5; k:5; len [i] are:2 5 8 3 4そして、カットの長さは:3。別の例:n:4; k:11; len [i]は:8 7 4 5で、カットの長さは次のようになります:2。

解決策:一見すると、この質問は従来の二分法アルゴリズムで使用できるモデルではありません。ただし、これを考慮してください。非常に多くのセグメントに分割できる最大値が必要です。この値よりも小さい場合は、非常に多くのセグメントに分割する必要があります。この値より大きい場合は、それほど多くのセグメントに分割しないでください。これは、二分法アルゴリズムを使用して境界値を変更するときの判断条件に似ています。したがって、二分法のアイデアを使用する場合、必要な結果として、まず範囲を設定してから、その範囲を2で除算し、中間値が分割の条件を満たしているかどうかを判断し、次に境界値を常に変更して二分法を完了します。ここでは2つの質問があります:1.境界値を変更するための判断条件; 2.正しい答えを得るために変更を停止するタイミング。

これらの2つの問題が解決したら、コードを二分法の形式で記述できます。

#include<iostream>
using namespace std;
int k,n;
int len[10005];

bool can(int x)
{
	int num=0;
	for(int i=0;i<n;i++)
	{
		num+=len[i]/x;
	}
	return num>=k;
}

int find(int left,int right)
{
	int l=left,r=right,ans=0;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(can(mid))
		{
		    l=mid+1;ans=mid;	
		}
		else r=mid-1;
	}
	return ans;
}

int main()
{
	cin>>n>>k;
	int max=0;
	for(int i=0;i<n;i++)
	{
		cin>>len[i];
		max=max>len[i]?max:len[i];
	}
	int ans=find(0,max);
	cout<<ans<<endl;
	return 0;
}

関数は非常に多くに分割できますが、find関数はl> rが検索を停止することを示しているため、このプログラムの複雑さもO(logn)です。

(2)攻撃的な牛(poj2456)

トピック:x [i]の位置にn個のブルペンがあり、次にm個の牛がそれらを可能な限り離して配置しています。

ソリューション:二分距離は、各牛がこの距離を満たすかどうかを判断するために、最大の二分結果を取ります。

ACコード:

#include<iostream>
#include<algorithm>
using namespace std;

int n,m;
int x[100005];

bool can(int dist)
{
	int num=1;
	int cur=0;
	for(int i=0;i<n;i++)
	{
		if(x[i]-x[cur]>=dist)
		{
			cur=i;
			num++;
		}
	}
	return num>=m;
}

int maxdist()
{
	int l=1,r=x[n-1]-x[0],mid,ans=0;
	while(l<=r)
	{
		mid=(l+r)/2;
		if(can(mid))
		{
			ans=mid;l=mid+1;
		}
		else r=mid-1;
	}
	return ans;
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
	{
		cin>>x[i];
	}
	sort(x,x+n);
	int ans=maxdist();
	cout<<ans<<endl;
	return 0;
}

(3)n個のアイテムがあり、各アイテムには重みwと値vがあります。次に、n個のアイテムからkを選択して、最大の合計ユニット値(つまり、kアイテムの合計値を合計で割った値)を取得します重量)。

問題の解決策:この問題は、貪欲なアルゴリズムを使用して、質量が最も高い上位k個のアイテムを直接選択するために使用することはできません。これは正しい結果を取得しません。二分法を使用して、二分法の単位値を選択する必要があります。条件が満たされているかどうか:この合計の単位値がmとして追加されます。それらの中から選択されたk個のアイテムについて、各アイテムの値はw [i] * mである必要があり、k個のアイテムはそれらの値に従って計算されます合計値を求め、それぞれの実際の値に応じて合計値を合計し、実際の合計値が合計値より高い場合は、単位値mが適格となる。このようにして、最終的な結果を得るために最大単位値が半分になります

 コード:

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const double EPS=1e-6;
int n,k;
double w[100005],v[100005];
double c[100005];

bool can(double x)
{
	for(int i=0;i<n;i++)
	{
		c[i]=v[i]-x*w[i];
	}
	sort(c,c+n);
	double p=0;
	for(int i=n-1;i>n-1-k;i--)
	    p+=c[i];
	return p>0;
}

double findmax(double left,double right)
{
	double l=left,r=right,mid,ans=0;
	while(abs(r-l)>EPS)
	{
		mid=(l+r)/2;
		if(can(mid))
		{
			ans=mid;l=mid;
		}
		else r=mid;
	}
	return ans;
}
int main()
{
	cin>>n>>k;
	double maxv=0;
	for(int i=0;i<n;i++)
	{
		cin>>w[i]>>v[i];
		maxv=maxv>v[i]?maxv:v[i];
	}
	double ans=findmax(0.0,maxv);
	cout<<ans<<endl;
	return 0;
}

(4)コピー本(poj1505)

入力例

2 
9 3 
100 200 300 400 500 600 700 800 900 
5 4 
100 100 100 100 100

出力例

100 200 300 400 500/600 700/800 900 100/100/100/100 
100

複数のデータセット。

Problem Solution:2ページのページ。各ブックの最大ページ数からすべてのブックのページの合計まで。判定機能では、要件を満たす連続値の最大値を取得するために、m人の要件を満たすように正しい境界を移動できる必要があります。次に、whileループで毎回正しい境界の動きになります。

コード:

#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const ll N = 505;

ll min1, max1;
ll num[N];
bool p[N];
int m, n;

bool can(ll x)
{
	int k = 1;
	ll sum = 0;
	for (int i = 0; i < n; i++)
	{
		sum+=num[i];
		if(sum>x)
		{
			k++;sum=num[i];
		}
	}
	return k<=m; //注意!!! 
}
ll minmax()
{
	ll x = min1;
	ll y = max1;
	ll mid,ans=0;
	while (x <= y)
	{
		mid = (x + y) / 2;
		if (can(mid))
		{
			y = mid-1,ans=mid;   //注意!! 
		}
		else
			x = mid + 1;
	}
	return ans;
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		min1 = -1;
		max1 = 0;
		cin >> n >> m;
		for (int i = 0; i < n; i++)
		{
			cin >> num[i];
			if (min1 < num[i])
				min1 = num[i];
			max1 += num[i];
		}
		memset(p, 0, sizeof(p));
		ll ans = minmax();
		ll sum = 0, k = 0;
		for (int i = n - 1; i >= 0; i--)
		{
			if (sum + num[i] <= ans)
				sum += num[i];
			else
			{
				sum = num[i];
				k++;
				p[i] = true;
			}
		}
for (int i = 0; i < n&&k < m - 1; i++)
		{
			if (!p[i])
			{
				p[i] = true;
				k++;
			}
		}
		for (int i = 0; i < n-1; i++)
		{
			cout << num[i] << " ";
			if (p[i])
			{
				cout << "/ ";
			}
		}
		cout << num[n - 1] << endl;
	}

	return 0;
}

上記は二分法アルゴリズムといくつかの使用法の私の要約です。

二分法アルゴリズムは、主に2つの側面で使用されます。1つは、順序付けされた配列で特定の要素を見つけることです。もう1つは、問題の解が区間内にあり、この区間の二分探索によって答えが見つかることです。連続する間隔があるかどうかの答えが満たされている必要があり、この間隔の最大値または最小値が取得されます。

 

リリース6元記事 ウォンの賞賛0 ビュー184

おすすめ

転載: blog.csdn.net/morning_zs202/article/details/92364209