题解:P12391 「RiOI-6」帝国少女
P12391 「RiOI-6」帝国少女 题解
思路产生
首先显然
我们先考虑 m>2 的情况:
有一个贪心策略就是对于任意一段相同的数的连续段,都把在这个连续段中的偶数位的数变成一个不同于其左右相邻的数。
比如 2 1 1 1 1 1 1 2 就要变成 2 1 2 1 2 1 3 2。
这个贪心策略的正确性是显然的,因为对于任意一对相邻且相同的数,都要选择一个选择其中一个,给成另一个数字,因为我们的可以选择换成的数字至少有
有了贪心策略,我们就要想怎么才能在时间复杂度限制内求出答案。
我们考虑每一个数对会对多少个子段产生贡献。我们求出来每一个子段的左端点的有多少种取法,右端点有多少种取法,这样根据乘法原理,我们就能求出来能让点对做出贡献的子段数。
对于右端点,只要大于等于点对的右端点是都能取的。
对于左端点,在小于点对左端点的同时,还要满足让点对的右端点成为一段连续相同的数的连续段的偶数位。
这些都是可求的,根据连续段长度奇偶分讨一下就好了。
我们接下来考虑 m=2 的情况
感觉
这样我们又有了一个贪心策略:对于一个子区间,
那么我们就把题转化成了求:
这里
我们前缀和分别维护出区间
考虑优化。设子区间有
而我们要求的所有子区间最小值之和可转化为:
其中
对于前者我们发现长度为
那么前者其实就是:
那么下面就是求后者了。
我们再进行进一步的转化:
将
我们考虑拆绝对值。
对于每个
拆分为两部分:
我们可以开两个权值树状数组分别记录次数和总和,由于前缀和数组可能有负数,所以不要忘了离散化一下。
然后在枚举
最后根据公式求一下就好了,最后时间复杂度是
附上代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int a[N],s[N],b[N];
int lowbit(int x){
return x&(-x);
}
struct Tree{
int n;
vector<int> tr;
Tree(int n):n(n),tr(n+2){}
void update(int x,int c){
for (;x<=n;x+=lowbit(x)) tr[x]+=c;
}
int query(int x){
int res=0;
for (;x;x-=lowbit(x)) res+=tr[x];
return res;
}
};
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
if(m>2){
int ans=0;
int nw=0,len=0;
for(int i=1;i<n;i++){
if(a[i]==nw){
len++;
}else{
nw=a[i];
len=1;
}
if(a[i]==a[i+1]){
if(len%2==0){
ans+=(len/2)*(n-i);
}else{
ans+=(len/2+(i-len)+1)*(n-i);
}
}
}
cout<<ans<<"\n";
}else{
for(int i=1;i<=n;i++){
if(a[i]==2)a[i]=0;
}
for(int i=2;i<=n;i+=2){
a[i]^=1;
}
for (int i=1;i<=n;i++) a[i]=(a[i]==0?1:-1);
for(int i=1;i<=n;i++){
s[i]=a[i]+s[i-1];
b[i]=s[i];
}
sort(b+1,b+n+1);
int tot=unique(b+1,b+n+1)-b-1;
Tree cnt(tot),sum(tot);
int ans=0;
for (int i=0;i<=n;i++){
int x=s[i];
int k=lower_bound(b+1,b+tot+1,x)-b;
int cl=cnt.query(k-1),sl=sum.query(k-1);
int ct=cnt.query(tot),st=sum.query(tot);
int cs=ct-cl,ss=st-sl;
ans+=x*cl-sl+ss-x*cs;
cnt.update(k,1);
sum.update(k,x);
}
cout<<(n*(n+1)*(n+2)/6-ans)/2;
}
}