1.第一题:Problem - B1 - Codeforces:Painting the Array I
题意:输入一个长为n(1 <= n <= 1e5) 的序列ai,你要维护两个栈 B 和 C,初始都为空。 现在顺次扫描序列 ,对于每个ai:有两个操作:
1. 要么将其压入 B 中
2. 要么将其压入 C 中
问: seg(B)+seg(C)的最小值是什么? 其中 seg(D)表示把序列 D 中相邻 且 相同的项合并后所剩的元素个数,比如 seg{1,1,2,3,4}=seg{1,2,3,4}=4.
我们肯定是希望每个数放进栈时,栈顶都会有一个相同的元素,也就是当每个数选择要放入的栈时,最优先选择有相同元素的栈顶的栈放入,我们能证明每次都这么做是最优的。 (证明:若最优解不是这样做,考虑除了我们正在进行的操作,前面全部操作与最优解一致的策略:最优解选择了放入无相同元素的一个,我们选择放入有相同元素的一个,以后我们的每次操作与最优解一致,最优解最多能凭借该操作消去一个,而我们肯定能消去一个。)
接下来考虑其他的情况:如果栈顶没有相同的元素怎么办?可以随便放吗,应该不可以,为什么?想了一下:我们每次放时,肯定希望被盖住的栈顶元素在以后的序列中不再出现,如果能够做到这点,我们这种策略肯定有效。到此我们发现了一种思路:每次放入元素时,考虑两个栈的栈顶元素在后面序列中的出现情况。
这是因为,对于 两个栈顶元素都与要放元素不相同 的情况,无论放入哪个栈,在本次情况下总和都会加一,只有对未来贡献最好的放法,才是最优的。而放入每个栈的不同在于:遮住了哪个未来 最难出现 的元素,留住了哪个未来 最易出现 的元素。接下来我们就要考虑,怎么说明,评定与计算 最易出现(或最难出现)?
很容易想到与出现次数联系在一起。不过我们要稍微严谨一些,此时再想想,比较每个数的出现次数可以吗?如果不可以,为什么不可以,能构造出反例吗?如果可以,怎么证明?
我思考了一下,感觉还是有点复杂。原因是对于后面直到栈B栈顶元素(设为b)和栈C栈顶元素(c)之前的元素该放哪还是未知的:会不会把原本多的栈顶遮住,还是一直选择少的栈顶?
既然如此,我们改善一下情况(先用已知的东西来代替未知的东西,考虑最简单的情况):设我们现在要放的元素为a,B栈顶b,C栈顶c,设后面出现的次数b要多于c,考虑后面的序列为 c , m1 , c , m2, c , m3 , b, b, b ,b. (m1 , m2 , m3互不相同)
有图:
可以发现,当m1,m2,m3,...的数量逐渐增大时,这种方法会逐渐失去它的优越性。也就是说,这种方法是行不通的。
为什么呢?思考过后得出答案:因为后面的序列会采用同样的方式,其中本可以消去的相同元素却没有消去,而换来的代价只是让b多消去了一个。此时在观察最优解,在这种情况下的最优解应该是把a放到B栈中接下来把所有的c放入C栈,m1,m2,m3放入B栈,四个b放哪里都行。
我们可以看到,在最优解中,考虑了局部最优的方法,不是让最多的b放一起,而是让最先出现的c放在了一起。我们或许可以得到启发。
我们接下来考虑比较栈顶元素在以后序列出现时间的先后。我们直接来看一下流程:对于放B栈的情况:(a,c)表示B栈放a,C栈放c。即(a,c)与(b,a)的比较。在b,c第一次出现之前,两者无差别。对于在b,c出现后的序列,两者也没有区别(因为后面再也没有b,c出现,对于b,c都无法消去)。我们只需要分析b,c出现的序列即可。我们假设b比c先出现,如果b,c都能消去,那么对于两种情况都需要从a到b(或c)第一次出现的序列全部放入C(或B)栈,由于b所在位置更靠前,因此b元素更易消去,并且b消去后对于后面的序列可自由放置。
对于两个元素都不相同的情况讨论完毕了,接下来讨论两个元素都不相同的情况,思考一下可以得到解决方案:随便放。因为无论放入哪个栈得到的栈顶元素是相同的,不会因为未来的序列放置情况的不同而不同。
总结一下:两个元素相同时:随便放。
两个元素不同时:(1)若有一个栈顶与当前元素相同:放入相同的
(2)若没有一个元素与当前元素相同:放入优先出现的
接下来是代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N],b[N],k[N],at=-1,bt=-1,ai=0,bi=0;
int fr[N],ne[N];
int main(){
int n;
scanf("%d",&n);
memset(fr,-1,sizeof fr);
for(int i=1;i<=n;i++){
scanf("%d",&k[i]);
if(fr[k[i]]==-1) fr[k[i]]=i,ne[i]=n+1;
else{
ne[fr[k[i]]]=i;
ne[i]=n+1;
fr[k[i]]=i;
}
}
// for(int i=1;i<=n;i++)
// printf("%d:%d\n",i,ne[i]);
for(int i=1;i<=n;i++){
// printf("%d--%d\n",ai,bi);
int t=k[i];
if(at==-1 || bt==-1){
if(at==-1){
a[++at]=t;
ai=i;
continue;
}
if(t==a[at]){
ai=i;
continue;
}
if(bt==-1 && t!=a[at]){
b[++bt]=t;
bi=i;
continue;
}
}
if(a[at]==b[bt]){
if(t!=a[at]) a[++at]=t;
ai=i;
continue;
}
if(a[at]!=b[bt]){
if(a[at]==t){
ai=i;
continue;
}
if(b[bt]==t){
bi=i;
continue;
}
if(ne[ai]>ne[bi]){
a[++at]=t;
ai=i;
continue;
}
else{
b[++bt]=t;
bi=i;
continue;
}
}
}
// for(int i=0;i<=at;i++)
// printf("%d ",a[i]);
// printf("\n");
// for(int i=0;i<=bt;i++)
// printf("%d ",b[i]);
// printf("\n");
printf("%d",at+bt+2);
return 0;
}