Week4-程序设计思维与实践(作业)

A

题意:

有 n 个作业,每个作业都有自己的 DDL,如果没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。输入n个作业的DDL以及所占的分,要求合理安排做作业的顺序,使得所扣的分最少。

思路:

解法一:

显然,为了使所扣的分最少,我们要优先安排所占分值较大的作业,初步思路是将 n n n个作业按照分值从大到小排序,然后对于第 i i i个作业,从其DDL往前搜索空闲时段,这样做的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。为了降低时间复杂度,这里利用线段树对寻找空闲时段的操作进行优化:将 1 1 1 m a x ( D D L ) max(DDL) max(DDL)这段时间,以时间间隔为1进行区间划分,建立存储区间最大值的线段树,这样就可以在 O ( l o g n ) O(logn) O(logn)的时间里找出作业 i i i所对应的空闲时段,注意每次选取结束后要对该时间点的信息进行更新,这样经优化后总的时间复杂度降为 O ( n l o g n ) O(nlogn) O(nlogn)

代码实现:

#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;

const int maxn = 1010;
int a[maxn],t[maxn<<2];        

void Pushup(int k){
    t[k]=max(t[k<<1],t[k<<1|1]);
}

void build(int k,int l,int r){    
    if(l==r)    
        t[k]=a[l];
    else{
        int m=l+((r-l)>>1);  
        build(k<<1,l,m);    
        build(k<<1|1,m+1,r);   
        Pushup(k);    
    }
}

void updata(int p,int v,int l,int r,int k){    
    if(l==r)    
        a[p]+=v,t[k]+=v;   
    else{
        int m=l+((r-l)>>1);   
        if(p<=m)   
            updata(p,v,l,m,k<<1);
        else    
            updata(p,v,m+1,r,k<<1|1);
        Pushup(k);    
    }
}

int query(int L,int R,int l,int r,int k){    
    if(L<=l&&r<=R)    
        return t[k];
    else{
        int res=-INT_MAX;   
        int m=l+((r-l)>>1);    
        if(L<=m)   
            res=max(res,query(L,R,l,m,k<<1));
        if(R>m)    
            res=max(res, query(L,R,m+1,r,k<<1|1));
        return res;    
    }
}

struct s{
	int ddl;
	int score;
	friend bool operator <(const s& s1,const s& s2){
		return s1.score>s2.score;
	}
};

s work[1010];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int m;
	cin>>m;
	while(m--){
		int n;
		cin>>n;
		int R=0;
		int sumscore=0;
		for(int i=1;i<=n;i++){
			cin>>work[i].ddl;
			R=max(R,work[i].ddl);
		}
		for(int i=1;i<=R;i++){
			a[i]=i;
		} 
		for(int i=1;i<=n;i++){
			cin>>work[i].score;
			sumscore+=work[i].score;
		}
		sort(work+1,work+1+n);
		build(1,1,R);
		int count=R;
		for(int i=1;i<=n;i++){
			int now=query(1,work[i].ddl,1,R,1);
			if(now<=work[i].ddl&&now>=1){
				sumscore-=work[i].score;
				updata(now,-INT_MAX,1,R,1);
				count--;
			}
			if(count==0)
				break;
		}
		cout<<sumscore<<endl;
	}
}

解法二:

逆向思维,将为分值最大的作业寻找空闲时段的操作改成为当天安排分值最大的作业,首先将所有作业按照时间从大到小的顺序进行排序,然后从第 m a x ( D D L ) max(DDL) max(DDL) 天往前安排作业,对于第 i i i天的操作如下:将所有 D D L = i DDL=i DDL=i 的作业压入大根堆,然后选取堆顶元素并弹出即选取价值最大的作业安排在第 i i i天,直到枚举到第一天,算法结束,总的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

代码实现:

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;

struct s{
	int ddl;
	int score;
	friend bool operator <(const s& s1,const s& s2){
		return s1.score<s2.score;
	}
};

s work[1010];

bool cmp(const s& s1,const s& s2){
	return s1.ddl>s2.ddl;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int m;
	cin>>m;
	while(m--){
		int n;
		cin>>n;
		int maxt=0;
		int sumscore=0;
		for(int i=1;i<=n;i++){
			cin>>work[i].ddl;
			maxt=max(maxt,work[i].ddl);
		}
		for(int i=1;i<=n;i++){
			cin>>work[i].score;
			sumscore+=work[i].score;
		}
		sort(work+1,work+1+n,cmp);
		priority_queue<s> q;
		int j=1;
		for(int i=maxt;i>=1;i--){
			while(work[j].ddl==i&&j<=n){
				q.push(work[j++]);
			}
			if(!q.empty()){
				sumscore-=q.top().score;
				q.pop();
			}
		}
		cout<<sumscore<<endl;
	}
	return 0;
}

B

题意:

有四个数列 A,B,C,D,每个数列都有 n 个数字。从每个数列中各取出一个数,问有多少种方案使得 4 个数的和为 0。(当一个数列中有多个相同的数字的时候,把它们当做不同的数对待)

思路:

分治,首先两层for循环分别求出数列A与数列B的和sum1、数列C与数列D的和sum2,然后对sum1进行排序,循环遍历sum2中的元素,对于sum2[i],利用二分查找来统计sum1中使得sum1[i]+sum2[i]=0的元素个数。

代码实现:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>

using namespace std;

int n;
int a[4010],b[4010],c[4010],d[4010],sum1[16000010],sum2[16000010];


int main()
{	
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d %d %d %d",&a[i],&b[i],&c[i],&d[i]);
	}
	int k=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			sum1[k]=a[i]+b[j];
			sum2[k++]=c[i]+d[j];
		}
	}
	int ans=0;
	sort(sum1,sum1+k);
	for(int i=0;i<k;i++){
		int index1=lower_bound(sum1,sum1+k,-sum2[i])-sum1;
		int index2=upper_bound(sum1+index1,sum1+k,-sum2[i])-sum1;
		ans+=index2-index1;
	}
	printf("%d\n",ans);
	return 0;
}

C

题意:

给定一个 N N N 个数的数组 c a t [ i ] cat[i] cat[i],并用这个数组生成一个新数组 a n s [ i ] ans[i] ans[i]。新数组定义为对于任意的 i , j i, j i,j i ! = j i != j i!=j,均有 a n s [ ] = a b s ( c a t [ i ] − c a t [ j ] ) , 1 < = i < j < = N ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N ans[]=abs(cat[i]cat[j])1<=i<j<=N。试求出这个新数组的中位数,中位数即为排序之后 ( l e n + 1 ) / 2 (len+1)/2 (len+1)/2 位置对应的数字,’/’ 为下取整。

思路:

首先将 c a t cat cat 数组从小到大排序去除绝对值,则中位数的答案范围为 [ 1 , c a t [ n ] − c a t [ 1 ] ] [1,cat[n]-cat[1]] [1,cat[n]cat[1]],这样就将问题转换为二分查找满足中位数条件的第一个数,判断当前查找的 m i d mid mid 是否为中位数的方法为:计算 c a t [ j ] − c a t [ i ] ≤ m i d cat[j]-cat[i]≤mid cat[j]cat[i]mid 的二元组对数,计算方法是对于每一个下标 i i i 通过二分查找第一个满足条件 c a t [ j ] ≥ m i d + c a t [ i ] cat[j]≥mid+cat[i] cat[j]mid+cat[i]的下标 j j j,若下标之和等于中位数所对应的名次则查找成功。

代码实现:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

int cat[100010];
int n,ans;

int main()
{
	while(scanf("%d",&n)!=EOF){
		for(int i=1;i<=n;i++)
			scanf("%d",&cat[i]);
		sort(cat+1,cat+n+1);
		int l=1,r=cat[n]-cat[1],ans=-1;
		int len=n*(n-1)/2;
		int idx=(len+1)/2;
		while(l<=r){
			int mid=l+((r-l)>>1);
			int sum=0;
			for(int i=1;i<=n;i++){
				sum+=upper_bound(cat+1,cat+1+n,mid+cat[i])-cat-1-i;
			}
			if(sum>=idx){
				ans=mid;
				r=mid-1;
			}
			else l=mid+1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

总结:

第一题是贪心算法的应用,贪心算法最重要的是选取正确的贪心策略,此外贪心策略往往是选择指标最××的××,此时需要考虑运用优先级队列等数据结构对选取操作进行优化。第二题与第三题是二分算法的应用,二分算法常常用来对暴力枚举算法进行优化以及求解最大值最小 / 最小值最大问题,最重要的是选取适当的上下界以及二分模板中对当前mid大小的判断,当对二分算法的边界难以把握时可以考虑使用STL中的upper_bound与lower_bound进行替代。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值