A - DDL 的恐惧
问题描述
已知有n项作业的ddl和对应的分值,一天只仅且只能完成一项作业,ddl前未完成的作业扣取作业对应的分值。请合理规划完成各项作业,以使被扣取的分值最少。
Input
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
Output
对于每个测试用例,您应该输出最小的总扣减分数,每个测试用例一行。
Example
Input
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
Output
0
3
5
解题思路
思路一:
可以使用贪心策略,每一次都选当前分值最大的一项作业完成。因此我们要将给出的n项作业按作业分值进行排序,然后按从大到小的顺序一一进行分配。此外为了确保当前的选择对其他作业的选择影响最小,就需要选择离该项作业的ddl最近的空闲时间完成该项作业。因此我们需要一个数组来记录当前每一天的空闲情况,如果找不到可以放置该作业的空闲时间,那么就说明这项作业完成不了,那么就将该作业对应的分值扣除。
思路二:
也可以按天进行分配,从ddl在这一天及之后的作业里挑选出分值最大的作业,为此我们为了降低复杂度,我们采用堆的方式按作业分值的大小存储作业。所以我们首先要先将给出的n项作业按作业ddl进行排序,从当前最大的ddl处进行分配,初始堆中只有ddl最大的一项作业,然后按ddl从大到小的顺序依次入堆。当相邻入堆的两项作业的ddl不相同时,最大元素出堆。重复该操作,直到所有元素入堆,若还未分配到第一天,则继续出堆,直到第一天为止。
代码1
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
struct work{
int ddl;
int value;
work(){}
work(int a1, int b1){
ddl = a1, value = b1;
}
bool operator < (const work&a){
return value<a.value;
}
};
work a[1500];
int ddl(int n){
int d1=0, d2=0, sum=0;
int b[1500] = {0};
for(int i=0;i<n;i++)
cin>>a[i].ddl;
for(int i=0;i<n;i++)
cin>>a[i].value;
sort(a, a+n);
bool judge;
for(int i=n-1;i>=0;i--)
{
judge=false;
for(int j=a[i].ddl;j>0;j--)
{
if(b[j]==0)
{
b[j]=1;
judge=true;
break;
}
}
if(!judge)
sum=sum+a[i].value;
}
return sum;
}
int main()
{
int n,m,sum;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>m;
sum = ddl(m);
cout<<sum<<endl;
}
return 0;
}
代码2
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;
struct work{
int ddl;
int value;
work(){}
work(int a, int b){
ddl = a, value = b;
}
bool operator < (const work&a){
return value<a.value;
}
};
bool compare(const work&a,const work&b){
return a.ddl<b.ddl;
}
work a[1000];
int ddl(int n)
{
work now;
int d1=0, d2=0, sum=0;
for(int i=0;i<n;i++)
cin>>a[i].ddl;
for(int i=0;i<n;i++)
cin>>a[i].value;
sort(a, a+n, compare);
vector<work> b;
b.push_back(a[n-1]);
int days = a[n-1].ddl;
make_heap(b.begin(),b.end());
for(int i=n-2;i>=0;i--)
{
d1 = a[i].ddl, d2=a[i+1].ddl;
if(d2==d1)
{
b.push_back(a[i]);
push_heap(b.begin(),b.end());
}
else
{
if(!b.empty() )
{
now = b.front();
pop_heap(b.begin(),b.end());
b.pop_back();
days--;
if(b.empty() )
days = a[i].ddl;
else
{
now = b.front();
if(days > now.ddl)
days = now.ddl;
}
b.push_back(a[i]);
push_heap(b.begin(),b.end());
}
}
}
while(days!=0&&!b.empty())
{
pop_heap(b.begin(),b.end());
b.pop_back();
days--;
now = b.front();
if(days > now.ddl)
days = now.ddl;
}
for(int i=0;i<b.size();i++)
sum = sum + b[i].value;
return sum;
}
int main()
{
int n,m,sum;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>m;
sum = ddl(m);
cout<<sum<<endl;
}
return 0;
}
B - 四个数列
问题描述
有四个数列 A,B,C,D,每个数列都有 n 个数字。现从每个数列中各取出一个数,求共有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
Input
第一行:n(代表数列中数字的个数) (1 ≤ n ≤ 4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
Output
输出不同组合的个数。
Example
Input
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
Output
5
解题思路
这是一个枚举问题,但如果直接枚举,算法的复杂度将达到n的4次方级别。因此我们要将数组进行两两分配,分别枚举计算两个数组的和,并将其中一个数组的和求反,然后分别储存两个大数组。之后使用枚举,查找两个元素中的相同元素,又因为当一个数列中有多个相同的数字的时候,把它们当做不同的数对待,因此我们需要查找到一个元素出现的最早和最晚位置以便统计。为了降低复杂度,查找时我们使用二分查找(本题考查的也主要是整数二分查找)。
代码
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <vector>
using namespace std;
vector<int> sum1,sum2;
int last1(int x, int n){
int l = 1, r = n, ans = -1;
while( l <= r)
{
int mid=(l + r )/2;
if(sum1[mid] == x){
ans = mid;
l = mid+1;
}
else if( sum1[mid] > x)
r = mid - 1 ;
else
l = mid+1;
}
return ans;
}
int begin1(int x, int n){
int l = 1, r = n, ans=-1;
while(l<=r)
{
int mid = (l+r)/2;
if(sum1[mid] == x){
ans = mid;
r = mid-1;
}
else if( sum1[mid] > x)
r = mid - 1 ;
else
l = mid+1;
}
return ans;
}
int main()
{
int n;
cin>>n;
int *a, *b, *c, *d;
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]>>b[i]>>c[i]>>d[i];
int sum = 0, count = 0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
sum = a[i]+b[j];
sum1.push_back(sum);
sum = c[i]+d[j];
sum = 0 - sum;
sum2.push_back(sum);
count++;
}
sort(sum1.begin(), sum1.end());
int begin = 0, last = 0;
sum = 0;
for(int i=0;i<count;i++)
{
begin = begin1(sum2[i], count);
last = last1(sum2[i], count);
if(begin != -1&&last != -1)
sum = sum + last - begin + 1;
}
cout<<sum<<endl;
return 0;
}
C - TT 的神秘礼物
问题描述
给定一个 n 个数的数组 a[n],并用这个数组生成一个新数组 ans[n]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(a[i] - a[j]),1 <= i < j <= n。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
Input
多组输入,每次输入一个 n,表示有 n 个数,之后输入一个长度为 n 的序列 a, a[i] <= 1e9 , 3 <= n <= 1e5
Output
输出新数组 ans 的中位数
Example
Input
4
1 3 2 4
3
1 10 2
Output
1
8
解题思路
为了求出ans数组的中位数,似乎必须求出ans数组来,但这样运行时间无疑会超过限制,因此我们需要在不计算出ans数组的情况下计算出ans数组的中位数。比如说给定一个数P,如果他在ans数组中的名次等于中位数,那么这个数就是中位数。那么我们只需要计算a[ j ] - a[ i ] <= P的元素对数即可,又因为a[ j ] <= a[ i ] + P。因此我们在P已知的情况下,查找满足条件的 a[ j ] 的个数即可。此时我们可以使用二分查找寻找到满足条件的最大 j ,然后我们就可找到满足条件的数组对数。至于P的取值范围,显然是从0到ans的最大值,在枚举P时也需要用到二分查找,找到第一个满足数组对数大于等于中位数的P。
代码
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
int *a;
int read()
{
int s=0,w=1;
char ch=' ';
while(ch<'0' || ch>'9')
{
ch=getchar();
if(ch=='-')
w=-1;
}
while(ch>='0' && ch<='9')
{
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
int search(int target, int n) // satisfy condition: array[?] <= target and the last one
{
int start = 0, end = n - 1;
while (start <= end)
{
int mid = (start + end) / 2;
if (a[mid] <= target)
start = mid + 1;
else if (a[mid] > target)
end = mid - 1;
}
if (start == 0)
return -1;
return start - 1;
}
int get(int n){
a = new int[n];
for(int i=0;i<n;i++)
a[i] = read();
sort(a, a+n);
int max = a[n-1]-a[0];
int rank = (n*n-n+2)/4;
int x = 0, y = 0, z = 0;
int start = 0, end = max, mid = 0;
while(start <= end)
{
int mid = (start+end)/2;
y = 0;
for(int j=0;j<n;j++)
{
x = a[j] + mid;
z = search( x , n) - j ;
if(z > 0)
y = y + z;
}
if( y >= rank)
end = mid - 1;
else if( y < rank)
start = mid + 1;
}
return end+1;
}
int main()
{
int n,sum;
while(scanf("%d",&n)!=EOF)
{
sum = get(n);
cout<<sum<<endl;
}
return 0;
}