選択問題
トピック:
n個の正の数が与えられた場合、ZJMはそれらの合計Sを合計するKを正確に選択できます。これでZJMはそれを取得する方法がいくつあるのか疑問に思います!
入力
最初の行、整数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
注意
k <= n <= 16であり、すべての数値は32ビット整数に格納できることに注意してください
アイデアと実践:
問題を解決するには、バックトラック方式を使用します。
関数のバックトラックを記述し、境界に到達するまでこの関数を再帰的に呼び出すか、有効なソリューションを取得してから戻ります。最初に、可能な解の配列を構築するために、配列内の可能な数の配列を1つずつ作成するコードを記述します。ここでループを使用するのは非常に簡単です。最初に、次の層に直接再帰せず、この数を次の層に入れてから再帰し、それを実現します。欠席には2つのケースがあり、考えられるすべてのケースにアクセスできます。重要なのは、時間の複雑さを最適化するために、剪定する必要があるということです。必要なのはK個の数値のみであり、配列内の数値がkを超えた場合に再帰を継続する必要はありません。満たすべき合計が0未満の場合、選択された数はすでにSよりも大きく、解の要件を満たしていないため、直接戻ります。現在のアクセス数値のインデックスが、返される境界である指定された配列範囲を超えました。適切な解決策が見つかった場合は、それを記録して直接戻ります。
バックトラック全体が終了すると、記録されたans値が答えになります。
要約:
非常に基本的なバックトラックの問題。再帰関数の記述方法と効率的なプルーニング方法に注意してください。
コード:
#include <list>
#include <stdio.h>
using namespace std;
#define N 16
#define rep(i,s,t) for(int i=s;i<=t;i++)
list<int> nums;
int num[N],ans;
void backtrack(int start,int n,int k,int s){
if(nums.size()==k&&s==0){
ans++;
return;
}else if(nums.size()>k||s<0||start>=n){
return;
}else{
rep(i,start,n-1){
nums.push_back(num[i]);
backtrack(i+1,n,k,s-num[i]);
nums.pop_back();
}
}
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int n,k,s;
while(!nums.empty()) nums.pop_back();
ans=0;
scanf("%d %d %d",&n,&k,&s);
rep(i,0,n-1){
scanf("%d",&num[i]);
}
backtrack(0,n,k,s);
printf("%d\n",ans);
}
return 0;
}
B間隔の選択
トピック:
数直線にn個の閉じた区間[a_i、b_i]があります。各インターバルに少なくとも1つのポイントが存在するように、可能な限り少ないポイントを取ります(異なるインターバルに含まれるポイントは同じでもかまいません)
入力
最初の行に1つの整数N(N <= 100)
行2〜N + 1、各行に2つの整数a、b(a、b <= 100)
出力
選択したポイントの数を表す整数
サンプル入力1
2
1 5
4 6
出力例1
1
サンプル入力2
3
1 3
2 5
4 6
出力例2
2
アイデアと実践:
貪欲戦略を使用して、区間選択の問題を解決します。
1つ目は、貪欲な基準を選択し、この基準に基づいて解決策を見つけることです。ここで使用される貪欲な基準は、間隔の最小端から右端へのソートです。最初の小さい値は、間隔が早く終了し、次の間隔が遅く終了することを意味します。次の間隔の左端点がその前の間隔の内側にあるかどうかを確認します。そうである場合、余分な点をカバーする必要はありません。そうでない場合は、余分な点を追加する必要があります。その右端で、このポイントをカバーし、ループダウンします。もう一度試した後、ポイントの数が解決策です。
要約:
貪欲な基準の選択に注意を払い、基準を考えてください。数学的な証明を達成できない場合は、それを転覆する反例がないか調べてください。このトピックのガイドラインは比較的簡単で、変数endを使用して現在の右エンドポイントを記録し、それを更新し続けることができます。
コード:
#include <stdio.h>
#include <algorithm>
#define N 100
using namespace std;
struct Interval{
int a,b;
Interval(){}
Interval(int _a,int _b):a(_a),b(_b){}
bool operator<(const Interval& i){
return b<i.b;
}
}interval[N];
int main(){
int n,a,b;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d %d",&a,&b);
interval[i]=Interval(a,b);
}
sort(interval,interval+n);
int ans=1,end=interval[0].b;
for(int i=1;i<n;i++){
if(interval[i].a>end){
ans++;
end=interval[i].b;
}
}
printf("%d\n",ans);
return 0;
}
C間隔のカバレッジ
トピック:
数直線にn(1 <= n <= 25000)の閉区間[ai、bi]があります。指定されたラインセグメント[1、t](1 <= t <= 1,000,000)をカバーするためにできるだけ少ない区間を選択してください。
ポイント全体をカバーします。つまり、(1,2)+(3,4)は(1,4)をカバーできます。
出力-1を実行できません
入力
1行目:NおよびTの
2行目からN + 1行まで:各行には閉じた間隔があります。
出力
選択した間隔の数は出力できません-1
入力例
3 10
1 7
3 6
6 10
出力例
2
アイデアと実践:
セルの最小数は広い間隔をカバーするように選択され、セルはできるだけ長くする必要があり、貪欲な方法でカバーするために最初から最後までカバーできることが理解できます。大まかに言えば、制限の終わり(最初の端が大きな間隔の始まりを見つける)が与えられた場合、残りの間隔から制限の左側まで左の端点を選択する必要があります(右側にある場合、切断され、完全にカバーできません)、右の端点は最も遠い間隔(長さが長いほど、対応する間隔の総数が減少する可能性があります)を選択した間隔の右端に更新します(特に関数fで実装され、見つかった最も適切な間隔のインデックス値を返しますが、見つかりません)適切なリターンに-1)。既存の端がカバーする広い範囲の右端に達するか超えるまでループするか、実行可能な解決策が見つからない場合は、ansに戻ります。
要約:
最初に考えたのは、入力時から間隔を編集して、左端と計算された間隔の長さに従って並べ替えてリサイクルし、各セル間の左端と長さを各更新端で同期的に更新することでした。その後、このようなトラブルも起こりやすく、区間の長さは計算されなくなり、区間の右端と端の間の距離が直接比較されることがわかりました。
コード:
#include <stdio.h>
#include <algorithm>
#define N 25000
using namespace std;
struct Interval{
int a,b;
Interval(){}
Interval(int _a,int _b):a(_a),b(_b){}
bool operator<(const Interval& i) const{
if(a!=i.a) return a<i.a;
return b>i.b;
}
}interval[N];
int f(int start,int end,int num){
int ans=-1,min=-1;
for(int i=start;i<end;i++){
if(interval[i].a<=num+1&&interval[i].b>=num+1){
if(interval[i].b-num-1>min){
min=interval[i].b-num-1;
ans=i;
}
}else if(interval[i].a>num+1) break;
}
return ans;
}
int main(){
int n,t;
while(~scanf("%d %d",&n,&t)){
int cnt=0,a,b;
for(int i=0;i<n;i++){
scanf("%d %d",&a,&b);
// if(a<=1&&b>=1&&b<=t) interval[cnt++]=Interval(1,b);
// else if(b>t&&a>=1&&a<=t) interval[cnt++]=Interval(a,t);
// else if(a>=1&&b<=t) interval[cnt++]=Interval(a,b);
// else if(a<1&&b>t) interval[cnt++]=Interval(1,t);
if(a<=t&&b>=1) interval[cnt++]=Interval(a,b);
}
sort(interval,interval+cnt);
int ans=0,index=0,end=0;
while(end<t){
index=f(index,cnt,end);
if(index==-1){ //没找到,断了
ans=-1;
break;
}
ans++; //找到,结果+1
end=interval[index].b; //更新end
}
printf("%d\n",ans);
}
return 0;
}