2026.10.1 考试

DP专题训练1

点击查看题面

最后一题时限 3s,其余题目时限 1s;所有题目内存限制 256M。


碰杯(toasting.in/toasting.out)

今天的酒席有 \(n\) 个人,他们要同时举杯,成对碰杯。碰杯的时候,不能有人不参与碰杯,也不希望有手臂交叉这种别扭的情况出现。如下图,左图的情况是好的,右图的情况是不希望出现的。

屏幕截图_1-10-2026_212559_

每个人都有一个喜爱的酒种类,每个人想要与和自己喝一样酒的人碰杯,请你设计一个方法,在保证每个人参与碰杯,且没有手臂交叉的情况下,有最多的人与喝一样酒的人碰杯,输出最多有多少人能与喝一样酒的人碰杯。

输入格式

第一行一个数 \(n\),表示酒桌上人的个数。

第二行 \(n\) 个数 \(c_i\),逆时针方向依次表示坐在第 \(i\) 个位置的人喝哪种酒,第 \(1\) 个和第 \(n\) 个人是相邻的。

输出格式

一个整数,表示在保证每个人参与碰杯,且没有手臂交叉的情况下,最多有多少人能与喝一样酒的人碰杯。

输入样例1

6
1 2 2 1 3 3

输出样例1

3

数据规模

  • 10% 数据满足 \(2 \le n \le 10\)
  • 100% 数据满足 \(2 \le n \le 1000\),\(1 \le c_i \le 100\)

分草莓(strawberry.in/strawberry.out)

院子里有一颗又高又大的草莓树,草莓树有 \(n\) 个节点,每个节点都结了一个草莓,吃掉第 \(i\) 个结点的草莓可以得到 \(a_i\) 的营养值,由于草莓可能会坏掉,所以 \(a_i\) 可能为负值,也可能为 \(0\)。

现在要砍掉这颗树的两条边,使树变成三份,并且使得三份各自草莓营养值的和恰好一样。请问是否有这样的方法呢?如果有,请输出 YES,否则输出 NO。

输入格式

第一行一个数 \(t\),表示测试点的个数。

接下来 \(t\) 组:

每组第一行一个数 \(n\),表示结点的个数。

接下来 \(n\) 行,每行两个数 \(fa\) 和 \(a_i\),表示第 \(i\) 个结点的父亲是 \(fa\),第 \(i\) 个结点有营养值为 \(a_i\) 的草莓,根节点的 \(fa\) 记为 \(0\)。

输出格式

输出 \(t\) 行,如果第 \(i\) 组有解,则输出 YES,否则输出 NO。

输入样例

2
6
2 4
0 5
4 2
2 1
1 1
4 2
6
2 4
0 6
4 2
2 1
1 1
4 2

输出样例

YES
NO

样例解释

第一组可以切掉 \(1\) 号和 \(4\) 号的父边,形成均等的三份。

数据规模

  • 10% 数据满足 \(3 \le n \le 100\)
  • 100% 数据满足 \(t \le 20\),\(3 \le n \le 100000\),\(-100 \le a_i \le 100\)

深意(meaning.in/meaning.out)

有的词语常常别有深意,比如 hehe 可以单纯地表示 hehe,也可以表示“excuse me?!”。现给出一段话 \(s\),和一个单词 \(t\),已知单词 \(t\) 有别有深意,也就是说有两个意思,求 \(s\) 可能有多少个意思?答案对 \(1000000007\) 取模。

输入格式

第一行一个数 \(n\),表示测试的组数。

接下来 \(n\) 组,每组两行,表示 \(s\) 和 \(t\)。

输出格式

一共 \(n\) 行,第 \(i\) 行表示第 \(i\) 组的 \(s\) 个意思个数。

输入样例

4
hehehe
hehe
woquxizaolehehe
woquxizaole
hehehehe
hehe
woyaogugenbierenliaotianle
wanan

输出样例

3
2
5
1

数据规模

  • 50% 数据满足 \(\mathrm{len}(s), \mathrm{len}(t) \le 1000\)
  • 100% 数据满足 \(\mathrm{len}(t) \le \mathrm{len}(s) \le 100000\),\(n \le 10\)。\(s\) 和 \(t\) 仅包含小写字母。

斐波那契子序列(fibonacci.in/fibonacci.out)

斐波那契序列是这样一个序列 \(\{A_n\}\),对于 \(A_i = A_{i-2} + A_{i-1}\)。现在给出一个序列 \(\{C_n\}\),求其中的最长斐波那契子序列。

输入格式

第一行一个数 \(n\),表示序列的长度。

第二行 \(n\) 个数,第 \(i\) 个数表示 \(C_i\)。

输出格式

一个整数,表示最长斐波那契子序列长度。

输入样例

10
1 1 3 -1 2 0 5 -1 -1 8

输出样例

5

样例解释

1 -1 0 -1 -1 为可行的子序列。

数据规模

  • 10% 数据满足 \(n \le 100\)
  • 100% 数据满足 \(n \le 3000\),\(\mathrm{abs}(C_i) \le 10^9\)

总结

\(T1:0~~~~T2:0~~~~T3:100~~~~T4:100\)
T1是区间DP,不会写;
T2是树形DP,不会写;
T3非正解(KMP),由于数据较水,暴力DP即可AC;
T4先想到了\(O(n^3)\)做法,但是肯定TLE。后面在oiwiki现学了一下哈希,优化到\(O(n^2)\),拿到AC。
总之,对于DP的掌握程度还不够,仍有许多DP知识已经遗忘或者根本没学,需要下来学习

题解

T1

区间DP板子题改,很简单

问题转化

我们可以把圆桌从某处“断开”成一条链。因为圆上不交叉的弦在断开后不会跨越断点,所以直接按输入顺序 \(1,2,\dots,n\) 作为链,用区间 DP 处理即可。

状态定义

设 \(f_{i,j}\) 表示:
在按逆时针顺序排列的区间 \([i, j]\) 内(包含 \(i\) 和 \(j\)),所有人两两碰杯且手臂不交叉时,最多能形成的“同酒碰杯对”的数量。
区间长度 \(len = j - i + 1\) 必须是偶数,否则无法让区间内所有人都配对,所以我们只计算长度为偶数的区间。

状态转移

对于区间 \([i, j]\),有两种配对方式:

Case 1:\(i\) 和 \(j\) 碰杯

此时 \(i\) 与 \(j\) 组成一对,剩下的人组成区间 \([i+1, j-1]\) 继续配对。
因为整个区间长度是偶数,所以 \(j - i + 1\) 是偶数,\(j - i\) 是奇数,那么 \([i+1, j-1]\) 的长度为 \((j-1) - (i+1) + 1 = j - i - 1\),是偶数,可以完全配对。

贡献为:

\[f_{i,j} = f_{i+1,j-1} + (a_i == a_j \ ? \ 1 : 0) \]

其中 \(a_i\) 表示第 \(i\) 个人喝的酒种类。

Case 2:\(i\) 和 \(j\) 不碰杯

那么区间内一定存在一个分界点 \(k\),使得配对完全发生在左半部分 \([i, k]\) 和右半部分 \([k+1, j]\) 内部,两部分互不交叉。
为了保证两个子区间长度都是偶数(能完全配对),\(k\) 必须满足:

  • \([i, k]\) 长度为偶数 \(\Rightarrow k - i + 1\) 为偶数 \(\Rightarrow k - i\) 为奇数。
  • 因此 \(k\) 从 \(i+1\) 开始,每次增加 2。

转移:

\[f_{i,j} = \max_{k = i+1, i+3, \dots}^{j-1} \big( f_{i,k} + f{k+1,j} \big) \]

综合两种情况,取最大值:

\[f_{i,j} = \max \Big( f_{i+1,j-1} + (a_i==a_j), \ \max_{k} (f_{i,k} + f_{k+1,j} )\Big) \]

顺序和边界

  • 区间长度 \(len\) 从 2 到 \(n\),每次增加 2(只处理偶数长度)。
  • 对于每个 \(len\),枚举左端点 \(i\),右端点 \(j = i + len - 1\)。
  • 因为转移需要用到更短区间的 \(f\) 值,所以按长度从小到大计算是正确的。
  • 初始时所有 \(f\) 值为 0。
  • 当 \(len=2\) 时,\(j = i+1\),情况 1 中 \(f_{i+1}{1}\) 是空区间,值为 0,所以 \(f_{i,i+1}=(a_i == a_{i+1}?1:0)\),符合预期。

\(AC~Code:\)

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1005;
const int INF=1e9;
int n,c[MAXN],f[MAXN][MAXN];
int main(){
    freopen("toasting.in","r",stdin);
    freopen("toasting.out","w",stdout);
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin>>n;
    if(n&1){
        cout<<0<<'\n';
        return 0;
    }
    for(int i=1;i<=n;i++){
        cin>>c[i];
        c[i+n]=c[i];
    }
    for(int len=2;len<=n;len+=2){
        for(int i=1;i+len-1<=n;i++){
            int j=i+len-1;
            f[i][j]=f[i+1][j-1]+(int)(c[i]==c[j]);
            for(int k=i+1;k<j;k+=2){
                f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
            }
        }
    }
    cout<<f[1][n]<<'\n';
    return 0;
}

T2

//TODO

T3

暴力DP

设\(n=s.size,m=t.size\),令\(f_i\)表示字符串\(s\)前\(i\)项有针对于字符串\(t\)的分解方式,则有以下状态转移方程:

\[f_i=f_{i-1} \]

则\(f_0=1\),那么我们可以从小到大枚举\(i\),当\(i\ge m\)后可以从枚举的前\(i\)项中再枚举长度为\(m\)的字符串,与\(t\)比较,如果相等,则有以下状态转移方程:

\[f_i=f_i+f_{i-m} \]

由于题目要求对答案取模,在计算时加上%MOD即可
\(AC~Code:\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100005;
const int MOD=1000000007;
int f[MAXN];
int main() {
    freopen("meaning.in","r",stdin);
    freopen("meaning.out","w",stdout);

    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    cin>>T;
    while(T--) {
        memset(f,0,sizeof(f));
        string s,t;
        cin>>s>>t;
        int n=(int)s.size();
        int m=(int)t.size();
        f[0]=1;
        for(int i=1;i<=n;i++){
            f[i]=f[i-1];
            if(i>=m&&s.substr(i-m,m)==t){
                f[i]=(f[i-m]+f[i])%MOD;
            }
        }
        cout<<f[n]<<'\n';
    }
    return 0;
}

正解

//TODO

T4

首先令\(f_{j,i}(j<i)\)表示以\(j、i\)结尾的斐波那契数列最长的长度,则其中任意初始值为\(2\),显然有如下状态转移方程:

\[f_{j,i}=f_{k,j}+1(k<j<i,C_i=C_j+C_k) \]

类似于基础求子序列,很容易就能想到三重循环的\(O(n^3)\)做法,但是数据范围为\(n \le 3000\),最多要运算\(3e9\)次,肯定会超时,此时我们应该考虑如何优化。显然,由方程可得\((C_k=C_i-C_j)\),这样我们就可以直接计算得出\(C_k\),但仍需找到结果的位置,明显用哈希表就可以实现。

\(AC~Code:\)

#include <bits/stdc++.h>
using namespace std;
const int MAXN=3005;
int n,c[MAXN],f[MAXN][MAXN];
int main(){
    freopen("fibonacci.in","r",stdin);
    freopen("fibonacci.out","w",stdout);

    ios::sync_with_stdio(false);
    cin.tie(0);

    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>c[i];
    }
    int ans=2;
    f[1][2]=2;
    for(int i=3;i<=n;i++){
        for(int j=1;j<i;j++) f[j][i]=2;
        unordered_map<int,int> mp(i*2);
        for(int j=1;j<i;j++){
            auto kp=mp.find(c[i]-c[j]);
            if(kp!=mp.end()){
                int k=kp->second;
                f[j][i]=f[k][j]+1;
                ans=max(ans,f[j][i]);
            }
            mp[c[j]]=j;
        }
    }
    cout<<ans<<endl;
    return 0;
}

posted @ 2026-10-02 12:01  %Sheeeeeeeep%  阅读(3)  评论(0)    收藏  举报