题解 P4713 【「语文」凑字数】
Treeloveswater · · 题解
Emmmm和出题人交流了一下,发现自己的算法没问题
也成功拿到最快的44ms
我来说一下我的做法吧,这个做法的复杂度非常的优越
首先我们肯定是要 2^k去枚举每个部分的状态的
复杂度优化的关键在于Dp
我们发现如果固定扣的分数,那么行数自然是尽量大。在行数一样大的情况下自然是最后一行字越多越好。
我们发现可以在dp的基础上贪心。
我们设f[i][j]为 dp到第i个句子,现在扣了j分
f[i][j]是一个pair,保存着两个值 行数 最后一行的字数
这样Dp的复杂度是 n·cnt·S cnt是不会扣到0的部分的数量
2^k种状态的cnt加和是 k*2^(k-1)
所以最后的复杂度是 2^(k-1)kSn == 80 · 200 · 200
非常优越的复杂度,甚至可以把S,n提高到300都可以做
附上代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
pair<int,int> f[201][1005];
int n,m,L,K,S,C;
int A[205];
int s[205][11];
bool vis[201][1005],ok[11];
void up(int i,int j,int a,int b){
if(!vis[i][j]){
vis[i][j]=1;
f[i][j]=make_pair(a,b);
}
else{
if(f[i][j].first>a)return;
if(f[i][j].first<a)
f[i][j]=make_pair(a,b);
else
f[i][j].second=max(f[i][j].second,b);
}
}
int main(){
scanf("%d%d%d%d%d%d",&n,&m,&L,&K,&S,&C);
for(int i=1;i<=n;i++) scanf("%d",&A[i]);
for(int i=1;i<n;i++)
for(int j=0;j<K;j++)
scanf("%d",&s[i][j]);
int ans=0;
for(int t=1;t<(1<<K);t++){
memset(vis,0,sizeof(vis));
for(int i=0;i<K;i++) ok[i]=(t>>i)&1;
int cnt=0,a,b,c,d,e;
for(int i=0;i<K;i++) cnt+=ok[i];
a=(A[1]+2)/L;b=(A[1]+2)%L;
if(!b) b=L;
else a++;
f[1][0]=make_pair(a,b);vis[1][0]=1;
for(int i=1;i<n;i++)
for(int j=0;j<=cnt*S;j++)
if(vis[i][j]){
a=f[i][j].first,b=f[i][j].second;
e=0;
c=a-1+(b+A[i+1])/L,d=(b+A[i+1])%L;
if(!d) d=L; else c++;
for(int k=0;k<K;k++) if(ok[k]&&s[i][k]<0) e-=s[i][k];
if(j+e<=cnt*S) up(i+1,j+e,c,d);
e=0;
c=a+(A[i+1]+2)/L,d=(A[i+1]+2)%L;
if(!d) d=L; else c++;
for(int k=0;k<K;k++) if(ok[k]&&s[i][k]>0) e+=s[i][k];
if(j+e<=cnt*S) up(i+1,j+e,c,d);
}
int total=cnt*S,zm;
for(int j=0;j<=cnt*S;j++) if(vis[n][j]){
zm=total-j;
if(f[n][j].first<m) zm-=(m-f[n][j].first)*C;
ans=max(ans,zm);
}
}
cout<<ans<<endl;
}