Problem——DDL的恐惧
问题的描述
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。
所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
请你帮帮他吧!
输入
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
输出
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
问题分析
首先吧,咱建立一个结构体,用于保存每个作业的分值和ddl。(因为不用说出具体的顺序,序号什么的就不管了)
然后输入所有的作业情况后,我们要干的就是把所有的作业按照分值的大小,由高往低排列。一样分值的再按照ddl小的进行排列。
排列完成后,按照这个顺序,挨个对作业进行处理,假设第m个任务的ddl为x,那么就优先把这个作业排在x那一天。如果那一天已经被其他作业占用,就往前找,找到一个小于ddl又是空闲的天数,确定下来。如果已经找不到小于ddl且空闲的天数,那么这个作业就舍弃掉,它的分值就加入到减少的分里面去。重复此操作,直到所有能安排的作业都已经安排好,即可。
代码
#include<iostream>
using namespace std;
#include<algorithm>
struct work
{
int deadtime;
int gread;
};
bool cmp(work& a,work& b)
{
if(a.gread!=b.gread)
return a.gread>b.gread;
else
return a.deadtime>b.deadtime;
}
int vis[1000];
int N;
void innitial()
{
for(int i=1;i<=N;i++)
vis[i]=0;
}
bool insert(int ddl)
{
int i;
for(i=ddl;i>=1;i--)
{
if(vis[i]==0)
break;
}
if(vis[i]==0&&i>=1)
{
vis[i]=1;
//cout<<i<<" "<<ddl<<endl;
return true;
}
else
return false;
}
int main()
{
int T;
int sum;
cin>>T;
work works[1001];
while(T--)
{
cin>>N;
sum=0;
for(int i=0;i<N;i++)
{
cin>>works[i].deadtime;
}
for(int i=0;i<N;i++)
{
cin>>works[i].gread;
}
sort(works,works+N,cmp);
innitial();
for(int i=0;i<N;i++)
{
if(!insert(works[i].deadtime))
sum=sum+works[i].gread;
}
cout<<sum<<endl;
}
return 0;
}
遇到的问题
对于数组的循环处理时,数组的端点还是没有处理的太好,都是后来拿数据试才找到究竟该怎么写。
Problem——四个数列
问题的描述
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
请你帮帮他吧!
输入
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
输出
输出不同组合的个数。
问题分析
首先先遍历a,b数组,把他们的值相加,存到另一个数组里面去。
然后再给那个数组排个序。
遍历c,d数组,使用二分法在上面存放a+b的结果的数组里寻找-(c+d)(用left,mid,right,三个点来循环寻找改点第一次出现的时候,以及最后一次出现的时候)累加差值即可。
接下来详细的说一说这个让我头疼的二分法:
先取left和right的值,即存放a+b结果的那个数组的最左端和最右端。mid就是他们两个的中值。用mid来和要寻找的数作比较,因为我们这个是一个升序的数组,于是比mid大就在右边,就在mid,right的范围内再来一次二分。反之就是在left,mid的范围内再来一次二分。
而当寻找的数就是mid的时候,操作就有所不同了。
寻找第一次出现的时候,当mid=shu,我们就继续往mid的右边找,因为右边可能还有和数相等的数,前面有数就代表着这个mid所代表的数不是第一个出现的shu。就需要我们继续往右边找。
而找到最后一次出现的数的时候,结果就刚好相反,也是一样的思路。当mid=shu,很有可能mid的右边还有和shu相等的数,不一定mid就是最后一个出现的数,所以还需要往左找。
一当出现相等的数,就记录答案,再次出现相等的数,就更新答案,简单来说就是这样。
代码
#include<iostream>
using namespace std;
#include<algorithm>
int step=0;
int *a,*b,*c,*d;
int *all;
int n;
int find_back(int shu)
{
int left=1;
int right=n*n;
int ans=-1;
//cout<<"back: "<<shu<<endl;
while(left<=right)
{
//cout<<"left="<<left<<" right="<<right<<" ans="<<ans<<endl;
int mid=(left+right)>>1;//除以2的意思
if(all[mid]==shu)
{
ans=mid;
left=mid+1;
}
else if(all[mid]>shu)
{
//cout<<all[mid]<<" mid="<<mid<<endl;
right=mid-1;
}
else left=mid+1;
//cout<<"front left="<<left<<" right="<<right<<" mid="<<mid<<" ans="<<ans<<endl;
}
return ans;
}
int find_front(int shu)
{
int left=1;
int right=n*n;
int ans=-1;
while(left<=right)
{
int mid=(left+right)>>1;//除以2的意思
if(all[mid]>=shu)
{
if(all[mid]==shu)
ans=mid;
right=mid-1;
}
else left=mid+1;
}
return ans;
}
bool cmp(int f,int b)
{
return f<=b;
}
int main()
{
cin>>n;
a=new int [n+1];
b=new int [n+1];
c=new int [n+1];
d=new int [n+1];
all=new int [n*n+2];
for(int i=0;i<n;i++)
{
cin>>a[i]>>b[i]>>c[i]>>d[i];
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
all[i*n+j+1]=a[i]+b[j];
//step=step+find(-(a[i]+b[j]));
//cout<<i*n+j+1<<" a="<<a[i]<<" b="<<b[j]<<endl;
}
}
sort(all+1,all+n*n+1,cmp);
//for(int i=1;i<=n*n;i++)
//cout<<all[i]<<" ";
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
int shu1,shu2;
shu1=find_back(-c[i]-d[j]);
shu2=find_front(-c[i]-d[j]);
//cout<<shu1<<" "<<shu2<<endl;
if(shu1!=-1)
step=step+shu1-shu2+1;
//cout<<" c="<<c[i]<<" d="<<d[j]<<" front="<<find_front(-c[i]-d[j])<<" back="<<find_back(-c[i]-d[j])<<endl;
}
}
cout<<step<<endl;
return 0;
}
遇到的问题
最开始以为自己懂了,写了半天发现还是n的4次方的复杂度。然后又多看了半天才理解到二分的用法。害,还是不太熟
Problem——TT的神秘礼物
问题的描述
TT 是一位重度爱猫人士,每日沉溺于 B 站上的猫咪频道。
有一天,TT 的好友 ZJM 决定交给 TT 一个难题,如果 TT 能够解决这个难题,ZJM 就会买一只可爱猫咪送给 TT。
任务内容是,给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
TT 非常想得到那只可爱的猫咪,你能帮帮他吗?
输入
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
输出
输出新数组 ans 的中位数
问题分析
这道题的数据十分庞大,所以必须要使用二分法来减少运算量。
先输入所有数据到数组里,然后对数组进行排序操作。
我就设定了,small,large来表示a[j]-a[i]的最小值和最大值。mid代表他们的中值,是用来估计新的数列的中位数的大小的。
然后就是每次的mid就采用,a[j]-a[i]<mid的方法来统计小于这个“中位数”的个数,如果这个数的名次在中位数上就采用,否则就按照名次的高低和真正中位数的名次做对比,如果比需要的名次低,即我们预测的这个mid的数值有些小,就再次按照mid,large的范围来再次二分。否则就按照small,mid的范围来二分。
然后我发现,如果同时出现了多个和真正的中位数大小一样的数,只找比mid(预测的中位数)小的数,不能完全说清楚它的名词,于是我就用sum和sum1来分别记录,小于mid的数和等于mid的数。于是我就得到了所有值为mid的数的名次范围。用它再来和真正的中位数的名词次比较,就能够很好的得出结果了。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int a[100005];
int findcnt(int i, int xi_p)
{
int l=i+1, r=n;
int m;
if(a[l] > xi_p) return 0;
while(l < r)
{
m = (l+r)>>1;
if(a[m] <= xi_p) l = m+1;
else r=m;
}
return l - (i+1);
}
bool judge(int p, int index) // x的名次小于index则返回1, 反之返回0
{
int sum=0;
for(int i=0;i<n-1;i++)
{
sum += findcnt(i, a[i]+p);
}
return sum < index;
}
int find(int index)
{
int l=0, r=a[n-1] - a[0];
int m;
while(l<r)
{
m = (l+r)>>1;
if(judge(m, index)) l = m+1;
else r = m;
}
return l;
}
int main ()
{
while(~scanf("%d", &n))
{
for(int i=0;i<n;i++) scanf("%d", &a[i]);
int indexOfMid = ((n*(n-1) >> 1) + 1) >> 1;
sort(a, a+n);
printf("%d\n", find(indexOfMid));
}
return 0;
}
遇到的问题
我这个代码还有一些问题,在和其他同学交流过后,我才意识到,在寻找有多少个a[j]-a[i]<mid的时候也可以使用部分的二分法来简便运算,不过我过了,我就没有继续搞了。
还有我的方法记录了小于它的数和等于它的数,在名次的计算上那个值就不直接是名次,还需要做一些转换才行。