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);
}