题目链接:
http://codeforces.com/gym/101194/attachments
题意:
一个长度为 $N$ 的序列,要求选出两段不重叠的区间,要求两个区间包含的元素均互不相同,求两段区间的长度和最大为多少。
题解:
(主要参考https://blog.csdn.net/a1214034447/article/details/78768645)
首先用 $back[i]$ 和 $front[i]$ 分别表示 $i$ 这个位置的数从i往右看第一次出现的位置,和往左看第一次出现的位置。
接着,我们从位置 $n$ 开始倒退回去枚举右区间的左端点 $r$,用 $rlen = back[r] - r$ 表示当前位置往右延伸的最长长度,并且用一个set保存目前右区间 $[r,back[r])$ 里所有的数。
然后,对于每个 $r$,均枚举左区间的右端点 $l(l<r)$,用 $llen = l - front[l]$ 表示目前该位置往左延伸的最长长度。
同时,再用一个set维护:找到左区间里,所有在右区间出现过的数,存储这些数的下标。
接下来依次枚举这些数,考虑这些数若不让其在右区间取到(当然,还有一种情况是都在右区间取),那么可以算出此时相应左区间最长能有多长。将左右区间长度求和,维护最大值。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+;
const int maxc=1e5+; int n,m;
int num[maxn],pos[maxc];
int bak[maxn],frt[maxn];
set<int> st,se; inline int maxllen(int p,int l,int llen)
{
auto it=se.end();
while((it--)!=se.begin())
if(pos[num[*it]]<p) return l-(*it);
return llen;
} int main()
{
int T;
cin>>T;
for(int kase=;kase<=T;kase++)
{
scanf("%d",&n);
for(int i=;i<=n;i++) scanf("%d",&num[i]); memset(pos,,sizeof(pos));
for(int i=;i<=n;i++)
{
frt[i]=pos[num[i]];
pos[num[i]]=i;
}
memset(pos,0x3f3f3f3f,sizeof(pos));
for(int i=n;i>=;i--)
{
bak[i]=pos[num[i]];
pos[num[i]]=i;
} int ans=;
st.clear();
for(int i=n,rlen=;i>=;i--,rlen++)
{
while(bak[i] <= i+rlen-)
{
st.erase(num[i+rlen-]);
rlen--;
}
st.insert(num[i]), pos[num[i]]=i;
se.clear();
for(int j=,llen=;j<i;j++,llen++)
{
while(frt[j] >= j-llen+)
{
se.erase(j-llen+);
llen--;
}
if(st.count(num[j])) se.insert(j);
if(!se.size()) ans=max(ans,llen+rlen);
else ans=max(ans,j-*(--se.end())+rlen);
for(auto it=se.begin();it!=se.end();it++)
ans=max(ans,maxllen(pos[num[*it]],j,llen)+pos[num[*it]]-i);
}
}
printf("Case #%d: %d\n",kase,ans);
}
}