猜你想要:更好的阅读体验。
给出一个有 $n$($1 \le n \le 5 \times 10^5$)个整数的序列 $A$,我们可以对他进行任意次将 $A_i$ 变成 $-A_i$ 的操作,求最少的区间 $[l,r]$ 满足 $A_l=A_r$ 且 $A_l$(或者说 $A_r$)是区间 $[l,r]$ 中最大的。
读完题后,暂时没有什么思路。看到数据范围,突然发现:Subtask #4 中 $|A_i|$ 均相等!这不妥妥的给我们骗分用的。
假设现有一个满足 $|A_i|$ 均相等的序列 $A$。
如果 $A_i$ 均为正数(或负数),那么具有”威胁“的区间的数量是 $\sum_{i = 1}^{n} i$(或者说是 $\frac{n(n+1)}{2}$)。
那么我们可以很容易地想到对于 $A$ 来说,想要得到最少具有"威胁“的区间,我们需要让 $\frac{n}{2}$ 个数字变成正数,让剩下的数变成负数。
假设 $k=|A_i|$。
将上面得出的”威胁“总数放在序列 $F$ 中,得 ${0,0,1,2,4,6,9,12}$。把每两项之间的差值求出,得 ${0,1,1,2,2,3,3}$,规律这不就来了吗。根据规律我们可以列出递推式:$F_i=F_{i-1}+\frac{i-1}{2}$。
#include<bits/stdc++.h>
using namespace std;
const int N=500860;
int n,t; //t表示 (i-1)/2 的值
long long f[N];
int main(){
cin>>n;
for(int i=3;i<=n;i++){
if(i&1) t++; //当i为奇数(也就是 (i-1)/2 的值会增加时)t++
f[i]=f[i-1]+t; //上面推导的 f[i]=f[i-1]+(i-1)/2
}
cout<<f[n];
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=500860;
int n,t,a[N];
long long ans,f[N];
bool v[N]; //v用来标记已用过的 a[i] 防止重复计算。
int main(){
cin>>n;
for(int i=3;i<=n;i++){
if(i&1) t++;
f[i]=f[i-1]+t;
}
for(int i=1;i<=n;i++){
scanf("%d",a+i);
a[i]=abs(a[i]); //如上面第 3 点所说。
}
for(int i=1;i<=n;i++){
if(!v[i]){
int cnt=1;
for(int j=i+1;j<=n;j++){
if(a[j]>a[i]) break; //如上面第 2 点所说。
if(a[i]==a[j]) cnt++,v[j]=1;
}
ans+=f[cnt];
}
}
cout<<ans;
return 0;
}
本来写完上面未优化的正解 (我比赛时不知到这是正解) 正准备关闭 luogu,打开 steam,享受美好人生的。但看到这个记录详情只 TLE 了两个点,并且还分别是最后两组时,我心中突然涌现出一股力量,这股力量告诉我,这道题我可以 AC!
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=500860;
unordered_map<int,vector<int> > m; //m[a[i]] 用来记录与 a[i] 相同的下一个数字的位置。
unordered_map<int,int> l; //记录 m[a[i]] 用到哪了,与之前 v 的作用相似。
//不开 unordered_map 会炸(MLE 或 TLE)。
int n,t,a[N],s[N][20]; //log2(500000)大约是19。
LL ans,f[N];
inline int qy(int l,int r){ //查询区间 [l,r] 的最大值。
int t=log2(r-l+1);
return max(s[l][t],s[r-(1<<t)+1][t]);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",a+i);
a[i]=abs(a[i]);
m[a[i]].push_back(i); //记录 m[a[i]] 的位置。
}
for(int j=0;j<20;j++) //st 表预处理。
for(int i=1;i+(1<<j)-1<=n;i++)
if(!j) s[i][j]=a[i];
else s[i][j]=max(s[i][j-1],s[i+(1<<j-1)][j-1]);
for(int i=3;i<=n;i++){
if(i&1) t++;
f[i]=f[i-1]+t;
}
for(int i=1;i<=n;i++){
int cnt=0,j=l[a[i]]; //j 记录 m[a[i]] 用到哪了。
for(;j<m[a[i]].size();j++){
if(qy(i,m[a[i]][j])>a[i]) break; //判断 a[i] 是否是最大的。
else cnt++;
}
l[a[i]]=j; //记录 m[a[i]] 用到的位置。
ans+=f[cnt];
}
cout<<ans;
return 0;
}
AC 喽ヾ(≧▽≦*)o,完结撒花。
第二天起来一看,这道题进题库了,真的太好了,打比赛还送 AC 率。一交上去,WA 了两个点,仅仅 90pts?一看题目,改了一个设定,即规定了 $0=-0$(昨天规定 $0\ne-0$),又被背刺了啊(っ °Д °;)っ。
因为 $0=-0$,所以以 $0$ 为左右端点的区间中具有”威胁“的区间的数量是 $\sum_{i = 1}^{n} i$(或者说是 $\frac{n(n+1)}{2}$),其递推式为 $F2_i=F2_{i-1}+i-1$。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=500860;
unordered_map<int,vector<int> > m;
unordered_map<int,int> l;
int n,t,a[N],s[N][20];
LL ans,f[N],f2[N]; //f2 专门用来处理 0 的情况。
inline int qy(int l,int r){
int t=log2(r-l+1);
return max(s[l][t],s[r-(1<<t)+1][t]);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",a+i);
a[i]=abs(a[i]);
m[a[i]].push_back(i);
}
for(int i=3;i<=n;i++){
if(i&1) t++;
f[i]=f[i-1]+t;
}
for(int i=2;i<=n;i++)
f2[i]=f2[i-1]+i-1; //如题推导的左右端点为 0 的情况。
for(int j=0;j<20;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
if(!j) s[i][j]=a[i];
else s[i][j]=max(s[i][j-1],s[i+(1<<j-1)][j-1]);
for(int i=1;i<=n;i++){
int cnt=0,j=l[a[i]];
for(;j<m[a[i]].size();j++){
if(qy(i,m[a[i]][j])>a[i]) break;
else cnt++;
}
l[a[i]]=j;
if(!a[i]) ans+=f2[cnt]; //特殊情况特殊对待ψ(`∇´)ψ。
else ans+=f[cnt];
}
cout<<ans;
return 0;
}
后序:题解有界,帮助无限,希望这篇题解可以帮助屏幕前的你ヾ(≧▽≦*)o。