https://ac.nowcoder.com/acm/contest/882/J
当时开局ac率100%,然后就死磕这道题(主要是因为题目短,不想去读别的题),一发RE,一发MLE,然后放弃了……
WJYTXDY
当时的想法很朴素,就是对于每一个位置的前缀和,看在他左边比他小的前缀和的数量(很明显时间复杂度不行,k*1e9,当时抱着如果评测机跑的快的话,说不定……),因为有负数,所以加个基准hash一下,但是题目只说了最大值不超过1e7但是没说最小值啊,然后RE了。然后又改成用unordered_map,就MLE,还没到TLE的测试点,(╥╯^╰╥)
因为注意到前缀和的变化是连续的,就开了两个unordered_map,dp和num,dp[i]表示小于等于i的数的数目,num[i]表示i现在出现的次数,每次ans+=dp[sum-1];
num[sum]++;
dp[sum]=num[sum]+dp[sum-1];//我觉得应该没错
而大佬看到前缀和的变化是连续的之后,就放弃树状数组维护,改用数组+lazy标记,tql
我遇到的最大问题就是区间太大,没有进行有效区间提取(有些全1区间之间不可能对结果产生贡献),如何处理见代码
要学习的地方我觉得是2点:
1.使用dp(贪心)的思想求出每段所在的连续段
2.因为前缀和是连续变化的,可以用lazy标记来代替树状数组来维护。
https://www.cnblogs.com/Yinku/p/11221494.html
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=3e7+5;
//1的个数最多1e7,所以有效的区间最大也就左扩1e7,右扩1e7,所以开3e7
const int N=1e6+5;
const int R=1e9;
const int base=1e7;
int l[N],r[N],f[N],g[N];
//f[i]以i段右端点为结尾的最大后缀和
//g[i]以i段左端点为开头的最大前缀和
int sum[M],mp[M],lazy[M];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d%d",&l[i],&r[i]);
}
f[0]=r[0]-l[0]+1;
for(int i=1;i<n;i++){
f[i]=max(0,f[i-1]-(l[i]-r[i-1]-1))+r[i]-l[i]+1;
//0:以i-1段右端点为结尾的最大后缀和不足以跨过[i-1,i]之间的-1
//f[i-1]-(l[i]-r[i-1]-1):跨过之后还剩下多少贡献给这段
}
g[n-1]=r[n-1]-l[n-1]+1;
for(int i=n-2;i>=0;i--){
g[i]=max(0,g[i+1]-(l[i+1]-r[i]-1))+r[i]-l[i]+1;
}
int i=0;
ll ans=0;
while(i<n){
int j=i+1;
while(j<n&&f[j-1]+g[j]>=l[j]-r[j-1]-1){
//说明这个[j-1,j]之间的-1段可以因为两侧的f[j-1]和g[j]足够大而连接起来
j++;
}
j--;
//此时j是从i开始最远能够连接到的区间
int left=max(0,l[i]-g[i]),right=min(R-1,r[j]+f[j]);
//以l[i]为开头的最大前缀和为g[i],那么我们就可以向左扩g[i](再扩区间和只能是负数了)
int t=i,mi=R,mx=0;
sum[0]=0;
for(int k=left;k<=right;k++){
//统计这一整段可连接区间的前缀和
//区间下标从1开始
if(k>=l[t]&&k<=r[t]) sum[k-left+1]=sum[k-left]+1;
else sum[k-left+1]=sum[k-left]-1;
if(k==r[t]) t++;
mi=min(mi,sum[k-left+1]+base);//前缀和的最小值
mx=max(mx,sum[k-left+1]+base);//前缀和的最大值
mp[sum[k-left+1]+base]++;
//mp记录前缀和出现过的次数
}
for(int k=mx-1;k>=mi;k--){
mp[k]+=mp[k+1];
//mp记录前缀和出现过的次数的后缀和
//即大于等于k的前缀和出现过的次数
}
ans+=mp[1+base];//包含最左侧点的贡献
//最左侧点是那个没有出现的0,mp[1+base]是前缀和大于等于1的数目
for(int k=left;k<=right;k++) {
t=sum[k-left+1]+base;
//t表示k位置sum的值
//mp[t+1]表示比t大的值的个数
//lazy[c+1]是在k位置左侧的比t大的值的个数的lazy
mp[t+1]-=lazy[t+1];//处理lazy
lazy[t]+=lazy[t+1]+1;//lazy下移
lazy[t+1]=0;//清空lazy
ans+=mp[t+1];
}
for(int k=mi;k<=mx;k++){
mp[k]=0;
lazy[k]=0;
}
i=j+1;
}
printf("%lld\n",ans);
return 0;
}