【题意】有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;
}