WY模拟赛5
WY模拟赛5
T1. 洛谷 P11079 「FSLOI Round I」山峦
设计状态: $ f[i][j][1/2/3/4] $ ,分别表示以 $ a[i] $ 结尾,最高峰为 $ j $ ,当前转态时准备上山/正在上山/准备下山/正在下山的方案数。
得到状态转移方程:
$ f[i][j][1]=f[i][j][1]+(a[k]>=a[i]?f[k][j][4]:0)+f[k][j][3] $ ;
$ f[i][j][2]=f[i][j][2]+f[k][j][1]+f[k][j][2] $ ,条件:$ (a[k]< a[i]) $ ;
$ f[i][a[k]][3]=f[i][a[k]][3]+f[k][j][2] $ ,条件:$ (a[k]>a[i],j< a[k]) $ ;
$ f[i][j][4]=f[i][j][4]+f[k][j][3]+f[k][j][4] $ ,条件: $ (a[i]< a[k]) $ ;
对于所有的 $ k $ 满足: $ k< i $ 。注意此题似乎有点卡常需要// #define int long long 。
code:
#include <bits/stdc++.h>
#define i8 __int128
// #define int long long
#define fuck inline
#define lb long double
using namespace std;
// typedef long long ll;
const int N=1e3+5,M=64,mod=998244353;
const int inf=INT_MAX,INF=1e9+7;
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118;
// const int M=mod1*mod2;
fuck int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
return x*f;
}
fuck void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,a[N],b[N];
int f[505][505][8];
fuck void solve()
{
// memset(f,0,sizeof(f));
for(int i=1;i<=500;i++)
for(int j=0;j<=500;j++)
for(int k=0;k<=4;k++)f[i][j][k]=0;
n=read();
for(int i=1;i<=n;i++)a[i]=read(),b[i]=a[i];
sort(b+1,b+n+1);int cnt=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
// for(int i=1;i<=n;i++)cout<<a[i]<<" ";
for(int i=1;i<=n;i++)
{
f[i][0][1]=1;
for(int k=1;k<i;k++)
for(int j=0;j<=cnt;j++)
{
f[i][j][1]=(f[i][j][1]+f[k][j][3])%mod;
if(a[k]>=a[i])f[i][j][1]=(f[i][j][1]+f[k][j][4])%mod;
}
for(int k=1;k<i;k++)
{
if(a[i]<=a[k])continue;
for(int j=0;j<=cnt;j++)
{
f[i][j][2]=(f[i][j][2]+f[k][j][1])%mod;
f[i][j][2]=(f[i][j][2]+f[k][j][2])%mod;
}
}
for(int k=1;k<i;k++)
{
if(a[i]>=a[k])continue;
for(int j=0;j<a[k];j++)
{
f[i][a[k]][3]=(f[i][a[k]][3]+f[k][j][2])%mod;
}
}
for(int k=1;k<i;k++)
{
if(a[i]>=a[k])continue;
for(int j=0;j<=cnt;j++)
{
f[i][j][4]=(f[i][j][4]+f[k][j][4])%mod;
f[i][j][4]=(f[i][j][4]+f[k][j][3])%mod;
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=cnt;j++)
{
ans=(ans+f[i][j][4])%mod;
ans=(ans+f[i][j][3])%mod;
}
write(ans);
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
// int fuckccf=read();
// int QwQ=read();
// while(QwQ--)solve();
solve();
return 0;
}
// 6666 66666 666666
// 6 6 6 6 6
// 6 6 6666 6
// 6 6 6 6 6
// 6666 6 6 6666666
T2. 洛谷 P8548 小挖的买花
妙妙题
题目本身是个二维的 $ 01 $ 背包,这是很显然的。
但是与普通的 $ 01 $ 背包存在两个不同点,也就是该题的两个关键:
- $ fr $ 总值可能很大,无法压入状态进行表示;
- 要求是至多花费 $ c $ 且 $ fr $ 总和等于大于 $ f $ 的 $ be $ 总和最大值,这是一个范围,并不好直接维护。
肥肠妙的解决办法:
- 发现询问 $ f $ 最大值 $ \leq 500 $ ,于是只要把超出 $ 500 $ 的部分用来更 $ 501 $ 就行。
- 我们可以通过 $ dp $ 维护出总花费恰好为 $ i $ ,总新鲜度恰好为 $ j $ 的最大 $ be $ 和,考虑如何将其转化为总新鲜度至少为 $ f $ 。一个很妙的方法是计算后缀最大值,数组 $ suf[i][j] $ 表示 $ cost $ 总和恰好为 $ i $ 时总新鲜度至少为 $ j $ 时最大值,为什么呢?可以理解为一种推平的操作,将符合题意且更优的解覆盖符合题意却稍劣的解。然后就是至多花费 $ c $ ,同样的思路做一遍前缀最大值即可。
注意数组需要初始化为负无穷。具体说明。
#include <bits/stdc++.h>
#define i8 __int128
#define int long long
#define fuck inline
#define lb long double
using namespace std;
// typedef long long ll;
const int N=8388608+5,M=64,mod=536870912;
const int inf=INT_MAX,INF=1e9+7;
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118;
// const int M=mod1*mod2;
fuck int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
return x*f;
}
fuck void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,q;
struct node
{
int ct,fr,be;
}p[N];
int f[505][505];
int suf[505][505],pre[505][505];
fuck void solve()
{
for(int i=0;i<=500;i++)
for(int j=0;j<=501;j++)f[i][j]=pre[i][j]=suf[i][j]=-inf;
f[0][0]=0;
cin>>n>>q;
for(int i=1;i<=n;i++)p[i].ct=read(),p[i].fr=read(),p[i].be=read();
for(int k=1;k<=n;k++)
for(int i=500;i>=p[k].ct;i--)
{
for(int j=501;j>=501-p[k].fr;j--)f[i][501]=max(f[i][501],f[i-p[k].ct][j]+p[k].be);
for(int j=500;j>=p[k].fr;j--) f[i][j]=max(f[i][j],f[i-p[k].ct][j-p[k].fr]+p[k].be);
}
// for(int i=1;i<=20;i++)
// {
// for(int j=1;j<=21;j++)cout<<f[i][j]<<" ";
// cout<<endl;
// }
for(int i=0;i<=500;i++)
for(int j=501;j>=0;j--)suf[i][j]=max(f[i][j],suf[i][j+1]);
//恰好花费i,新鲜度至少为j的最大值
for(int i=0;i<=500;i++)
for(int j=0;j<=501;j++)pre[i][j]=max(suf[i][j],pre[max(0*1ll,i-1)][j]);
//花费至多为i,新鲜度至少为j的最大值
while(q--)
{
int x,y;cin>>x>>y;
cout<<pre[x][y]<<endl;
}
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
// int fuckccf=read();
// int QwQ=read();
// while(QwQ--)solve();
solve();
return 0;
}
// 6666 66666 666666
// 6 6 6 6 6
// 6 6 6666 6
// 6 6 6 6 6
// 6666 6 6 6666666
T3. 洛谷 P10236 [yLCPC2024] D. 排卡
很容易可以看出来是一道区间 $ dp $ 题,但是写的时候对着 $ b $ 序列疯狂思考,然后看完题解后发现思考了个寂寞。
这道区间 $ dp $ 的一个核心是找到一个连续的区间。正向思考发现区间是两端进行的,并不是连续的。但是我从最后一弹出队列 $ a $ 的进行反向思考,发现上一个一定是从其两旁转移出去的,于是就找到了这个连续的区间。
状态设计: $ f[l][r][0] $ 表示区间 $ [l,r] $ 当前从 $ l $ 端取出的最大值, $ f[l][r][1] $ 表示区间 $ [l,r] $ 当前从 $ r $ 端取出的最大值。转移的话就很简单了。
#include <bits/stdc++.h>
#define i8 __int128
#define int long long
#define fuck inline
#define lb long double
using namespace std;
// typedef long long ll;
const int N=1e3+5,M=64,mod=998244353;
const int inf=INT_MAX,INF=1e9+7;
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118;
// const int M=mod1*mod2;
fuck int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
return x*f;
}
fuck void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
fuck int ksm(int a,int b)
{
if(a==0&&b==0)return 0;
int res=1;
while(b)
{
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res%mod;
}
int n;
int a[N],f[N][N][3];
fuck void solve()
{
memset(f,0,sizeof(f));
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
// for(int i=0;i<=n;i++)
// {
// for(int j=0;j<=n;j++)cout<<f[i][j][1]<<" ";
// cout<<endl;
// }
// for(int i=0;i<=n;i++)
// {
// for(int j=0;j<=n;j++)cout<<f[i][j][0]<<" ";
// cout<<endl;
// }
for(int len=2;len<=n;len++)
for(int l=1;l<=(n-len+1);l++)
{
int r=len+l-1;
// cout<<a[l]<<" "<<a[l+1]<<endl;
// cout<<ksm(a[l],a[l+1])<<endl;
f[l][r][0]=max(f[l][r][0],f[l+1][r][0]+ksm(a[l],a[l+1]));
f[l][r][0]=max(f[l][r][0],f[l+1][r][1]+ksm(a[l],a[r]));
f[l][r][1]=max(f[l][r][1],f[l][r-1][0]+ksm(a[r],a[l]));
f[l][r][1]=max(f[l][r][1],f[l][r-1][1]+ksm(a[r],a[r-1]));
}
cout<<max(f[1][n][0],f[1][n][1])<<endl;
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
// int fuckccf=read();
int QwQ=read();
while(QwQ--)solve();
// solve();
return 0;
}
// 6666 66666 666666
// 6 6 6 6 6
// 6 6 6666 6
// 6 6 6 6 6
// 6666 6 6 6666666
T4. 洛谷 P1541 [NOIP 2010 提高组] 乌龟棋
好吧,确实挺水的
由于 $ 4 $ 种牌的数量并不是很多,直接打包全都丢进状态里。
直接枚举 $ 4 $ 种牌各花费多少张,并取一下 $ 4 $ 种牌放与不放的最大值即可,注意最开始有一个 $ 1 $ 。
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+520,mod=1000000007 ;
typedef long long ll;
int n,m,x[N],y[N],kkksc03;
ll f[55][55][55][55];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>x[i];
for(int i=1;i<=m;i++) cin>>kkksc03,y[kkksc03]++;
f[0][0][0][0]=x[1];
for(int a=0;a<=y[1];a++)
for(int b=0;b<=y[2];b++)
for(int c=0;c<=y[3];c++)
for(int d=0;d<=y[4];d++)
{
int sum=1+a+b*2+c*3+d*4;
if(a!=0) f[a][b][c][d]=max(f[a][b][c][d],f[a-1][b][c][d]+x[sum]);
if(b!=0) f[a][b][c][d]=max(f[a][b][c][d],f[a][b-1][c][d]+x[sum]);
if(c!=0) f[a][b][c][d]=max(f[a][b][c][d],f[a][b][c-1][d]+x[sum]);
if(d!=0) f[a][b][c][d]=max(f[a][b][c][d],f[a][b][c][d-1]+x[sum]);
}
cout<<f[y[1]][y[2]][y[3]][y[4]]<<endl;
return 0;
}
总结
进步:
- 能总结出一些套路;
不足:
- 动态规划类型题目训练量太少,缺乏一个系统的总结;
- 思考方式过于单一,无法熟练运用逆向思维;
- 解题策略不够完善,容易被前面的题搞心态,比如 $ T2 $ 。
完结收工!!!!!

看完点赞,养成习惯
\(\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\)

浙公网安备 33010602011771号