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 $ 背包存在两个不同点,也就是该题的两个关键:

  1. $ fr $ 总值可能很大,无法压入状态进行表示;
  2. 要求是至多花费 $ c $ 且 $ fr $ 总和等于大于 $ f $ 的 $ be $ 总和最大值,这是一个范围,并不好直接维护。

肥肠妙的解决办法:

  1. 发现询问 $ f $ 最大值 $ \leq 500 $ ,于是只要把超出 $ 500 $ 的部分用来更 $ 501 $ 就行。
  2. 我们可以通过 $ 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;
}

总结

进步:

  1. 能总结出一些套路;

不足:

  1. 动态规划类型题目训练量太少,缺乏一个系统的总结;
  2. 思考方式过于单一,无法熟练运用逆向思维;
  3. 解题策略不够完善,容易被前面的题搞心态,比如 $ 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\)

posted @ 2025-07-08 23:17  Nightmares_oi  阅读(10)  评论(0)    收藏  举报