第4週の割り当て
総括する
A:DDLへの恐怖
n個の割り当て。各割り当てには独自のDDLがあります。DDLの前に割り当てを完了できない場合は、対応するスコアを差し引く必要があります。入力割り当ての最小控除ポイントを見つけます。
1.サンプルの入力と出力
入力
入力にはT個のテストケースが含まれています。入力の最初の行は、テストケースの数である単一の整数Tです。
各テストケースは、ジョブの数を示す正の整数N(1 <= N <= 1000)で始まります。
次に2行。最初の行にはDDLを示すN整数が含まれ、次の行には控除ポイントを示すN整数が含まれます。
3
3
3 3 3
10 5 1
3
1 3 1
6 2 3
7
1 4 6 4 2 4 3
3 2 1 7 6 5 4
アウトアウト
テストケースごとに、最小の合計削減スコアをテストケースごとに1行出力する必要があります。
0
3
5
2.トピックのアイデアとコード
貪欲なアルゴリズムを実践するための質問。貪欲な基準は、最後の締め切りからトラバースし、最も多くのポイントが差し引かれた宿題を優先することです。ジョブのDDLと控除ポイントを入力した後、ジョブを並べ替えます。最初のキー値はDDLの日付で、2番目のキー値は控除ポイントです。並べ替えの条件は、小さいものから大きいものまでです。次に、最後のDDLから現在の日付としてトラバースし、まだ期限が切れていないジョブを選択し、現在の日付から最も多くのポイントを差し引いて、ジョブ配列のアイテムを削除します。p.back()。DDL <dateに遭遇した場合、それは現在の日付で実行できる作業がないことを意味し、最初の日がトラバースされるまでトラバースを続行します。最終的な配列の残りのジョブの差し引かれた合計が最適なソリューションです。
コードは次のとおりです。
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
int num;
int LostPoint;
class hwk
{
public:
hwk()
{
DDL=0;
de=0;
}
hwk(int a,int b)
{
DDL=a;
de=b;
}
public:
int DDL;
int de;//减分
};
vector<hwk> p;
bool cmp(hwk a,hwk b)
{
if(a.DDL<b.DDL)
return true;
else if(a.DDL==b.DDL&&a.de<b.de)
return true;
else return false;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
LostPoint=0;
p.clear();
cin>>num;
for(int j=0;j<num;j++)
{
int ddl;
cin>>ddl;
p.push_back({ddl,0});
}//输入作业DDL
for(int j=0;j<num;j++)
{
int De;
cin>>De;
p[j].de=De;
}//输入每个作业相应扣除的分数
sort(p.begin(),p.begin()+num,cmp);
//按照作业优先度排序
for(int date=p.back().DDL;date>=1;date--)
{
//cout<<"date:"<<date<<endl;
if(p.empty())
break;
if(p.back().DDL<date)
continue;
int max=p.size()-1;
int k=p.size()-1;
//挑选所有还未过期且具有最大减分值的作业
while(p[k].DDL>=date&&k>=0) {
//cout<<max<<' ';
if (p[max].de < p[k].de)
max = k;
k--;
}
//将这个作业做掉
p.erase(p.begin()+max,p.begin()+max+1);
}
//计算最优失分值。
while(!p.empty())
{
LostPoint=LostPoint+p.back().de;
p.pop_back();
}
cout<<LostPoint<<endl;
}
return 0;
}
B:4つのシリーズ
4つのシリーズA、B、C、D。各シリーズにはn個の番号があります。
各シーケンスから数値を取得する場合は、4つの数値の合計をゼロにするスキームの数を調べます。
1.サンプルの入力と出力
入力
最初の行:n(シーケンスの数を表す)(1≤n≤4000)
次のn行では、i番目の行に4つの数字があり、A、B、C、Dのシーケンスのi番目の数字を表します
(この数は2の28乗を超えません)。
6
-45 22 42 -16
-41 -27 56 30
-36 53 -37 77
-36 30 -75 -46
26 -38 -10 62
-32 -54 -6 45
出力
異なる組み合わせの数が出力されます。
5
注意:
样例解释: (-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).
2.トピックのアイデアとコード
それは二分法の練習の問題です。n <= 4000であるため、複雑さを軽減するには二分法が必要です。この方法では、最初にシーケンスAとシーケンスBの合計を計算して、格納およびソートします。次に、新しいシーケンスで、シーケンスCとシーケンスDの要素の合計の逆を見つけます。複雑さはO(n ^ 2 + 2logn)に減らすことができます。このとき、二分探索法で境界を決定するときは、等号の使用に注意してください。
コードは次のとおりです。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n;
int result=0;
int* A;
int* B;
int* C;
int* D;
vector<int> p1;
void count(int a)
{
int tar1=0;//标记>=a的首个位置
int tar2=0;//标记<=a的最后一个位置
int st=0;//起始指针
int end=p1.size()-1;//末尾指针
int mid=(st+end)/2;//中间指针
//cout<<'!'<<endl;
while(st<=end)
{
//cout<<'!'<<endl;
if(a<=p1[mid]) {
tar1=mid;
end = mid - 1;
mid=(st+end)/2;
}
else {
st = mid + 1;
mid = (st + end) / 2;
}
}
if(p1[tar1]!=a)
return ;
st=0;
end=p1.size()-1;
while(st<=end)
{
if(a>=p1[mid]) {
tar2=mid;
st = mid + 1;
mid=(st+end)/2;
}
else {
end = mid - 1;
mid=(st+end)/2;
}
}
result=result+(tar2-tar1+1);
}
int main()
{
cin>>n;
A=new int[n];
B=new int[n];
C=new int[n];
D=new int[n];
for(int i=0;i<n;i++)
{
cin>>A[i];
cin>>B[i];
cin>>C[i];
cin>>D[i];
}
for(int i=0;i<n;i++) {
for (int j = 0; j < n; j++)
p1.push_back(A[i] + B[j]);
}
sort(p1.begin(),p1.begin()+p1.size());
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
count(-(C[i]+D[j]));
}
cout<<result<<endl;
return 0;
}
C TTの不思議な贈り物
N個の配列cat [i]が与えられ、この配列を使用して新しい配列ans [i]を生成します。新しい配列は、任意のi、j、およびi!= Jと同様に定義され、すべてans [] = abs(cat [i] -cat [j])、1 <= i <j <= Nを持ちます。この新しい配列の中央値を見つけてください。中央値は、ソート後の(len + 1)/ 2の位置に対応する数値であり、「/」は切り捨てられます。
1.サンプルの入力と出力
入力
複数の入力セット。毎回Nを入力します。これは、N個の数値があることを意味し、次に長さNのシーケンスcat、cat [i] <= 1e9、3 <= n <= 1e5を入力します。
4
1 3 2 4
3
1 10 2
出力
新しい配列ansの中央値を出力します
1
8
2.トピックのアイデアとコード
中央値を見つけるために回答二分法が使用され、2つの二分法を共有する新しい配列newListを検索する際にも二分法が使用されました。
最初に入力シーケンスをソートして、xj-xiで常に正(j> i)になるようにし、絶対値を削除します。入力シーケンスのサイズがnの場合、新しいシーケンスのサイズはN = n *(n-1)/ 2です。Nが奇数、N ++の場合、N / 2が新しいシーケンスの中央値になるようにしてください。
次に、二分して答えます。左側の境界は0で、右側の境界は、元のシーケンスの最大値から最小値を引いたものです。中央の数値をP =(l + r)/ 2とします。条件(xj-xi)<= Pを満たすバイナリグループの数を計算します。N / 2より小さい場合、左の境界はP + 1に右に移動します。それ以外の場合、左の境界はPに移動します。
これで、適格なバイナリをカウントする問題を解決するだけで済みます。条件(xj-xi)<= P as xi> = xj + Pを書き換えます。メイン関数でjをトラバースし、各jの条件を満たすiを検索します。検索関数では、xj + Pは固定値であり、最初の要素の添え字ansを見つけます>元の数値シーケンスでxx + Pを満たします。j-ansは、jが条件を満たしていると判断するシーケンス内のiの数です。番号。すべてのjを合計して、適格な双対の数を取得します。
コードは次のとおりです。
#include<iostream>
#include<algorithm>
#include <cstdio>
using namespace std;
int n,N;
int P,tmp;
int* List;
int l, r, ans,mid;
//返回newList中满足<=P的二元组(i,j)个数
int find(int p,int j)
{
int L = 0, R = j;
ans=j;
while(L<R)
{
mid=(L+R)/2;
if(List[mid]>=List[j]-P)
{
ans=mid;
R=mid;
} else{
L=mid+1;
}
}
return j-ans;
}
int main()
{
while(cin>>n)
{
N=n*(n-1)/2; //新数列的元素个数
if(N%2==1) N++;
List=new int[n];
for(int i=0;i<n;i++)
scanf("%d",&List[i]);
sort(List,List+n);
l = 0;
r = List[n-1]-List[0];
while (l < r)
{
//cout<<l<<' '<<r<<endl;
P = (l+r) /2;
tmp = 0;
for (int j = 1; j < n; j++) //遍历j,寻找xj-xi<=P的个数
{
tmp = tmp + find(P, j);
//cout<<tmp<<endl;
}
if (tmp < N/2) //当计数小于n/2时。
l=P+1;
else //当计数大于等于n/2时。
{
r=P;
}
}
cout<<r<<endl;
}
return 0;
}