CF1416E-Split【dp,set】
正题
题目链接:https://www.luogu.com.cn/problem/CF1416E
题目大意
给出\(n\)个正整数的一个序列\(a_i\),你要把\(a_i\)拆成两个正整数的和\(b_{2i},b_{2i+1}\),要求使得\(b\)的相同连续段最少。
\(1\leq n\leq 5\times 10^5,1\leq a_i\leq 10^9\)
解题思路
考虑求最大的相邻相同数目,先考虑暴力的\(dp\),设\(f_{i,j}\)表示分解完\(a_i\)且\(b_{2i+1}=j\)时的方案,那么有转移方程
而且不难发现对于一个\(i\)来说它的所有\(f_{i,j}\)在加上\([2j=a_i]\)之前差距不会超过\(1\),而且我们显然只有可能从最大值转移。
对于\(2j=a_i\)的情况很难处理,我们可以先考虑都是奇数的情况。
首先开始都有\(f_{1,j}=0\),可以记为区间\([1,a_{1}-1]\),然后到第二个对于一个最大的\(j\),我们可以转移到\(a_2-j\)(如果合法)。同样的我们可以翻转之后得到一个新的最大区间\([l,r]\),当某次之后这个区间空了那么因为上面提到的\(f_{i,j}\)的差距不会超过\(1\),所以最大值不变然后区间变回\([1,a_{i}-1]\)。
之后考虑\(a_i\)有偶数的情况怎么处理,此时会出现的问题就是:如果\(\frac{a_i}{2}\)加之前是最大值,那么加上之后就变为了唯一的最大值,这个很好处理,而如果之前不是最大值,那么加了之后就变为了最大值。
这个时候有可能会在区间之外出现一些单点的最大值,我们可以用\(set\)来储存这些位置,至于翻转之后所有的位置\(x\)都会变为\(a_i-x\),那么可以储存一个\(x\)表示实际上这个位置的值为\(x\times f+buf\)的情况,这样我们就可以快速的翻转然后把不合法的值去掉就好了。
时间复杂度:\(O(n\log n)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define ll long long
using namespace std;
const ll N=5e5+10;
ll T,n,ans,l,r,flag,f,buf,a[N];
set<ll> s;
void solve(ll lim){
flag=1;
if(l<=r){
if(lim<=l)l=1,r=0;
else l=lim-l,r=lim-min(r,lim-1),swap(l,r),flag=0;
}
f=f*-1;buf=lim-buf;
while(!s.empty()){
ll w=(*s.begin())*f+buf;
if(w<1||w>=lim)s.erase(s.begin());
else break;
}
while(!s.empty()){
ll w=(*(--s.end()))*f+buf;
if(w<1||w>=lim)s.erase(--s.end());
else break;
}
return;
}
signed main()
{
scanf("%lld",&T);
while(T--){
s.clear();f=ans=1;buf=flag=0;
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
scanf("%lld",&a[i]);
if(a[1]&1)l=1,r=a[1]-1,ans++;
else l=r=a[1]/2;
for(ll i=2;i<=n;i++){
// if(s.size())printf("%d\n",*s.begin());
if(a[i]&1){
solve(a[i]);ans++;
if(s.empty()&&flag)l=1,r=a[i]-1,ans++;
}
else{
if(s.find((a[i]/2-buf)*f)!=s.end()||a[i]/2>=l&&a[i]/2<=r)
s.clear(),l=r=a[i]/2;
else solve(a[i]),s.insert((a[i]/2-buf)*f),ans++;
}
}
printf("%lld\n",ans);
}
return 0;
}