プログラミング思考割り当て週3
今週の主な練習内容は、主に貪欲な基準を見つけるための貪欲なアルゴリズムです。全体的に、最初の2つの質問は非常にスムーズで、各質問は約20分でした。3番目の質問は、複雑さを減らすためにより多くの時間を費やしました。
問題A
正の数がNの場合、合計がSになるKを選択する必要があります。次に、それを取得する方法をいくつ計算してください!
1.サンプルの入力と出力
入力
最初の行、整数T <= 100は、テストケースの数を示します。いずれの場合も、2つの行があります。1行目、3つの整数はn、K、Sを示します。2行目n整数は正の数を示します。
1
10 3 10
1 2 3 4 5 6 7 8 9 10
出力
いずれの場合も、整数は答えを独立した行で示します。
4
2.全体的なアイデアとコード
要素ごとに2つのオプション(selectとunselect)を使用して、入力配列を反復処理します。選択した場合は、目標合計との差から要素を差し引き、次の要素を調べます。選択されていない場合は、直接次の要素に移動します。このうち、適時終了条件(実現可能枝刈り)は、目標合計との差が0未満であるか、選択された数の数がKを超えているが、目標とSにまだ到達していないことです。
#include<iostream>
#include<algorithm>
using namespace std;
int tmp;
int n,m,K,S;
int *p;
void SOL(int i,int size,int sum)
{
//符合K个数相加等于S的要求
if(sum==0&&size==K)
{
tmp++;
return ;
}
//提前结束情况
if(sum<0||size>K||i>=m)
return ;
//如果选第i个数
SOL(i+1,size+1,sum-p[i]);
//不选
SOL(i+1,size,sum);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
tmp=0;
//一共m个数,K个数的和为S的方案个数
cin>>m;
cin>>K;
cin>>S;
p=new int[m];
for(int j=0;j<m;j++)
{
cin>>p[j];
}
SOL(0,0,S);
cout<<tmp<<endl;
}
}
問題Bインターバルポイントの選択の問題
数直線にn個の閉じた区間[a_i、b_i]があります。各インターバルに少なくとも1つのポイントが存在するように、可能な限り少ないポイントを取ります(異なるインターバルに含まれるポイントは同じでもかまいません)。
1.サンプルの入力と出力
入力
最初の行に1つの整数N(N <= 100)
行2〜N + 1、各行に2つの整数a、b(a、b <= 100)
2
1 5
4 6
出力
選択したポイントの数を表す整数
1
2.全体的なアイデアとコード
古典的な貪欲な問題。最初に、入力間隔を右端点から小から大に並べ替え、limを使用して現在選択されているセクションの右端点の値を記録します。次に、間隔配列をトラバースし、限界点が現在の間隔にある場合は、次の間隔に進みます。そうでない場合は、区間の右端をlimに割り当て、区間番号++を選択します。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class section
{
public:
section()
{
_a=0;
_b=0;
}
section(int a,int b)
{
_a=a;
_b=b;
}
inline bool operator < ( section & x)
{
return _b<x._b;
}
public:
int _a,_b;
};
bool cmp(section a,section b)
{
return a._b<b._b;
}
int n;
vector<section> p;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
int a,b;
cin>>a;
cin>>b;
section Nsection(a,b);
p.push_back(Nsection);
}
sort(p.begin(),p.begin()+n,cmp);
int lim=p.front()._b;
int tmp=1;
for(int i=1;i<n;i++)
{
if(lim>=p[i]._a&&lim<=p[i]._b)
continue;
else
{
tmp++;
lim=p[i]._b;
}
}
cout<<tmp<<endl;
return 0;
}
問題C間隔カバレッジ問題
数直線にn(1 <= n <= 25000)の閉区間[ai、bi]があります。指定されたラインセグメント[1、t](1 <= t <= 1,000,000)をカバーするためにできるだけ少ない区間を選択してください。
ポイント全体をカバーします。つまり、(1,2)+(3,4)は(1,4)をカバーできます。
出力-1を実行できません
1.サンプルの入力と出力
入力
1行目:NおよびTの
2行目からN + 1行まで:各行には閉じた間隔があります。
3 10
1 7
3 6
6 10
出力
選択した間隔の数は出力できません-1
2
2.全体的なアイデアとコード
貪欲な問題については、まず問題を解決するためのマインドマップに移動します。
アイデアの設計に関しては、最初に入力インターバル配列を右のエンドポイントに従ってソートします。mlimを使用して現在選択されている間隔の右端のエンドポイント値を記録し、posを使用して現在選択されている間隔の配列内の位置を記録します。count関数が呼び出されるたびに、配列はposから先頭までトラバースされ、milmから始まる範囲を選択し、右端の値が最大で、新しい右端の値をMlimに割り当て、posを記録します。ターゲットの右端の値tに到達するまで。それらのうち、達成できない状況は、間隔の間にカバーできない空のポイントがあり(コードでMlimに新しい値が割り当てられていないことを示す)、最大右端点がターゲット値tに到達できない(コードでは、startがMlimに等しい)場合です。 )。
最終的なコードの複雑さはO(nlogn)です。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
class section
{
public:
section()
{
_a=0;
_b=0;
}
section(int a,int b)
{
_a=a;
_b=b;
}
public:
int _a,_b;
};
int n;//区间个数
int t;//目标右端点值
int wa=-1;//错误输出
int tmp=1;//区间个数计数
vector<section> p;//区间数组
bool cmp(section a,section b)
{
return a._b<b._b;
}
void count(int s,int start)
{
//记录从start开始的区间能够到达的最右点
int Mlim=0;
//记录下一次数组遍历的起始位置
int pos=0;
//判断是否成功结束
if(start==t)
{
cout<<tmp;
return;
}
//简化区间到【start,t】之中
for(int i=s+1;i<p.size();i++)
{
if(p[i]._a<=start+1)
{
//p[i]._a = start;
if(p[i]._b>Mlim)
{
Mlim = p[i]._b;
pos=i;
}
}
}
if(Mlim==0||Mlim==start)
{
cout<<wa;
return ;
}
else {
tmp++;
//cout<<Mlim<<endl;
count(pos,Mlim);
}
}
int main()
{
while(cin>>n)
{
scanf("%d", &t);
//记录从1起始的区间能够到达的右极点
tmp=1;
int mlim = 0;
for (int i = 0; i < n; i++) {
int a, b;
scanf("%d", &a);
scanf("%d", &b);
if (b >= t) {
b = t;
}
if (a <= 1) {
a = 1;
if (b > mlim)
mlim = b;
}
section Nsection(a, b);
p.push_back(Nsection);
}
if (mlim == 0) {
cout << wa;
return 0;
}
//按右端点排序
sort(p.begin(), p.begin() + p.size(), cmp);
count(0, mlim);
p.clear();
}
return 0;
}