CF1819D-Misha and Apples【dp】
正题
题目链接:https://www.luogu.com.cn/problem/CF1819D
题目大意
你有 \(n\) 个集合 \(S_i\) ,还有一个空的可重集 \(S\) 。
你会按顺序将 \(S_i\) 加入集合 \(S\) 中,如果某次加入后 \(S\) 中有重复元素则清空 \(S\) 。
有部分 \(S_i\) 你可以随机设置里面的元素,要求最后使得 \(S\) 中元素个数最多。
\(1\leq T,\sum n,m\leq 2\times 10^5,0\leq \sum k_i\leq 2\times 10^5\)
解题思路
考虑最后一次清空,那么在这次之后肯定没有重复元素且如果有可以设置的集合那么答案就直接为 \(n\) 了。
我们可以通过从后往前做求出最后在某个位置清空时的答案。
然后考虑能否在某些位置清空,设 \(f_i\) 表示能否在第 \(i\) 次进行清空。
考虑对于 \(f_i\) 能够从哪里转移过来,首先如果 \(S_i\) 可以随机设置那么此时肯定可以清空,如果 \(S_i\) 固定,那么对于 \(S_i\) 中的每个元素 \(x\) 我们就看前面能否恰好含有一个 \(x\) 。
首先对于往前数第二个个 \(x\) 的位置以及再往前都是肯定不行的,而对于往前数第一个 \(x\) 往后都是可以的,当然往前数第一个可以设置的集合也是可以的。这样我们就可以维护出一个可以转移的区间,然后用前缀和即可。
时间复杂度:\(O(n)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=2e5+10;
int T,n,m,f[N],s[N],las[N];
vector<int> v[N];
bool sum(int l,int r){
if(r<0)return 0;
if(l>r)return 0;
if(l==0)return s[r]>0;
return (s[r]-s[l-1])>0;
}
int main()
{
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1,k;i<=n;i++){
scanf("%d",&k);
for(int j=1,x;j<=k;j++)
scanf("%d",&x),v[i].push_back(x);
}
int las0=0,lim=0;f[0]=s[0]=1;
for(int i=1;i<=n;i++){
if(v[i].empty()){
f[i]=1;
las0=i;
}
else{
int rim=las0-1;
for(int j=0;j<v[i].size();j++)
rim=max(rim,las[v[i][j]]-1);
f[i]=sum(lim,rim);
for(int j=0;j<v[i].size();j++){
lim=max(lim,las[v[i][j]]);
las[v[i][j]]=i;
}
}
s[i]=s[i-1]+f[i];
}
for(int i=0;i<=n;i++)
for(int j=0;j<v[i].size();j++)
las[v[i][j]]=0;
int sum=0,ans=0;
for(int i=n;i>=1;i--){
bool flag=0;
for(int j=0;j<v[i].size();j++){
if(las[v[i][j]])flag=1;
las[v[i][j]]=1;
}
if(flag)break;
if(sum<m){
if(v[i].size())sum+=v[i].size();
else sum=m;
}
if(f[i-1])ans=max(ans,sum);
}
printf("%d\n",ans);
for(int i=0;i<=n;i++){
for(int j=0;j<v[i].size();j++)
las[v[i][j]]=0;
v[i].clear(),f[i]=s[i]=0;
}
}
return 0;
}