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;
}

浙公网安备 33010602011771号