Luogu P11230 CSP-J 2024 接龙 题解 [ 线性 dp ] [ 前缀和 ]

接龙:一个前缀和优化 dp 或者单调队列优化 dp 的题目。

怎么周围的人都秒了 T3 不会 T4 啊,只有我觉得 T4 很套路,T3 比较难写吗。

暴力 dp

为了避免多维的状态定义,我们把每个人的子序列化为长度最多为 2×105 的一维序列,并记录下每一张牌对应的人是谁。记这个一维序列的长度为 tot,显然 tot=i=1nli

我们定义状态 dpi,j 表示当前进行到第 i 轮,且该轮以一维序列中的第 j 张牌结尾是否可行。

有一个显然的转移,我们遍历第 j 张牌的前面 k1 张牌(必须要是同一个人的),假设某张前面的牌所写的数字为 x,那么我们可以遍历 r1 层中不是同一个人的且值为 x 的人的牌转移过来,只要这些牌中有一个的结果为 1,那么第 j 张牌的值就是 1

时间复杂度 O(rn2k)

第一步优化

注意到我们的限制有一个是“不能从同一个人那里转移过来”,考虑一个和前几年提高组假期计划那题很像的想法。

原题中是记录前 3 大的值,目的是避免走到重复的景点。而这题我们可以借鉴它的思路,记录两个属于不同人的且可以转移过来牌。只要这两张牌中,有一张和我现在的牌所属的人不同,就是可以转移过来的。

这个可以通过在每一轮结束之后预处理一遍得到。

时间复杂度 O(rnk)

第二步优化

到这里我们就快做完这题了。观察到一个数的结果为 1,当且仅当前 k1 位的预处理值中有一个可以转移。熟悉 dp 的人肯定能一下就想到单调队列优化,进行转移,时间复杂度就被降到了 O(rn)

但这是普及组,不能考单调队列。于是我们换一种普及组思路。因为这题可以预处理,而查询操作是查询 lr 之间有没有元素 1,因此我们预处理之后前缀和一下就能做到 O(1) 查询有没有元素 1 了。

时间复杂度 O(rn)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int t,n,k,q,f[105][200005][2],a[200005],bl[200005],tot,can[200005];
bitset<200005>dp[105];
void init()
{
    memset(f,-1,sizeof(f));
    for(int r=1;r<=100;r++)
    {
        //count can
        if(r==1)
        {
            for(int i=1;i<=tot;i++)can[i]=can[i-1]+(a[i]==1);
        }
        else
        {
            for(int i=1;i<=tot;i++)can[i]=can[i-1]+((f[r-1][a[i]][0]!=-1&&f[r-1][a[i]][0]!=bl[i])||(f[r-1][a[i]][1]!=-1&&f[r-1][a[i]][1]!=bl[i]));
        }
        //count dp
        int pre=0;
        for(int i=1;i<=tot;i++)
        {
            if(bl[i]!=bl[i-1])pre=i;
            int rn=i-1;
            int ln=max(pre,i-k+1);
            dp[r][i]=((can[rn]-can[ln-1])>0);
        }
        //count f
        for(int i=1;i<=tot;i++)
        {
            if(dp[r][i]==0)continue;
            if(f[r][a[i]][0]==-1)f[r][a[i]][0]=bl[i];
            else if(f[r][a[i]][1]==-1&&f[r][a[i]][0]!=bl[i])f[r][a[i]][1]=bl[i];
        }
        // for(int i=1;i<=tot;i++)
        // {
        //     cout<<"can["<<r<<"]["<<i<<"]="<<can[i]<<endl;
        // }
        // for(int i=1;i<=tot;i++)
        // {
        //     cout<<"f["<<r<<"]["<<i<<"][0]="<<f[r][i][0]<<endl;
        //     cout<<"f["<<r<<"]["<<i<<"][0]="<<f[r][i][1]<<endl;
        // }
    }
}
void solve()
{
    scanf("%d%d%d",&n,&k,&q);
    tot=0;
    for(int i=1;i<=n;i++)
    {
        int l;
        scanf("%d",&l);
        for(int j=1;j<=l;j++)
        {
            tot++;
            scanf("%d",&a[tot]);
            bl[tot]=i;
        }
    }
    init();
    while(q--)
    {
        int r,c;
        scanf("%d%d",&r,&c);
        printf("%d\n",(f[r][c][0]!=-1||f[r][c][1]!=-1));
    }
}
int main()
{
    freopen("chain.in","r",stdin);
    freopen("chain.out","w",stdout);
    scanf("%d",&t);
    while(t--)solve();
    return 0;
}
posted @   KS_Fszha  阅读(453)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示