luogu P5662 纪念品
题面传送门
说实话,\(ccf\)又抄袭原题惹。而且居然是这道题的弱化版。不应该像是这样的原题的稍微弱化版吗?
言归正传,这道题看似像模拟。又可以爆搜,不过复杂度太高。也太难打,所以略微思考一下,打了一个五重循环的\(dp\)。看看太恶心,再加上样例也没过,于是就放下了。打了一个贪心保底:
#include<bits/stdc++.h>
using namespace std;
inline int max(register int a,register int b) {
return a>b?a:b;
}
int n,m,t,a[139][139],l[139][139],r[139][139],ans,fs[139][139],flag1,flag2,tot,fx[139][139];
int main() {
// freopen("souvenir.in","r",stdin);
// freopen("souvenir.out","w",stdout);
register int i,j,k;
scanf("%d%d%d",&t,&n,&m);
for(i=1; i<=t; i++) {
for(j=1; j<=n; j++) scanf("%d",&a[i][j]);
}
for(i=1; i<=t; i++) {
for(j=1; j<=n; j++) {
flag1=flag2=0;
for(k=i+1; k<=t; k++) {
if(a[k][j]<a[i][j]&&!flag1) flag1=1,l[i][j]=k;
if(a[k][j]>a[i][j]&&!flag2) flag2=1,r[i][j]=k;
}
}
}
for(i=0;i<=t;i++){
for(j=0;j<=n;j++) fs[i][j]=m;
}
for(i=1; i<t; i++) {
for(j=1; j<=n; j++) {
if(l[i][j]&&(!r[i][j]||r[i][j]>=l[i][j])) fs[i][j]=fs[i-1][j]+fx[i-1][j]*a[i][j],fx[i][j]=0;
if(r[i][j]&&(!l[i][j]||r[i][j]<=l[i][j])) fx[i][j]=fx[i-1][j]+fs[i-1][j]/a[i][j],fs[i][j]=fs[i-1][j]-fs[i-1][j]/a[i][j]*a[i][j];
}
}
for(i=1; i<=n; i++) ans=max(ans,fs[t-1][i]+fx[t-1][i]*a[t][i]);
printf("%d",ans);
return 0;
}
考完后在\(luogu\)上交了一遍,\(25\)分。
觉得贪心没有组合,不保险,于是想了一个小时五十分钟的\(dp\),结果很悲惨的发现只剩十分钟了。我的\(T3\)啊!
\(dp\)思路:(套用吴老师的经典语录) 如果一道题,既可以搜索,又看上去可以贪心,那么大部分就是\(dp\)(\(rsj\)追问:那么那一小部分呢?吴老师:那就是贪心!) (\(rsj\)反驳:为什么是我!除了\(rsj\)的同学:就是你!) 我们设\(f_{i,j,k}\)为第\(i\)天买到第\(j\)件而花了\(k\)元钱所赚的钱。那么状态转移方程可以想象:\(f_{i,j,k}=max(f_{i,j,k},f_{i,j,k-a_{i,j}}+a_{i,j}-a_{i-1,j}\);因为这是一个完全背包,所以我们可以正序枚举。而同样,我们注意到\(f_{100,100,10000}\)会爆内存,所以我们可以滚动。所以状态转移方程可以改为:\(f_{i,k}=max(f_{i,k},f_{i,k-a_{i,j}}+a_{i,j}-a_{i-1,j}\),而如果\(a_{i,j}\leq a_{i-1,j}\),那么就没有意义了。所以我们可以特判一下,卡个常(这只是最初级的)。
代码君奉上:
#include<cstdio>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n,m,t,a[139][139],f[139][10039],ans,tot;
int main() {
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
register int i,j,k;
scanf("%d%d%d",&t,&n,&m);
for(i=1; i<=t; i++) {
for(j=1; j<=n; j++) scanf("%d",&a[i][j]);
}
ans=m;
for(i=1; i<t; i++) {
tot=0;
for(j=1; j<=n; j++) {
if(a[i][j]<a[i+1][j]) {
for(k=a[i][j]; k<=ans; k++) {
f[i][k]=max(f[i][k],f[i][k-a[i][j]]+a[i+1][j]-a[i][j]);
tot=max(tot,f[i][k]);
}
}
}
ans+=tot;
}
printf("%d",ans);
return 0;
}
但是......原题很卡常。于是我们可以稍微的玩一玩。
我们观察到第十九行:
f[i][k]=max(f[i][k],f[i][k-a[i][j]]+a[i+1][j]-a[i][j]);
tot=max(tot,f[i][k]);
\(k-a_{i,j}\)十分耗时间,于是我们可以提前计算。\(a_{i+1,j}-a_{i,j}\)也慢,于是也可以提前计算。而\(tot\)的取值也在重复计算,于是我们可以把它拉到外面来一次。
终极代码如下:
#include<cstdio>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n,m,t,a[139][139],f[139][10039];
inline void read(register int &x){
x=0;register int s=getchar();
while(s<'0'||s>'9') s=getchar();
while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+(s^48),s=getchar();
}
int main() {
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
register int i,j,k,tot,ans=0,now,pus;
scanf("%d%d%d",&t,&n,&m);
for(i=1; i<=t; i++) {
for(j=1; j<=n; j++) read(a[i][j]);
}
ans=m;
for(i=1; i<t; i++) {
tot=0;
for(j=1; j<=n; j++) {
if(a[i][j]<a[i+1][j]) {
now=a[i+1][j]-a[i][j];pus=0;
for(k=a[i][j]; k<=ans; k++) {
f[i][k]=max(f[i][k],f[i][pus]+now);
pus++;
}
}
}
for(j=0;j<=ans;j++) tot=max(tot,f[i][j]);
ans+=tot;
}
printf("%d",ans);
return 0;
}
第一次提交记录
而改良后的提交记录
你是不是觉得看不出差异,那么看一看原题:第一次提交记录
第二次提交记录
看出差异了吧!