【2018/07/23测试T3】【SOJ 967】Work

【题目】

题目描述:

假设现在离 noip 还有 m 天,有 n 个人要去参加比赛。他们每个人都有一个预定的训练量 r[i] ,所以每一天他们都抓紧时间练习。但是由于条件限制,第 i 天只有 t[i] 的时间可以练习。

我们都知道,一个人在开始干活以前总要浪费一些时间做一些杂七杂八的事情。现在我们假定第 i 个人每天在训练前浪费的时间是固定的,记为 d[i] 。这段浪费掉的时间过后,选手会专心致志训练,他们会充分利用剩下的时间。然而一个可能的情况时,一个人还在无所事事的时候,某一天的训练时间已经过去了,所以他那一天什么事情都没有做。

现在请问每个人在第几天的时候可以完成自己的训练任务。当然会存在志向远大但是很懒惰的人到最后也是做不完的情况。

输入格式:

第一行两个整数 n,m ,表示人数和天数 。
接下来一行 m 个整数 t[i] 。
接下来 n 行每行两个整数 d[i],r[i] 。

输出格式:

一行输出 n 个整数表示每个人在第几天可以完成自己的工作,如果完不成,输出 0 。

样例数据 :

输入
3 3
4 2 5
1 3
2 5
3 4

输出
1 3 0

备注:

数据范围:
对 30% 的输入数据 :1≤n,m≤1000 
对 100% 的输入数据 :1≤n,m≤ 200000;1≤t[i]≤1000000; 0≤d[i]≤1000000;1≤r[i]≤1000000

注意事项:
如果某人浪费的时间超过一天,不需减去负的时间。

 

【分析】

首先,对于每个人,如果他恰好在第 i 天完成任务,那么他肯定能在 j (j > i)天内完成任务,在 k(k < i)天完不成任务

这样的话,答案具有单调性,我们可以用二分答案,现在的问题是怎么去判断 mid 是否合法

考虑用线段树,我们先对天数按从大到小排序,在对每个人按每天浪费的时间从大到小排序(排序是为了方便)

我们每次只把比第 d[ i ] 大的天数加入线段树,这样判断 mid 就用总的 t 减去总的 d 就是能完成的任务量,再与 r 比较

但是这样的话复杂度是O(n * log n * log n),还是会超时,还要再优化

由于线段树本身就有“二分”的思想,我们可以不用二分,就降了一个 log

emmm……还有不知道为何 cout 要比 printf 快,有大佬路过可以解释一下吗

 

【代码】

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200005
using namespace std;
int ans[N],sum[4*N],cnt[4*N];
struct Day
{
	int t,pos;
}day[N];
struct Man
{
	int d,r,pos;
}man[N];
bool comp1(const Day &p,const Day &q)
{
	return p.t>q.t;
}
bool comp2(const Man &p,const Man &q)
{
	return p.d>q.d;
}
void Add(int k,int l,int r,int x,int v)
{
	if(l==r)
	{
		sum[k]=v;
		cnt[k]++;
		return;
	}
	int mid=l+r>>1;
	if(x<=mid)  Add(k<<1,l,mid,x,v);
	else  Add(k<<1|1,mid+1,r,x,v);
	sum[k]=sum[k<<1]+sum[k<<1|1];
	cnt[k]=cnt[k<<1]+cnt[k<<1|1];
}
int Sum(int k,int l,int r,int v,int d,int pre)
{
	if(l==r)  return l;
	int mid=l+r>>1,ans;
	if(sum[k<<1]-cnt[k<<1]*d+pre>=v)  ans=Sum(k<<1,l,mid,v,d,pre);
	else  ans=Sum(k<<1|1,mid+1,r,v,d,pre+sum[k<<1]-cnt[k<<1]*d);
	return ans;
}
int main()
{
//	freopen("work.in","r",stdin);
//	freopen("work.out","w",stdout);
	int n,m,i,j;
 	scanf("%d%d",&n,&m);
 	for(i=1;i<=m;++i)
	{
 		scanf("%d",&day[i].t);
 		day[i].pos=i;
	}
 	sort(day+1,day+m+1,comp1);
 	for(i=1;i<=n;++i)
	{
		scanf("%d%d",&man[i].d,&man[i].r);
 		man[i].pos=i;
	}
 	sort(man+1,man+n+1,comp2);
 	j=1;
 	for(i=1;i<=n;++i)
	{
 		while(day[j].t>man[i].d&&j<=m)
		{
 			Add(1,1,m,day[j].pos,day[j].t);
 			j++;
 		}
 		if(sum[1]-cnt[1]*man[i].d<man[i].r)  ans[man[i].pos]=0;
 		else  ans[man[i].pos]=Sum(1,1,m,man[i].r,man[i].d,0);
 	}
 	for(i=1;i<=n;++i)
	  cout<<ans[i]<<" ";
// 	fclose(stdin);
// 	fclose(stdout);
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值