总计 $30$ 题。

洛谷

总计 $17$ 题。

P1002

没什么好说的,注意一下细节。

如果马的掌控范围不在棋盘内,特判防 RE。

$\Huge 不开 long\ long 见祖宗!!!$

#include<bits/stdc++.h>
using namespace std;
long long f[50][50]; 
int dx[]={1,1,-1,-1,2,-2,2,-2,0};
int dy[]={2,-2,2,-2,1,1,-1,-1,0};
int main()
{
    int n,m,x,y;
    cin>>n>>m>>x>>y;
    x++;
    y++;
    n++;
    m++;
    f[1][1]=1;
    for(int i=0;i<=8;i++)
    {
        int X=dx[i]+x,Y=dy[i]+y;
        if(X>0&&Y>0)f[X][Y]=-1;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(i==1&&j==1)continue;
            if(f[i][j]==-1)
            {
                f[i][j]=0;
                continue;
            }
            f[i][j]=f[i-1][j]+f[i][j-1];
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

P1044

对每个出栈的数,当前的方案数一定是比他小的方案数,乘上比它大的。

枚举一下即可。

#include<bits/stdc++.h>
using namespace std;
int f[20];
int main()
{
    int n;
    cin>>n;
    f[0]=f[1]=1;
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            f[i]+=f[j-1]*f[i-j]; 
        }
    }
    cout<<f[n]<<endl;
    return 0;
}

P1057

$f_{i,j}$ 表示第 $i$ 次传球,球在 $j$ 号同学的手中方案数。

每次往左往右加一下。

#include<bits/stdc++.h>
using namespace std;
int f[50][50];
int main()
{
    int n,m;
    cin>>n>>m;
    f[0][1]=1;
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(j==1)f[i][n]+=f[i-1][j];
            else f[i][j-1]+=f[i-1][j];
            if(j==n)f[i][1]+=f[i-1][j];
            else f[i][j+1]+=f[i-1][j];
        }
    }
    cout<<f[m][1]<<endl;
    return 0;
}

[P1077]()

$f_{i,j}$ 表示 $n$ 种花选 $m$ 盆的最大值。

然后直接多重背包。

#include<bits/stdc++.h>
using namespace std;
int f[120][120];
int a[120];
const int mod=1e6+7;
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            for(int k=0;k<=min(a[i],j);k++)
            {
                f[i][j]+=f[i-1][j-k];
                f[i][j]%=mod;
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

P1091

导弹拦截第一问做两遍,$n$ 规模较小 $\mathcal O(n^2)$ 的写法可过。

从左到右,从右到左各做一次,看能拦截多少“导弹”,最后减一下即为“拦截不住”即“该出列的学生”。

#include<bits/stdc++.h>
using namespace std;
int f[120];
int g[120];
int a[120];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])f[i]=max(f[i],f[j]);
        }
        f[i]++;
    }
    for(int i=n;i>=1;i--)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(a[j]<a[i])g[i]=max(g[i],g[j]);
        }
        g[i]++;
    }
    int ans=-114514;
    for(int i=1;i<=n;i++)
    {
        int x=f[i],y=g[i];
        ans=max(x+y-1,ans);
    }
    cout<<n-ans<<endl;
    return 0;
}

P1095

$f_i$ 表示第 $i$ 个时刻最长距离。

我们先考虑只能位移,然后再扫一遍,如果走路更优就走路。

#include<bits/stdc++.h>
using namespace std;
int f[300020];
int main()
{
    int m,s,t;
    cin>>m>>s>>t;
    for(int tt=1;tt<=t;tt++)
    {
        if(m>=10)
        {
            m-=10;
            f[tt]=f[tt-1]+60;
        }
        else
        {
            m+=4;
            f[tt]=f[tt-1];
        }
    }
    for(int tt=1;tt<=t;tt++)
    {
        f[tt]=max(f[tt],f[tt-1]+17);
        if(f[tt]>=s)
        {
            printf("Yes\n%d\n",tt);
            return 0;
        }
    }
    printf("No\n%d\n",f[t]);
    return 0;
}

P1358

热知识,杨辉三角可以用于求组合数。

$C^m_n$ 即为下标从 $0$ 开始的第 $n$ 行第 $m$ 列的数字。

#include<bits/stdc++.h>
using namespace std;
const int mod=10007;
int f[10020][120]; 
int main()
{
    f[0][0]=1;
    for(int i=1;i<=10000;i++)
    {
        f[i][0]=1;
    }
    for(int i=1;i<=10000;i++)
    {
        for(int j=1;j<=min(i,100);j++)
        {
            f[i][j]=f[i-1][j]+f[i-1][j-1];
            f[i][j]%=mod;
        }
    } 
    int n,m;
    long long ans=1;
    cin>>n>>m;
    while(m--)
    {
        int x;
        cin>>x;
        ans*=1LL*f[n][x];
        ans%=mod;
        n-=x;
    }
    cout<<ans<<endl;
    return 0;
}

P1439

乍一眼,是个 LCS。

但看了 阮行止 大佬的博客,受益匪浅,可以转换成 LIS 做。

所以这题是个复杂度只能为 $\mathcal O(n\log n)$ 的导弹拦截第一问。

但略有不同,导弹拦截是最长不上升子序列,即非严格 LDS,这题是最长上升,即 LIS。

但原理相同,二分优化。

#include<bits/stdc++.h>
using namespace std;
int a[100020];
int b[100020];
int f[100020];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        a[x]=i;
    }
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        b[i]=a[x];
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(!ans||f[ans]<b[i])
        {
            f[++ans]=b[i];
        }
        else
        {
            int mid=lower_bound(f+1,f+ans+1,b[i])-f;
            f[mid]=b[i];
        }
    }
    cout<<ans<<endl;
    return 0;
}

P1616

完全背包板子。

$\Huge 不开 long\ long 见祖宗!!!$

#include<bits/stdc++.h>
using namespace std;
long long f[10000020];
int a[10020];
int b[10020];
int main()
{
    int v,n;
    cin>>v>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i]>>b[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=a[i];j<=v;j++)
        {
            f[j]=max(f[j],f[j-a[i]]+b[i]);
        }
    }   
    cout<<f[v]<<endl;
    return 0;
}

P1679

完全背包。

价值为 $1$,重量为 $i^4$,问价值最小。

#include<bits/stdc++.h>
using namespace std;
const int mod=10007;
int f[100020]; 
int a[50];
int main()
{
    memset(f,0x3f,sizeof(f));
    int m;
    cin>>m;
    for(int i=1;i<=50;i++)
    {
        a[i]=i*i*i*i;
    }
    memset(f,0x7f,sizeof(f));
    f[0]=0;
    for(int i=1;i<=50;i++)
    {
        for(int j=a[i];j<=m;j++)
        {
            f[j]=min(f[j],f[j-a[i]]+1);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

P1734

01背包,物品重量 $i$,价值 $a_i$。

$a_i$ 为 $i$ 的约数和去掉本身。

#include<bits/stdc++.h>
using namespace std;
int f[1020];
int a[1020];
int main()
{
    int n;
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(i%j==0)
            {
                a[i]+=j;
            } 
        }
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=n;j>=i;j--)
        {
            f[j]=max(f[j],f[j-i]+a[i]);
        }
    } 
    cout<<f[n]<<endl;
    return 0;
}

P1853

表面上题目挺吓人的,但其实只是完全背包,容量每年在变而已。

#include<bits/stdc++.h>
using namespace std;
long long f[10000020];
int a[10020];
int b[10020];
int main()
{
    int v,n,x;
    cin>>v>>x>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i]>>b[i];
    }
    for(int k=1;k<=x;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=a[i];j<=v;j++)
            {
                f[j]=max(f[j],f[j-a[i]]+b[i]);
            }
        }   
        v+=f[v];
    }
    cout<<v<<endl;
    return 0;
}

P2008

求最长不下降子序列时,顺便存一下和即可。

#include<bits/stdc++.h>
using namespace std;
int a[10020];
int f[10020];
int s[10020];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(a[j]<=a[i])
            {
                if(f[j]>f[i])
                {
                    f[i]=f[j];
                    s[i]=s[j];
                }
            }
        }
        s[i]+=a[i];
        f[i]++;
        cout<<s[i]<<" ";
    }
    return 0;
}

P2347

和背包很类似,如果 $i$ 重量可以组成,$i+w_i$ 也可以组成。

暂且把 $w_i$ 看成题目中的砝码重量。

#include<bits/stdc++.h>
using namespace std;
bool f[1020];
int main()
{
    f[0]=1;
    for(int i=1;i<=6;i++)
    {
        int x,y=0;
        scanf("%d",&x);
        switch(i)
        {
            case 4:
                y=5;
                break;  
            case 5:
                y=10;
                break;  
            case 6:
                y=20;
                break;  
            default:
                y=i;
        }
        for(int j=1;j<=x;j++)
        {
            for(int k=1000;k>=y;k--)
            {
                f[k]|=f[k-y];
            } 
        }
    }
    int ans=0;
    for(int i=1;i<=1000;i++)
    {
        ans+=f[i];
    }
    printf("Total=%d",ans);
    return 0;
}

当然,上个代码中,

for(int k=1000;k>=y;k--)
{
    f[k]|=f[k-y];
}

这一部分,可以简化。

C++ 一个 STL 容器,叫做 bitset。

bitset 自带或操作和位移操作。

那么,上述代码可以简化为

b|=b<<y;

b<<y 是整个容器右移 $y$ 位,相当于原来代码 $-y$。

#include<bits/stdc++.h>
using namespace std;
bitset<1020>b;
int main()
{
    b[0]=1;
    for(int i=1;i<=6;i++)
    {
        int x,y=0;
        scanf("%d",&x);
        switch(i)
        {
            case 4:
                y=5;
                break;  
            case 5:
                y=10;
                break;  
            case 6:
                y=20;
                break;  
            default:
                y=i;
        }
        for(int j=1;j<=x;j++)
        {
            b|=b<<y;
        }
    }
    printf("Total=%d",b.count()-1);
    return 0;
}

P2639

01背包,装箱问题的双倍经验。

#include<bits/stdc++.h>
using namespace std;
int a[520];
int b[520];
int f[45020];
int main()
{
    int v,n;
    cin>>v>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=a[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=v;j>=a[i];j--)
        {
            f[j]=max(f[j],f[j-a[i]]+b[i]);
        }
    }
    cout<<f[v]<<endl;
    return 0;
}

P2722

无限背包板子。

#include<bits/stdc++.h>
using namespace std;
int a[10020];
int b[10020];
int f[10020];
int main()
{
    int v,n;
    cin>>v>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i]>>b[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=b[i];j<=v;j++)
        {
            f[j]=max(f[j],f[j-b[i]]+a[i]);
        }
    }
    cout<<f[v]<<endl;
    return 0;
}

P2925

01背包模板。

#include<bits/stdc++.h>
using namespace std;
int f[50020];
int a[5020];
int b[5020];
int main()
{
    int v,n;
    cin>>v>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=a[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=v;j>=a[i];j--)
        {
            f[j]=max(f[j],f[j-a[i]]+b[i]);
        }
    }
    cout<<f[v]<<endl;
    return 0;
}

CF

总计 $6$ 题。

CF414B

又是个小清新题!

$f_{i,j}$ 表示最大 $i$,长度 $j$ 的序列个数。

伪代码如下:

枚举数列长度
    枚举最大值
        枚举与前一项的商

但显然,复杂度 $\mathcal O(n^2k)$,然而本题 $n\leq 2000,k\leq 2000$ 明显不行。

考虑优化,如果前一项乘上商已经比 $n$ 大,就可以换一个最大值枚举了。

#include<bits/stdc++.h>
using namespace std;
int f[2020][2020];
const int mod=1e9+7;
int main()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        f[i][1]=1;
    }
    for(int j=2;j<=k;j++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int _=1;_*i<=n;_++)
            {
                f[i*_][j]+=f[i][j-1];
                f[i*_][j]%=mod;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans+=f[i][k];
        ans%=mod;
    }
    cout<<ans<<endl;
    return 0;
}

CF417A

无限背包。

题意很复杂,什么保送,什么赛制。

其实就是做无限背包后求 $\min\limits_{x \in [0,2]}\{f_{n\times m-x}\}$ 。

#include<bits/stdc++.h>
using namespace std;
int f[10020]; 
int a[10];
int b[10];
int main()
{
    memset(f,0x3f,sizeof(f));
    int c,d,n,m,k,v;
    cin>>c>>d>>n>>m>>k;
    if(n*m<=k)
    {
        cout<<0<<endl;
        return 0;
    }
    v=n*m;
    a[1]=n;
    b[1]=c;
    a[2]=1;
    b[2]=d;
    f[0]=0;
    for(int kk=0;kk<=k;kk++)
    {
        v=n*m-kk;
        for(int i=1;i<=2;i++)
        {
            for(int j=a[i];j<=v;j++)
            {
                f[j]=min(f[j],f[j-a[i]]+b[i]);
            }
        }
    }
    int ans=INT_MAX;
    for(int kk=0;kk<=k;kk++)
    {
        v=n*m-kk;
        ans=min(ans,f[v]);
    }
    cout<<ans<<endl;
    return 0;
}

CF431C

小清新题,爱了爱了。

用 $f_{i,0/1}$ 分别表示权值和为 $i$ 时,当前边权(看为 $j$)是否大于等于 $d$。

分成 $j\geq d$ 和 $j<d$ 两种情况。

  • $j\geq d$

    $f_{i,0}= f_{i-j,0}$

    $f_{i,1}=f_{i-j,0}+f_{i-j,1}$

  • $j<d$

    $f_{i,0}= f_{i-j,0}$

    $f_{i,1}=f_{i-j,1}$

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
long long f[120][2];
int main()
{
    int n,k,d;
    cin>>n>>k>>d;
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=k;j++)
        {
            if(j>i)break;
            if(j>=d)
            {
                f[i][1]+=f[i-j][0]+f[i-j][1];
                f[i][1]%=mod;
            }
            else
            {
                f[i][0]+=f[i-j][0];
                f[i][1]+=f[i-j][1];
                f[i][0]%=mod;
                f[i][1]%=mod;
            }
        }
    }
    cout<<f[n][1]<<endl;
    return 0;
}

CF545C

读完这题,你就会发现,这题是个贪心。

确实可以贪心,这题贪心十分简单,所以我们写 dp。

不为别的,就为装B。

dp 用 $f_{i,0/1/2}$ 表示第 $i$ 棵树不倒/往左边倒/往右边倒时的最大倒下树木。

  • $f_{i,0}$

    即为前面的三种情况取最大值。

  • $f_{i,1}$

    分类讨论,再空间足够时,讨论由哪种转变来。

  • $f_{i,2}$

    如果可以就往右倒。

#include<bits/stdc++.h>
using namespace std;
int x[100020];
int h[100020];
int f[100020][3];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>x[i]>>h[i];
    }
    f[1][1]=1;
    f[1][2]=x[2]-x[1]>h[1];
    x[n+1]=2000000001;
    for(int i=2;i<=n;i++)
    {
        f[i][0]=max(max(f[i-1][0],f[i-1][1]),f[i-1][2]);
        if(h[i-1]+x[i-1]<x[i]-h[i])
        {
            f[i][1]=f[i-1][2]+1;        
        }
        if(x[i-1]<x[i]-h[i])
        {
            f[i][1]=max(f[i][1],max(f[i-1][0]+1,f[i-1][1]+1));
        }
        if(x[i]+h[i]<x[i+1])
        {
            f[i][2]=max(max(f[i-1][0],f[i-1][1]),f[i-1][2])+1;
        } 
    }
    cout<<max(max(f[n][0],f[n][1]),f[n][2])<<endl; 
    return 0;
}

CF933A

多看大佬博客受益匪浅。

所以这题暴力。

$n=2000$,暴力想都别想。

得到的最长不下降,一定是 $111\dots222\dots111\dots222$ 这种排列的。

我们用 $f_{i,j}$ 表示前 $i$ 个第 $j$ 段的答案。

那么可以得出:

$f_{i,1}=f_{i-1,1}+[a_i=1]$

最后修改:2023 年 04 月 23 日
v我50吃疯狂星期四