P11230 [CSP-J 2024] 接龙(官方数据) 题解

· · 个人记录

题目介绍

n 个人,第 i 个人有一个整数序列 S_i 作为他的词库。

一次游戏分为 t 轮,每一轮规则如下:

样例

样例输入

1
3 3 7
5 1 2 3 4 1
3 1 2 5
3 5 1 6
1 2
1 4
2 4
3 4
6 6
1 1
7 7

样例输出

1
0
1
0
1
0
0

提示

【样例 1 解释】

在下文中,我们使用 \{A_i\} = \{A_1, A_2, \dots , A_r\} 表示一轮游戏中所有的接龙序列,\{p_i\} = \{p_1, p_2, \dots , p_r\} 表示对应的接龙的人的编号。由于所有字符均为一位数字,为了方便我们直接使用数字字符串表示序列。

【数据范围】

对于所有测试数据,保证:

测试点 n\leq r\leq \sum l\leq q\leq 特殊性质
1 10^3 1 2000 10^3
2,3 10 5 20 10^2
4,5 10^3 10 2000 10^3 A
6 10^5 10^2 2\times 10^5 10^5 A
7,8 10^3 10 2000 10^3 B
9,10 10^5 10^2 2\times 10^5 10^5 B
11,12 10^3 10 2000 10^3 C
13,14 10^5 10^2 2\times 10^5 10^5 C
15\sim 17 10^3 10 2000 10^3
18\sim 20 10^5 10^2 2\times 10^5 10^5

特殊性质 A:保证 k = 2 \times 10^5

特殊性质 B:保证 k ≤ 5

特殊性质 C:保证在单组测试数据中,任意一个字符在词库中出现次数之和均不超过 5

原题链接

100pts 思路讲解

算法:动态规划

时间复杂度:O(n)

通过搜索可以拿 25 分,可以考虑运用记忆化搜索来优化程序效率,进而可以想到从动态规划的角度思考这道题的解法。

对于第 j 个数字序列 S_j ,将其长度记为 len_j。不妨认为 S_j 的下标范围为 0 \sim len_j-1

我们定义状态 dp_{i,j,t} 表示在第 i 轮中,能否以第 j 个数字序列的第 t 个位置作为起点(其中第 2 维和第 3 维的状态总数不超过 \sum len = 200,000)。

定义状态 f_{i,j,t} 表示在第 i 轮中,能否以第 j 个数字序列的第 t 个位置作为终点(其中第 2 维和第 3 维的状态总数不超过 \sum len = 200,000)。

动态规划的边界条件:若 S_{j,t}=1,则 dp_{1,j,t}=\text{true};否则 dp_{1,j,t}=\text{false}

动态规划的答案表示:对于询问 (r,c),若存在 j,t 满足 S_{j,t}=c 并且 f_{r,j,t}=\text{true},则说明存在一种接龙方案,否则说明不存在。

分析状态转移方程

转移可以分为两类:

第一类转移的暴力实现需要 O(k\sum l ) 的时间复杂度,因为对于每一个 dp_{i,j,t} = \text{true} 的位置,需要将 f_{i,j,t+1}\ldots f_{i,j,\min(t+k-1,l_j - 1)} 都标记为 \text{true}

第二类转移的暴力实现需要需要 O((\sum l)^2) 的时间复杂度,因为对于每一个 f_{i,j,t}\text{true} 的位置,我们需要找到所有的 j't' 满足 S_{j',t'}=S_{j,t}j' \neq j,将 dp_{i+1,j',t'} 设置为 \text{true}

上面两种转移的暴力实现总时间复杂度为 O(r(k+\sum l)\sum l ),会超时,需要进一步优化。

dp_i 转移到同一轮 f_i 的优化

我们发现,对于每一个 dp_{i,j,t}\text{true} 的位置,需要将连续一段区间的 f_{i, j, t} 标记为 \text{true}。那么我们可以通过一个长度为 l_j 的差分数组 num 比较快地实现连续一段区间标记。

如果某一轮中 dp_{i,j,t}\text{true},我们就对第 j 个数字序列在区间 [t+1, \min(t+k-1, l_j-1)] 进行加 1 标记。这一操作表示能以 [t+1, \min(t+k-1, l_j-1)] 区间的数字作为这一轮接龙的结尾。

然后对 num 每个位置进行前缀和处理,这样只要前缀和后数组 num 位置 t 的值大于 0,就知道 f_{i,j,t} 的值应该为 \text{true}

代码+注释

#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll N=200010,M=110;
ll t,n,m,k,len[N],num[N],l,r;
vector<ll> a[N];
vector<bool> dp[N],f[N];
bool vis[N],ans[M][N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>t;
    while(t--)
    {
        cin>>n>>m>>k;
        for(int i=1;i<=n;i++)
        {
            cin>>len[i];
            a[i].resize(len[i]),dp[i].resize(len[i]),f[i].resize(len[i]);
            for(int j=0;j<len[i];j++)
            {
                cin>>a[i][j];
                if(a[i][j]==1)
                {
                    dp[i][j]=true;
                }
                else
                {
                    dp[i][j]=false;
                }
            }
        }
        memset(ans,false,sizeof(ans));
        for(int i=1;i<=100;i++)
        {
            for(int j=1;j<=n;j++)
            {
                for(int l=0;l<len[j];l++)
                {
                    num[l]=0;
                }
                for(int l=0;l<len[j];l++)
                {
                    if(dp[j][l])
                    {
                        num[l+1]++;
                        if(len[j]>l+m)
                        {
                            num[l+m]--; 
                        }
                    }
                }
                for(int l=0;l<len[j];l++)
                {
                    if(l>0)
                    {
                        num[l]+=num[l-1];
                    }
                    if(num[l]>0)
                    {
                        f[j][l]=true;
                    }
                    else
                    {
                        f[j][l]=false;
                    }
                    ans[i][a[j][l]]|=f[j][l];
                }
            }
            memset(vis,false,sizeof(vis));
            for(int j=1;j<=n;j++)
            {
                for(int l=0;l<len[j];l++)
                {
                    dp[j][l]=vis[a[j][l]];
                }
                for(int l=0;l<len[j];l++)
                {
                    if(f[j][l])
                    {
                        vis[a[j][l]]=true;
                    }
                }
            }
            memset(vis,false,sizeof(vis));
            for(int j=n;j>=1;j--)
            {
                for(int l=0;l<len[j];l++)
                {
                    dp[j][l]=dp[j][l]||vis[a[j][l]];
                }
                for(int l=0;l<len[j];l++)
                {
                    if(f[j][l])
                    {
                        vis[a[j][l]]=true;
                    }
                }
            }
        }
        while(k--)
        {
            cin>>l>>r;
            cout<<ans[l][r]<<"\n";
        }
    }
    return 0;
}

100pts 记录