P4062 [Code+#1]Yazid 的新生舞会
杭电多校懂得都懂
Code1
分治
比较喜欢分治的做法,非常好写。skylee大佬题解
首先对于任何一个区间来说,由于两个端点不确定性非常难以一次性统计多组区间,因为它们没有相似之处。
考虑分治,花费 log \log log的代价使得当前考虑的区间必须经过 mid \text{mid} mid,意味着当前区间左端点必须在 [ l , mid ] [\text{l},\text{mid}] [l,mid]区间内部,而右端点必须在 [ mid + 1 , r ] [\text{mid}+1,\text{r}] [mid+1,r]区间内部,这样的区间特殊在一定经过mid使得可以先预处理左端点的一些信息,然后枚举右端点一次性统计多个区间。
首先枚举可能作为区间绝对众数的数 v \text v v,然后考虑哪些上述区间能使得该数作为区间的绝对众数。
vl
,
vr
\text{vl},\text{vr}
vl,vr分别是可能使
v
\text v
v作为区间
[
vl
,
vr
]
[\text{vl},\text{vr}]
[vl,vr]的绝对众数。
cnt
vl
\text{cnt}_{\text {vl}}
cntvl:
vl
→
mid
\text{vl}\to \text{mid}
vl→mid 数字
v
\text v
v出现的次数。
cnt
vr
\text{cnt}_{\text {vr}}
cntvr:
mid
+
1
→
vr
\text{mid}+1\to \text {vr}
mid+1→vr 数字
v
\text v
v出现的次数。
cnt
vr
+
cnt
vl
>
1
2
[
r
−
l
+
1
]
\text{cnt}_{\text {vr}}+\text{cnt}_{\text {vl}}>\frac{1}{2}[r-l+1]
cntvr+cntvl>21[r−l+1]
从上面式子可以得出
2
cnt
vl
+
l
−
1
>
r
−
2
cnt
vr
2\text{cnt}_{\text {vl}}+l-1>r-2\text{cnt}_{\text {vr}}
2cntvl+l−1>r−2cntvr
考虑枚举 vr \text{vr} vr,我们只需要统计有哪些左端点 vl \text{vl} vl满足上面式子即可,显然可以预处理+前缀和一次性统计。
如果一个数 v v v是区间 [ vl , vr ] [\text{vl},\text{vr}] [vl,vr]的绝对众数,那么它一定是区间 [ vl , mid ] [\text{vl},\text{mid}] [vl,mid]以及 [ mid + 1 , vr ] [\text{mid}+1,\text{vr}] [mid+1,vr]的绝对众数,于是可以提前预处理。
显然能够满足上述条件作为区间绝对众数的种类不会很多。上面大佬题解说是 log \log log量级的。
时间复杂度 O ( n log 2 n ) O(n\log ^2n) O(nlog2n)
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
T res=0;T fg=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
return res*fg;
}
const int N=500010;
int a[N],n;
ll ans;
int b[N],cnt;
int mp[N],in[N];
int num[2*N];
void solve(int l,int r)
{
if(l==r) return ans++,void();
int mid=l+r>>1;
solve(l,mid),solve(mid+1,r);
cnt=0;
for(int i=mid;i>=l;i--)
{
mp[a[i]]++;
if(mp[a[i]]>(mid-i+1)/2)
{
if(in[a[i]]) continue;
in[a[i]]=1;
b[++cnt]=a[i];
}
}
for(int i=l;i<=mid;i++) mp[a[i]]--;
for(int i=mid+1;i<=r;i++)
{
mp[a[i]]++;
if(mp[a[i]]>(i-mid)/2)
{
if(in[a[i]]) continue;
in[a[i]]=1;
b[++cnt]=a[i];
}
}
for(int i=mid+1;i<=r;i++) mp[a[i]]--;
for(int i=l;i<=r;i++) in[a[i]]=0;
//for(int i=1;i<=cnt;i++) cout<<b[i]<<" \n"[i==cnt];
for(int i=1;i<=cnt;i++)
{
int cur=0;
int L=3*n,R=0;
for(int j=mid;j>=l;j--)
{
if(a[j]==b[i]) cur++;
num[2*cur+j-1]++;
L=min(L,2*cur+j-1);
R=max(R,2*cur+j-1);
}
for(int i=R;i>L;i--) num[i-1]+=num[i];
cur=0;
for(int j=mid+1;j<=r;j++)
{
if(a[j]==b[i]) cur++;
ans+=(num[max(L,j-2*cur+1)]);
}
for(int i=R;i>=L;i--) num[i]=0;
}
}
int main()
{
n=rd();rd();
for(int i=1;i<=n;i++) a[i]=rd();
solve(1,n);
printf("%lld\n",ans);
return 0;
}
Code2
其实赛时的将此问题转化成了下面的问题:如何求小于一段公差为1的等差数列的个数?
- 求在 a 1 → n a_{1\to n} a1→n中求小于 x x x的个数,小于 x + 1 x+1 x+1的个数,小于 x + 2 x+2 x+2的个数,小于 x + k x+k x+k的个数把他们累加。
然后就死了???这不就是前缀和然后区间询问吗?wtcl
开个桶,然后做一个前缀和,然后就是个区间询问 [ x , x + k ] [x,x+k] [x,x+k]的问题。。。
此题首先记录每个数出现的位置,单独考虑每个数作为区间的绝对众数。
不妨设当前考虑的数为
v
\text v
v
v
\text v
v能作为区间
(
L
,
R
]
(\text L,\text R]
(L,R]的众数的充要条件是
cnt
R
−
cnt
L
>
R
−
L
2
\text{cnt}_{\text R}-\text{cnt}_{\text L}>\frac {\text R-\text L}{2}
cntR−cntL>2R−L
即
2
cnt
L
−
L
<
2
cnt
R
−
R
,
L
<
R
2\text{cnt}_{\text L}-\text L<2\text{cnt}_{\text R}-\text R ,\text{L}<\text R
2cntL−L<2cntR−R,L<R
记 b L = 2 cnt L − L \text b_{\text L}=2\text{cnt}_{\text L}-\text L bL=2cntL−L
于是转化成二维偏序:
L
<
R
b
L
<
b
R
\text{L}<\text R \\ \text b_{\text L}<\text b_{\text R}
L<RbL<bR
显然我们枚举右端点,用个树状数组就可以统计,但是复杂度不行。
观察每个数出现的位置将整个区间划分为下面模式
[
1
,
…
)
[
…
)
[
…
)
[
…
,
n+1
)
\color{blue}[1,\dots)[\dots)\color{red}[\dots)\color{black}[\dots,\text{n+1})
[1,…)[…)[…)[…,n+1)
上面
)
)
)代表的就是该数出现的位置。
对于每个
[
…
)
\color{red}[\dots)
[…)内部
b
i
\text b_{\text i}
bi是单调下降,且公差为1,显然当区间右端点在此区间内部时,区间左端点不可能在其内部。换句话说就是只有前面的即
[
1
,
…
)
[
…
)
\color{blue}[1,\dots)[\dots)
[1,…)[…)可能对区间右端点在
[
…
)
\color{red}[\dots)
[…)产生贡献。
而且重要的是 [ … ) \color{red}[\dots) […)的 b j \text b_{\text j} bj连续的,即对于每一个 [ … ) \color{red}[\dots) […)的 b j \text b_{\text j} bj我们需要在 [ 1 , … ) [ … ) \color{blue}[1,\dots)[\dots) [1,…)[…)找到 b i < b j \text b_{\text i}<\text b_{\text j} bi<bj,显然就是最开始那个问题,只需要求个前缀和然后区间询问即可。
每次过考虑 [ … ) \color{red}[\dots) […)后进行区间修改,将 [ … ) \color{red}[\dots) […)内部的 b j \text b_{\text j} bj插入数据结构中。
区间修改+前缀和区间询问
树状数组的话可以把差分转化为单点修改,那么本次要做到区间询问就意味着要用树状数组维护三维前缀和
∑
i
=
1
n
∑
j
=
1
i
∑
k
=
1
j
d
k
=
1
2
[
(
n
2
+
3
n
+
2
)
∑
i
=
1
n
d
i
−
(
2
n
+
3
)
∑
i
=
1
n
i
⋅
d
i
+
∑
i
=
1
n
i
2
⋅
d
i
]
\sum_{i=1}^{n}\sum_{j=1}^i\sum_{k=1}^jd_k=\frac{1}{2}[(n^2+3n+2)\sum_{i=1}^nd_i-(2n+3)\sum_{i=1}^ni·d_i+\sum_{i=1}^ni^2·d_i]
i=1∑nj=1∑ik=1∑jdk=21[(n2+3n+2)i=1∑ndi−(2n+3)i=1∑ni⋅di+i=1∑ni2⋅di]
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
T res=0;T fg=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
return res*fg;
}
const int N=500010;
int a[N],n;
vector<int> loc[N];
ll c0[N<<1],c1[N<<1],c2[N<<1];
void update(int k,ll v,int n)
{
ll i=k*v,ii=1ll*k*k*v;
while(k<=n)
{
c0[k]+=v;
c1[k]+=i;
c2[k]+=ii;
k+=k&-k;
}
}
ll qsum(int k)
{
ll ans=0;
ll k1=1ll*k*k+3*k+2,k2=2*k+3;
while(k)
{
ans+=k1*c0[k]-k2*c1[k]+c2[k];
k-=k&-k;
}
return ans>>1;
}
int main()
{
n=rd();rd();
for(int i=1;i<=n;i++) a[i]=rd(),loc[a[i]].push_back(i);
ll ans=0;
const int Bs=n+1;
for(int i=0;i<n;i++)
{
if(loc[i].empty()) continue;
int pre=0;
loc[i].push_back(n+1);
for(int j=0;j<loc[i].size();j++)
{
int R=2*j-pre+Bs,L=2*j-(loc[i][j]-1)+Bs;
ans+=qsum(R-1)-qsum(max(0,L-2));// 严格小于
update(L,1,n<<1|1);
update(R+1,-1,n<<1|1);
pre=loc[i][j];
}
pre=0;
for(int j=0;j<loc[i].size();j++)
{
int R=2*j-pre+Bs,L=2*j-(loc[i][j]-1)+Bs;
update(L,-1,n<<1|1);
update(R+1,+1,n<<1|1);
pre=loc[i][j];
}
}
printf("%lld\n",ans);
return 0;
}
要加油哦~