Luogu P11013 「ALFR Round 4」C 粉碎 题解 [ 蓝 ] [ 前缀和优化 DP ] [ 指针优化 ]

粉碎

旧题新写。

结论 \(1\):对于两张能同时存在的花色相同的牌 \(i, j(i < j)\),若不存在 \(i < k <j\) 使得 \(a_i = a_k = a_j\),则一定能将 \(1\sim j\) 全部删掉。

证明是简单的,如果 \(i, j\) 能同时存在(即 \(i\) 不会先被删掉或者与前面的同色牌匹配),那么我直接把 \(i\) 放在最左边,\(i + 1\sim j - 1\) 的牌全部插到右边,最后再把 \(j\) 插到右边,整个双端队列就直接被清空了。

可以考虑对能否删去某个前缀定义 DP。然后发现如果状态直接设 \(dp_i\)\(1\sim i\) 能否清空是不好转移的(可以做,但是细节比较多),而这种状态下我们转移的过程中看重的就是 \(\bm{pre_{a_i}}\) 能否单独存在,因为只有这样 \(i\) 才可以发生匹配。于是转换思路,定义 \(\bm{dp_i}\) 表示考虑 \(\bm{1\sim i}\) 的前缀,\(\bm{i}\) 能否作为值为 \(\bm{a_i}\) 的最后一个元素存在。而这种状态定义就要求需要把 \(pre_{a_i}\) 删掉,有如下两种转移方式:

  • \(pre_{a_i}\) 与前面的同色牌匹配了。
  • \(pre_{a_i} + 1\sim i - 1\) 有牌发生了匹配。

容易发现这等价于在问 \(pre_{a_i} \sim i - 1\) 之间是否存在把队列清空的牌。于是我们用一个指针记录上一个能清空队列的牌的位置即可,这个用前缀和优化其实也是可以做的。

时间复杂度 \(O(n)\),柚子题还是太妙了。

#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, a[N], lst[N], ans = 0;
bitset<N> dp;
void solve()
{
    cin >> n;
    ans = 0;
    memset(lst, 0, sizeof(lst));
    dp.reset();
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        int ne = lst[a[i]];
        lst[a[i]] = i;
        dp[i] = (ans >= ne);
        if(dp[ne]) ans = i;
    }
    cout << ans << '\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}
posted @ 2025-11-18 20:49  KS_Fszha  阅读(14)  评论(0)    收藏  举报