WZRY
为了排位赛的Cjj神,最近耗尽气力来打WZRY。
Cjj神最近有N局预约的排位赛,其中第i局需要耗时Li的时间。因为浓浓的Gay情,Cjj神不能改变这些排位赛的的顺序。作为一个很有(mei)自制力的人,Cjj神计划用M+1天打完N局,为了能够活着见到第M+2天的太阳,他希望耗时最长的一天最短。
每天最少打一局。
请告诉他这个值是多少,以使他判断他是否能活下来;并且告诉他在总时长最长一天等于这个最小值的情况下有多少种方案,以使他判断他活下来的概率是多少。
输入格式:
第一行两个整数N,M。
接下来N个整数Li。
输出格式:
一行两个数,第一个是总时长最长的一天的最小值,第二个数是方案总数,对10,007取模。
数据范围:
10%:N<=25
30%:N<=300
60%:N<=10000
100%:N<=50000,0<=m< n,m<1000,1<=Li<=1000
这道题有两问,第一问可以很简单地用二分+贪心解决。
但第二问呢?
首先这道题的普通暴力DP是很好想到的,设f[i][j]为第i天,打了j局比赛的方案总数。
很显然f[i][j]=∑f[i-1][k](sum[j]-sum[k]<=ans)
sum为Li的前缀和。
奉上暴力代码:
#include <bits/stdc++.h> #define mod 10007 using namespace std; inline char tc(){ static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline int read(){ char c; while(!isdigit(c=tc()));int x=c-'0'; while(isdigit(c=tc()))x=x*10+c-'0'; return x; } int n,m,a[30001],mx,ans,ww,f[1001][30001],sum[30001]; int check(int x){ int tot=0,last=x,d=1; for(int i=1;i<=n;i++) if(last>=a[i])last-=a[i]; else d++,last=x-a[i]; return d<=m; } int main(){ n=read(),m=read();m++; for(int i=1;i<=n;i++)a[i]=read(),mx=max(mx,a[i]),ww+=a[i],sum[i]=sum[i-1]+a[i]; int L=mx,R=ww; while(L<=R){ int mid=L+R>>1; if(check(mid))R=mid-1,ans=mid; else L=mid+1; }//二分,第一问 for(int i=1;i<=m;i++){f[i-1][0]=1; for(int j=i;j<=n;j++){ for(int k=i-1;k<j;k++) if(sum[j]-sum[k]<=ans) f[i][j]=(f[i][j]+f[i-1][k])%mod; } } int tot=0; for(int i=1;i<=m;i++)tot=(tot+f[i][n])%mod; printf("%d %d",ans,tot); }
这个O(M*N^2)的效率一定会超时,所以我们要对暴力优化。
仔细看这个暴力,不难发现其实是一个不停枚举j和k边界的过程。
又想到我们知道最大耗时的最小值,那么就可以在已知k便边界的情况下通过预处理求出j边界的值。
设pre[i]表示第i局最远可以由哪一局转移过来。
那么只需枚举k加前缀和维护f数组的值就可以得到O(MN)的算法了。
但通过观察数据可得内存会炸,由于有前缀和维护,只需要每次i做完1次时将f清零即可。
code:
#include <bits/stdc++.h> #define mod 10007 #define ll long long using namespace std; inline char tc(){ static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline int read(){ char c; while(!isdigit(c=tc()));int x=c-'0'; while(isdigit(c=tc()))x=x*10+c-'0'; return x; } int n,m,a[30001],mx,ans,ww,f[30001]; int sum[30001],pre[30001],d[30001]; int check(int x){ int tot=0,last=x,d=1; for(int i=1;i<=n;i++) if(last>=a[i])last-=a[i]; else d++,last=x-a[i]; return d<=m; } int main(){ n=read(),m=read();m++; for(int i=1;i<=n;i++)a[i]=read(),mx=max(mx,a[i]),ww+=a[i],sum[i]=sum[i-1]+a[i]; int L=mx,R=ww; while(L<=R){ int mid=L+R>>1; if(check(mid))R=mid-1,ans=mid; else L=mid+1; } for(int i=1;i<=n;i++){int sum=0; for(int j=i;j>0;j--){ if(sum+a[j]<=ans)sum+=a[j]; else {pre[i]=j;break;} } }//预处理pre for(int i=0;i<=n;i++)d[i]=1; int tot=0; for(int i=1;i<=m;i++){ for(int j=i;j<=n;j++){ if(pre[j]>0)f[j]=(f[j]+d[j-1]-d[pre[j]-1])%mod; else f[j]=(f[j]+d[j-1])%mod; }d[0]=0; tot=(tot+f[n])%mod; for(int j=1;j<=n;j++)d[j]=(d[j-1]+f[j])%mod,f[j]=0;//维护,清零 } tot=((tot+mod)%mod+mod)%mod; printf("%d %d",ans,tot); }