Copier

【题意】有n台机器,给出每台机器的复印时间ti,有m个订单,每个订单大小用ai表示。规定复印机必须从小到大开启,必须有复印件才能进行复印,开始时只有一份原件。问最小复印时间。
【分析】妥妥的二分答案,关键在于整个程序的策略。

【算法一】
首先优先队列算出复印出1到n-1张的时间,同时是,第2到n台机器开启的时间。对于每个询问,如果小于n直接输出,其余情况我们可以二分时间判断能否印ai张复印件。
优先队列

struct P{int time,id;};priority_queue<P>q;
bool operator <(const P &x,const P &y){return x.time>y.time;}
inline void Init(){
    q.push((P){t[1],1});
    for(rint i=1;i<n;i++){
        P e=q.top();q.pop();
        tn[i]=e.time,q.push((P){e.time+t[i+1],i+1}),e.time+=t[e.id];
        q.push(e);
    }
}

二分判定

inline bool check(rint rest,const rint num){
	rint cnt=0;
	for(rint i=1;i<=n;i++)
		cnt+=(rest-srt[i])/t[i]*(rest>srt[i]?1:0);
	return cnt>=num;
}

时间复杂度为O(40n*m)。加上特殊点关于ti=1的特判有60分。

【算法二】
这有一个神奇的优化,因为知道所有ai的大小所以可以在线转离线。将所有ai排序,先二分答案求出f[1]和f[m]的值。因为所用的时间关于订单的需求单调不降,所以区间[2,m-1]所有二分答案的值都在 [ f [ 1 ] , f [ m ] ] [ f[1] , f[m] ] [f[1],f[m]]之间。求出f[mid]的值,递归缩小区间大小,这样可以利用已知部分缩小二分答案的上下界。
同样的套路还可以用在其他部分,对于卡常数还是略显有效的。
时间复杂度O(玄学)。期望得分70~80分。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rint register int
#define int long long
using namespace std;
const int maxn=2e5+1000;
const int maxm=1e3+100;
int n,m;
int t[maxn],srt[maxn];
struct Q{
	int num,id;
}query[maxm];
int f[maxm],ans[maxm];
struct P{
	int time,id;
};
bool operator <(const P &x,const P &y){
	return x.time>y.time;
}
priority_queue<P>q;
inline void Init(){
	int cnt=1;
	srt[cnt]=0;
	q.push((P){t[1],1});
	while(cnt<n){
		P e=q.top();q.pop();
		srt[++cnt]=e.time,q.push((P){e.time+t[cnt],cnt}),e.time+=t[e.id];
		q.push(e);
	}
}
inline int read(){
	rint x=0;char tmp=getchar();
	while(tmp<'0'||tmp>'9') tmp=getchar();
	while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
	return x;
}
inline bool check(rint rest,const rint num){
	rint cnt=0;
	for(rint i=1;i<=n;i++){
		cnt+=(rest-srt[i])/t[i]*(rest>srt[i]?1:0);
	}
	return cnt>=num;
}
inline void solve(const rint x,rint l,rint r,const rint num){
	rint mid,ans;
	while(l<=r){
		mid=l+r>>1;
		if(check(mid,num)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	f[x]=ans;
}
void divide(const int l,const int r,const int suml,const int sumr){
	if(l>r) return ;
	int mid=l+r>>1;
	solve(mid,suml,sumr,query[mid].num);
	divide(l,mid-1,suml,f[mid]);
	divide(mid+1,r,f[mid],sumr);
}
bool cmp(const Q &x,const Q &y){
	return x.num<y.num;
}
inline bool solve_sp(){
	for(int i=1;i<=n;i++)
		if(t[i]!=1) return 0;
	for(int i=1;i<=m;i++){
		int x=query[i].num;
		while((1<<ans[i])<n&&x>0)x-=(1<<ans[i]),ans[i]++;
		while(x>0) x-=n,ans[i]++;
	}
	for(rint i=1;i<=m;i++)
		printf("%lld%c",ans[i],i==m?'\n':' ');
	return 1;
}
signed main(){
	cin>>n>>m;
	for(rint i=1;i<=n;i++)
		t[i]=read();
	for(rint i=1;i<=m;i++)
		query[i].num=read(),query[i].id=i;
	Init();
	if(solve_sp()){
		return 0;
	}
	sort(query+1,query+m+1,cmp);
	solve(m,1,t[1]*query[m].num,query[m].num);
	solve(1,1,f[m],query[1].num);
	divide(2,m-1,f[1],f[m]);
	for(rint i=1;i<=m;i++)
		ans[query[i].id]=f[i];
	for(rint i=1;i<=m;i++)
		printf("%lld%c",ans[i],i==m?'\n':' ');
	return 0;
}

【算法三】
问题在于二分判定时时间消耗较长,分ti类除数和it类余数,用前缀和维护除数为k余数大于等于的ti。
新的二分判定函数

inline bool check(rint x,rint num){
    rint cnt=0;
    for(rint j=1;j<=1000;j++)
        cnt+=ct[j]*(x/j)-s[j]-c[j][x%j+1];
    return cnt>=num;
}

顺便一提,这也可以用算法二进行优化,不过正常的卡常是可以过的。。。

【code for ac】

#pragma GCC optimize(2)
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rint register int
#define int long long
#define maxt maxm
using namespace std;
const int maxn=2e5+1000;
const int maxm=1e3+100;
int n,m,a;
int t[maxn],tn[maxn];
int ct[maxt],s[maxt];
int c[maxt][maxt];
inline int read(){
    rint x=0;char tmp=getchar();
    while(tmp<'0'||tmp>'9') tmp=getchar();
    while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
    return x;
}
struct P{int time,id;};priority_queue<P>q;
bool operator <(const P &x,const P &y){return x.time>y.time;}
inline void Init(){
    q.push((P){t[1],1});
    for(rint i=1;i<n;i++){
        P e=q.top();q.pop();
        tn[i]=e.time,q.push((P){e.time+t[i+1],i+1}),e.time+=t[e.id];
        q.push(e);
    }
}
inline bool check(rint x,rint num){
    rint cnt=0;
    for(rint j=1;j<=1000;j++)
        cnt+=ct[j]*(x/j)-s[j]-c[j][x%j+1];
    return cnt>=num;
}
signed main(){
    cin>>n>>m;
    for(rint i=1;i<=n;i++)
        t[i]=read(),ct[t[i]]++;
    Init();
    for(rint i=1;i<=n;i++){
        s[t[i]]+=tn[i-1]/t[i];
        c[t[i]][tn[i-1]%t[i]]++;
    }
    for(rint i=0;i<=1000;i++)
        for(rint j=i;j>=0;j--)
            c[i][j]+=c[i][j+1];
    for(rint i=1;i<=m;i++){
        a=read();
        if(a<n){printf("%lld%c",tn[a],i==m?'\n':' ');continue;}
        rint l=tn[n-1]-1,r=t[1]*a,ans=r,mid;
        while(l<=r){
            mid=l+r>>1;
            if(check(mid,a)) ans=mid,r=mid-1;
            else l=mid+1;
        }
        printf("%lld%c",ans,i==m?'\n':' ');
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值