尺取法
其实就是双指针法。
给一道例题找找感觉吧:
HDU2029判断回文串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include<bits/stdc++.h> using namespace std; int main() { int n; scanf("%d",&n); while(n--) { string s; cin>>s; bool ans=true; int i=0,j=s.size()-1; while(i<j) { if(s[i]!=s[j]) { ans=false; break; } i++,j--; } if(ans) cout<<"yes"<<endl; else cout<<"no"<<endl; } return 0; }
|
尺取法可以解决滑动窗口问题,也可以进行数组去重。
指针不够的时候可以加;
二分法
这是一种非常考验边界处理细节的一种算法。
STL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include<bits/stdc++.h> using namespace std; const int maxn=100000+10; const int INF=2*int(1e9)+10; #define LL long long int cmd(int a,int b){ return a>b; } int main(){ int num[6]={1,2,4,7,15,34}; sort(num,num+6); int pos1=lower_bound(num,num+6,7)-num; int pos2=upper_bound(num,num+6,7)-num; cout<<pos1<<" "<<num[pos1]<<endl; cout<<pos2<<" "<<num[pos2]<<endl; sort(num,num+6,cmd); int pos3=lower_bound(num,num+6,7,greater<int>())-num; int pos4=upper_bound(num,num+6,7,greater<int>())-num; cout<<pos3<<" "<<num[pos3]<<endl; cout<<pos4<<" "<<num[pos4]<<endl; return 0; }
|
其实也有一种可以不用 greater<int>()
仿函数的方法:
- 小于等于:查找最小的大于某数的下标(upper_bound) - 1
- 小于:查找最小的大于等于某数的下标(lower_bound) - 1
在这里不多赘述,只可意会不可言传。
练习:
P1462通往奥格瑞玛的道路
二分+最短路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| #include<bits/stdc++.h> using namespace std; const int N=50005,M=1000005; int n,m,b; int f[N],fir[N],cnt,dist[N],righ[N]; struct edge{ int next,to,w; }e[M]; const int MAX=1e9; void add(int u,int v,int w) { e[++cnt].next=fir[u]; e[cnt].to=v; e[cnt].w=w; fir[u]=cnt; } struct node{ int p,d; node(){} node(int a,int b){p=a;d=b;} }; bool operator<(const node &a,const node &b) { return a.d>b.d; } priority_queue<node>q; int dijksta(int x) { if(x<f[1]) return 0; for(int i=1;i<=n;i++) dist[i]=1e9; memset(righ,0,sizeof(righ)); dist[1]=0; for(int i=1;i<=n;i++) { q.push(node(i,dist[i])); } for(int i=1;i<=n;i++) { while(righ[q.top().p]) q.pop(); node now=q.top(); int p=now.p; righ[p]=1; q.pop(); for(int j=fir[p];j;j=e[j].next) { int v=e[j].to; if(f[v]>x) continue; int d=e[j].w+dist[p]; if(d<dist[v]) { dist[v]=d; q.push(node(v,d)); } } } if(dist[n]<=b) return 1; return 0; } int erfen() { int l=1,r=MAX,mid=(l+r)>>1; int c; while(l<=r) { c=dijksta(mid); if(c!=0) { r=mid-1; mid=(l+r)>>1; } else { l=mid+1; mid=(l+r)>>1; } } return l; } int main() { scanf("%d%d%d",&n,&m,&b); for(int i=1;i<=n;i++) { scanf("%d",&f[i]); } for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } if(dijksta(MAX)==0) { cout<<"AFK"<<endl; return 0; } cout<<erfen()<<endl; }
|
P1824进击的奶牛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include<bits/stdc++.h> using namespace std; const int maxn=1e9; int a[1000010],n,c; bool P(int d) { int k=0,last=-maxn; for(int i=1;i<=n;i++) { if(a[i]-last>=d) { last=a[i]; k++; } } return k>=c; } int main() { scanf("%d%d",&n,&c); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } sort(a+1,a+n+1); int l=0,r=1e9,ans,mid; while(l<=r) { if(P(mid=l + r >> 1)) ans=mid,l=mid+1; else r=mid-1; } printf("%d",ans); }
|
P1419
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<bits/stdc++.h> const int N=100005; using namespace std; int n,s,t; int a[100050]; double sum[N]; int q[N]; bool check(double k) { int l=1,r=0; for(int i=1;i<=n;i++) { sum[i]=sum[i-1]+a[i]*1.0-k; } for(int i=s,p=0;i<=n;i++,p++) { while(r>=l&&sum[q[r]]>sum[p]) r--; q[++r]=p; while(i-q[l]>t)++l; if(sum[i]-sum[q[l]]>=0) return 1; } return 0; } int main() { cin>>n; cin>>s>>t; for(int i=1;i<=n;i++) scanf("%d",&a[i]); double l=-1e4,r=1e4,mid; while(r-l>1e-5) { mid=(l+r)/2; if(check(mid)) l=mid; else r=mid; } printf("%.3f\n",l); return 0; }
|
POJ3122
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include<stdio.h> #include<math.h> #include<iostream> using namespace std; double PI=acos(-1.0); double a[100000]; int n,m; bool check(double mid) { int sum=0; for(int i=1;i<=n;i++) { sum+=(int)(a[i]/mid); } if(sum>=m) return true; else return false; } int main() { int t; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); m++; double maxp=0; for(int i=1;i<=n;i++) { int r; scanf("%d",&r); a[i]=PI*r*r; if(maxp<a[i]) maxp=a[i]; } double l=0,r=maxp; for(int i=1;i<=100;i++) { double mid=l+(r-l)/2; if(check(mid)) l=mid; else r=mid; } printf("%.4f\n",l); } return 0; }
|
P1083
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include<bits/stdc++.h> using namespace std; #define int long long const int N=1e6+117; int r[N],d[N],s[N],t[N],pre[N],ne[N]; int n,m; bool check(int mid) { memset(pre,0,sizeof(pre)); for(int i=1;i<=mid;i++) { pre[s[i]]+=d[i]; pre[t[i]+1]-=d[i]; } for(int i=1;i<=n;i++) { ne[i]=ne[i-1]+pre[i]; if(ne[i]>r[i]) return false; } return true; } signed main() { cin>>n>>m; for(int i=1;i<=n;i++) scanf("%lld",&r[i]); for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&d[i],&s[i],&t[i]); if(check(m)) { cout<<"0"; return 0; } int l=1,r=m; while(l<r) { int mid=(l+r)>>1; if(check(mid)) l=mid+1; else r=mid; } printf("-1\n%lld",l); return 0; }
|
P2678
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include<bits/stdc++.h>
using namespace std;
int L,N,M; int ans; int stone[500005]; int s[100000005]={0};
int f(int x) { int size=0,take=0; s[0]=0; for(int i=1;i<=N;++i) { if(stone[i]-s[size]<x) { take++; continue; } s[++size]=stone[i]; } if(L-s[size]<x) { size--; take++; } return take<=M; } int main () { scanf("%d%d%d",&L,&N,&M); for(int i=1;i<=N;++i) scanf("%d",&stone[i]); int left=0,right=L+1; while(left+1<right) { int mid=(left+right)/2; if(f(mid)==1) left=mid; else right=mid; } ans=left; cout<<ans; return 0; }
|
P1314
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include<bits/stdc++.h> using namespace std; #define int long long const int N=2e5+7; long long ans=1e12+7; int n,m,s,maxn=-1,sum; int w[N],v[N],l[N],r[N]; int ren[N],rev[N],y; bool check(int mid) { y=0,sum=0; memset(ren,0,sizeof(ren)); memset(rev,0,sizeof(rev)); for(int i=1;i<=n;i++) { if(w[i]>=mid) ren[i]=ren[i-1]+1,rev[i]=rev[i-1]+v[i]; else ren[i]=ren[i-1],rev[i]=rev[i-1]; } for(int i=1;i<=m;i++) y+=(ren[r[i]]-ren[l[i]-1])*(rev[r[i]]-rev[l[i]-1]); sum=llabs(y-s); if(y>s) return true; else return false; } signed main() { cin>>n>>m>>s; for(int i=1;i<=n;i++) { scanf("%lld%lld",&w[i],&v[i]); maxn=max(maxn,w[i]); } for(int i=1;i<=m;i++) scanf("%lld%lld",&l[i],&r[i]); int l=0,r=maxn; while(l<=r) { int m=l+(r-l)/2; if(check(m)) l=m+1; else r=m-1; if(sum<ans) ans=sum; } printf("%lld",ans); return 0; }
|
三分法
练习
P3382
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include<bits/stdc++.h> using namespace std; const double eps=1e-6; int n; double a[20]; double f(double x) { double s=0; for(int i=n;i>=0;i--) s=s*x+a[i]; return s; } int main() { double l,r; scanf("%d%lf%lf",&n,&l,&r); for(int i=n;i>=0;i--) scanf("%lf",&a[i]); while(r-l>eps) { double k=(r-l)/3.0; double m1=l+k; double m2=r-k; if(f(m1)>f(m2)) r=m2; else l=m1; } printf("%.5f\n",l); return 0; }
|
P3745
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e5+7; int n,m,t[N],b[N]; ll A,B,C,ans; ll calc1(int p) { ll x=0,y=0; for(int i=1;i<=m;i++) { if(b[i]<p) x+=p-b[i]; else y+=b[i]-p; } if(A<B) return min(x,y)*A+(y-min(x,y))*B; else return y*B; } ll calc2(int p) { ll sum=0; for(int i=1;i<=n;i++) { if(t[i]<p) sum+=(p-t[i])*C; } return sum; } int main() { cin>>A>>B>>C>>n>>m; for(int i=1;i<=n;i++) scanf("%d",&t[i]); for(int i=1;i<=m;i++) scanf("%d",&b[i]); sort(b+1,b+m+1); sort(t+1,t+n+1); if(C>=1e16) { printf("%lld\n",calc1(t[1])); return 0; } ans=1e16; int l=1,r=N; while(r-l>2) { int m1=l+(r-l)/3; int m2=r-(r-l)/3; ll c1=calc1(m1)+calc2(m1); ll c2=calc1(m2)+calc2(m2); if(c1<=c2) r=m2; else l=m1; } for(int i=l;i<=r;i++) { ll x=calc1(i)+calc2(i); ans=min(ans,x); } printf("%lld",ans); }
|
P1883
咕咕咕
倍增与ST
超级快的速度:O(log2n)
原理与二分法相对,不多赘述。
倍增
利用二进制的“特性”,可以将一个数展开得到我们想要的“倍增形式”。
习题
P4155
这道题有道思路相仿的题目可以先做一下:P1803
对于本题:
- 断环为链。
- 贪心:选择一个区间,下一个区间的左端点必须小于等于这个区间的右端点,其中满足条件的下一个区间的右端点最大为最优。
- 倍增:快速查询走2i步所能到达的最优的区间,避免暴力枚举。
定义go[s][i]
表示从第s个区间出发,走2i个最优区间所到达的区间(战士的范围)。
所以首先需要预处理出go[][]
。
复杂度:nlog2n
递推式:go[s][i]=go[go[s][i-1]][i-1]
总复杂度:2nlog2n
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include<bits/stdc++.h> using namespace std; int n,m; const int N=4e5+1; struct yum{ int id,l,r; bool operator<(const yum x) const{ return l<x.l; } }w[N*2]; int n2; int go[N][20],res[N]; void init() { int nxt=1; for(int i=1;i<=n2;i++) { while(nxt<=n2&&w[nxt].l<=w[i].r) nxt++; go[i][0]=nxt-1; } for(int i=1;(1<<i)<=n;i++) for(int j=1;j<=n2;j++) go[j][i]=go[go[j][i-1]][i-1]; } void get(int x) { int len=w[x].l+m,cur=x,ans=1; for(int i=log2(N);i>=0;i--) { int pos=go[cur][i]; if(pos&&w[pos].r<len) { ans+=1<<i; cur=pos; } res[w[x].id]=ans+1; } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { w[i].id=i; scanf("%d%d",&w[i].l,&w[i].r); if(w[i].r<w[i].l) w[i].r+=m; } sort(w+1,w+1+n); n2=n; for(int i=1;i<=n;i++) { n2++; w[n2]=w[i]; w[n2].l=w[i].l+m; w[n2].r=w[i].r+m; } init(); for(int i=1;i<=n;i++) get(i); for(int i=1;i<=n;i++) printf("%d ",res[i]); return 0; }
|
ST
ST算法基于倍增原理,适用于求解静态空间的区间最值查询(RMQ)。
RMQ问题:给定长度为n的静态数列,做m次查询,每次给定一段区间[L,R]
,求这段区间中的最值。
以最小值为例:
若一个大区间能被两个小区间覆盖,那么大区间的最值等于两个小区间的最值的最值(重合不影响)。
于是得到基本思路:
- 将序列拆分为若干个小区间,并预处理小区间的最值。
- 对任意区间进行最值查询,只需要找到覆盖它的两个小区间,由两个小区间的最值得出。
定义dp[s][k]=min(dp[s][k-1],dp[s+(1<<(k-1))][k-1])
,
代表从s开始的2k的区间最值为从s开始的2k−1区间和从s+2k−1开始的2k−1区间的最值。
递推关系实际是一个DP
的过程,复杂度nlogn。
区间[L,R]
的长度为len=R-l+1
,两个小区间的长度都为x,使得x≤len并且2x≥len,这样可以保证覆盖。
于是令x=2k,k=log2(x),由于k是向下取整的,于是能满足x≤len并且2x≥len的条件。
当然也可以写作k=(int)(log(double(R−L+1))/log(2.0))(利用换底公式)。
如果嫌库函数慢的话也可以自己写log函数。
ST:
nlogn−(i=1∑logn2i)
P2251
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
#include<bits/stdc++.h> using namespace std; int n,m; int a[100005]; int st[100005][23]; int LOG2[100005]; void init() { LOG2[0]=-1; for(int i=1;i<=n;i++) LOG2[i]=LOG2[i>>1]+1; for(int i=1;i<=n;i++) st[i][0]=a[i]; for(int i=1;(1<<i)<=n;i++) for(int j=1;j+(1<<i)-1<=n;j++) st[j][i]=min(st[j][i-1],st[j+(1<<(i-1))][i-1]); } int qe(int l,int r) { int k=LOG2[r-l+1]; return min(st[l][k],st[r-(1<<k)+1][k]); } int main() { cin>>n>>m; for(int i=1;i<=n;i++) scanf("%d",&a[i]); init(); for(int i=1;i<=n-m+1;i++) { printf("%d\n",qe(i,i+m-1)); } return 0; }
|
上面注释的做法是用set
维护了一个长度为m
的序列,利用set
自动排序的机制和定值删除的功能实现,pair
是为了防止重复。
然后删头去尾,每次输出第一个就是最小的。
st
做法没啥好说的。
P1816
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<bits/stdc++.h> using namespace std; const int N=1e5+7; int a[N],st[N][23]; int n,m; void init() { for(int i=1;i<=m;i++) st[i][0]=a[i]; for(int i=1;(1<<i)<=m;i++) for(int j=1;j+(1<<i)-1<=m;j++) st[j][i]=min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
} int qe(int l,int r) { int k=(int)log2(r-l+1); return min(st[l][k],st[r-(1<<k)+1][k]); } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) scanf("%d",&a[i]); init(); for(int i=1;i<=n;i++) { int l,r; scanf("%d%d",&l,&r); printf("%d ",qe(l,r)); } return 0; }
|
板子题,没啥好说的。
关于ST的一些理解:
st[i][j]
代表以i为起点,长度为2j的区间中的最值大小(包含i这个端点)
st[j][i]=min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
所以预处理的时候这个是直接加上区间长度。
min(st[l][k],st[r-(1<<k)+1][k]);
而访问的时候要减去长度再加一。
前缀和
sum[i]=sum[i-1]+a[i]
sum[i]
即元素a在[0,i]的区间和,这样就可以快速进行区间求和了。
sum[l~r]=sum[r]-sum[l-1]
二阶前缀和
设:
- A 是原数组
- B 是 A 的前缀和,即 Bx=∑i=1xAi
- C 是 B 的前缀和,即 Cx=∑i=1xBi
对于 Ai←Ai+1 这一操作,Bi∼Bx 都会加一,即 Cx←Cx+(x−i+1)×1
练习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include <iostream> #include <cstring> #include <cstdio> #define lowbit(x) (x & (-x)) using namespace std; const int N = 1e5 + 7; typedef long long LL;
int n, m; LL a[N]; LL B[N], C[N];
void addB(int x, LL v) { for (int i = x; i <= n; i += lowbit(i)) B[i] += v; } void addC(int x, LL v) { for (int i = x; i <= n; i += lowbit(i)) C[i] += v; } LL queryB(int x) { LL res = 0; for (int i = x; i; i -= lowbit(i)) res += B[i]; return res; } LL queryC(int x) { LL res = 0; for (int i = x; i; i -= lowbit(i)) res += C[i]; return res; } void update(int x, int v) { addB(x, v); addC(x, 1ll * x * v); } LL query(int x) { return queryB(x) * (x + 1) - queryC(x); }
int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%lld", &a[i]); update(i, a[i]); } while (m--) { string s; cin >> s; if (s == "Query") { int x; scanf("%d", &x); printf("%lld\n", query(x)); } else if (s == "Modify") { int x, v; scanf("%d%d", &x, &v); update(x, v - a[x]); a[x] = v; } } return 0; }
|
三阶前缀和
设:
- A 是原数组
- B 是 A 的前缀和,即 Bx=∑i=1xAi
- C 是 B 的前缀和,即 Cx=∑i=1xBi
- D 是 C 的前缀和,即 Dx=∑i=1xCi
对于 Ai←Ai+1 这一操作,Cj 会变成 Cj+(j−i+1)×1
即 Dx 会变成 Dx+1+2+⋯+(x−i+1),即 Dx+2(x−i+1)×(x−i+2)
Cx=i=1∑xBi=i=1∑x(x−i+1)×Ai
Dx=i=1∑x2(i2−3i)Ai−2xiAi+(x2+3x+2)Ai
也可以这样写:
Dx=i=1∑x2i2Ai−(2x+3i)Ai+(x2+3x+2)Ai
维护 i2Ai,iAi,Ai 即可。
练习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #include <iostream> #include <cstdio> #define lowbit(x) (x & (-x)) using namespace std; const int N = 2e5 + 7; const int MOD = 998244353; typedef long long LL; const int inv = 499122177;
int n, q; LL A[N]; LL B[N], C[N], D[N];
void addB(int x, LL v) { for (int i = x; i <= n; i += lowbit(i)) B[i] = (B[i] + v) % MOD; } void addC(int x, LL v) { for (int i = x; i <= n; i += lowbit(i)) C[i] = (C[i] + v) % MOD; } void addD(int x, LL v) { for (int i = x; i <= n; i += lowbit(i)) D[i] = (D[i] + v) % MOD; } void update(LL x, LL v) { addB(x, v); addC(x, 1ll * v * x % MOD); addD(x, ((1ll * x * x - 3ll * x) % MOD + MOD % MOD) * 1ll * v % MOD); } LL queryB(int x) { LL res = 0; for (int i = x; i; i -= lowbit(i)) res = (res + B[i]) % MOD; return res; } LL queryC(int x) { LL res = 0; for (int i = x; i; i -= lowbit(i)) res = (res + C[i]) % MOD; return res; } LL queryD(int x) { LL res = 0; for (int i = x; i; i -= lowbit(i)) res = (res + D[i]) % MOD; return res; } LL query(LL x) { return ((queryD(x) - 1ll * x * 2 % MOD * queryC(x) % MOD + (1ll * x * x + 1ll * 3 * x + 2) % MOD * queryB(x) % MOD) % MOD + MOD) % MOD; }
signed main() { scanf("%d%d", &n, &q); for (int i = 1; i <= n; i++) { scanf("%lld", &A[i]); update(i, A[i]); } while (q--) { int op; LL x, y; scanf("%d", &op); if (op == 1) { scanf("%lld%lld", &x, &y); update(x, (y - A[x] + MOD) % MOD); A[x] = y; } else if (op == 2) { scanf("%lld", &x); printf("%lld\n", (1ll * query(x) * inv) % MOD); } } return 0; }
|
与此类似的还有一题~
P8313
题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #include <iostream> #include <vector> #include <algorithm> #define lowbit(x) (x & (-x)) using namespace std; const int N = 2e5 + 7; typedef long long LL;
int n; LL ans; int a[N], S[N]; LL C[N << 1], D[N << 1], E[N << 1]; vector<int> num; vector<int> loc[N];
void add(int x, LL v) { for (int i = x; i <= 2 * n + 1; i += lowbit(i)) { C[i] += v; D[i] += 1ll * v * x; E[i] += 1ll * v * x * x; } } LL query(int x) { LL res = 0; for (int i = x; i; i -= lowbit(i)) res += 1ll * C[i] * (1ll * x * x + 3ll * x + 2) - 1ll * D[i] * (2 * x + 3) + E[i]; res >>= 1; return res; } int find(int x) { return lower_bound(num.begin(), num.end(), x) - num.begin() + 1; }
int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); num.push_back(a[i]); } sort(num.begin(), num.end()); num.erase(unique(num.begin(), num.end()), num.end()); int cnt = (int)num.size(); for (int i = 1; i <= n; i++) a[i] = find(a[i]); for (int i = 1; i <= n; i++) loc[a[i]].emplace_back(i);
for (int i = 1; i <= cnt; i++) { loc[i].push_back(n + 1); int last = 0; for (int j = 0; j < (int)loc[i].size(); j++) { int s = 2 * j - last + n + 1; int t = 2 * j - loc[i][j] + 1 + n + 1; ans += query(s - 1) - (t >= 2 ? query(t - 2) : 0); add(t, 1); add(s + 1, -1); last = loc[i][j]; } last = 0; for (int j = 0; j < (int)loc[i].size(); j++) { int s = 2 * j - last + n + 1; int t = 2 * j - loc[i][j] + 1 + n + 1; add(t, -1); add(s + 1, 1); last = loc[i][j]; } } printf("%lld\n", ans); return 0; }
|
差分
差分是前缀和的逆运算,它将区间修改转化为端点修改。
一维差分
定义差分数组D[]
,D[i]=a[i]-a[i-1]
。
查询:
a[k]=i=1∑kD[i]
也就是对差分数组求前缀和。
区间修改:
将区间[l,r]的每个元素都加d,
只需要:
D[l]+=d,D[r+1]-=d
这样就可以保证:
- 1≤x<l,a[x]不变。
- l≤x≤r,a[x]增加d。
- r<x≤n,a[x]不变。
n个数m次区间修改+一次查询复杂度:O(mn)→O(m+n)
差分对于单点查询不是很高效,这时候就需要线段树或者树状数组了。
习题:
HDU1556
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include<bits/stdc++.h> using namespace std; const int N=1e5+7; int D[N]; int n; int main() { while(~scanf("%d",&n)) { memset(D,0,sizeof(D)); for(int i=1;i<=n;i++) { int l,r; scanf("%d%d",&l,&r); D[l]++,D[r+1]--; } for(int i=1;i<=n;i++) { D[i]=D[i-1]+D[i]; if(i!=n) printf("%d ",D[i]); else printf("%d\n",D[i]); } } return 0; }
|
二维差分
点扩展为面
差分数组的前缀和:
a[i][j]代表点(1,1)到点(i,j)的矩形的面积(所有元素之和);
差分:
D[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]
a就是大矩形,D就是大矩形中的小矩形。

区间修改
将点(x1,y1),(x2,y2)围成的一段区间中每个元素都加上d,需要:
D[x1][y1]+=d
D[x1][y2+1]-=d
D[x2+1][y1]-=d
D[x2+1][y2+1]+=d//被减了两次的地方需要加回来一次
处理的时候可以直接把D数组当成a来更新,以此节省空间,因为是从前往后更新,后面的值需要前面的值,前面的值不需要后面的值,所以互不影响。
习题:
P3397
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include<bits/stdc++.h> using namespace std; int n,m; int D[5005][5005]; int main() { scanf("%d%d",&n,&m); while(m--) { int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); D[x1][y1]+=1; D[x1][y2+1]-=1; D[x2+1][y1]-=1; D[x2+1][y2+1]+=1; } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { D[i][j]+=D[i-1][j]+D[i][j-1]-D[i-1][j-1]; printf("%d ",D[i][j]); } puts(""); } return 0; }
|
其中,计算前缀和可以这样替换:
1 2 3 4 5 6
| for(int i=1;i<=n;i++) for(int j=1;j<n;j++) D[i][j+1]+=D[i][j]; for(int i=1;i<n;i++) for(int j=1;j<=n;j++) D[i+1][j]+=D[i][j];
|
即横向纵向分别处理前缀和。
也可以这样写:
1 2 3 4 5 6
| for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) D[i][j]+=D[i][j-1]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) D[i][j]+=D[i-1][j];
|
二维前缀和的应用:
P2280
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include<bits/stdc++.h> using namespace std; const int N=5001; int n,m,ans=-1; int sum[5007][5007]; int main() { cin>>n>>m; for(int i=1;i<=n;i++) { int x,y,v; scanf("%d%d%d",&x,&y,&v); sum[++x][++y]+=v; } for(int i=1;i<=N;i++) for(int j=1;j<N;j++) sum[i][j+1]+=sum[i][j]; for(int j=1;j<=N;j++) for(int i=1;i<N;i++) sum[i+1][j]+=sum[i][j]; for(int i=m;i<=N;i++) for(int j=m;j<=N;j++) { int p=sum[i][j]-sum[i-m][j]-sum[i][j-m]+sum[i-m][j-m]; ans=max(ans,p); } printf("%d",ans); return 0; }
|
二阶差分
这是一个链接
离散化
很多时候我们并不关心元素的绝对大小,我们关心它们的相对大小,那么就可以对它们进行离散化处理。
离散化的具体步骤:
- 排序
- 编号
- 归位
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<bits/stdc++.h> using namespace std; const int N=500; struct data{ int val,id; }olda[N]; int newa[N]; bool cmp(data x,data y) { return x.val<y.val; } int n; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&olda[i].val); olda[i].id=i; } sort(olda+1,olda+1+n,cmp); for(int i=1;i<=n;i++) { newa[olda[i].id]=i; if(olda[i].val==olda[i-1].val) newa[olda[i].id]=newa[olda[i-1].id]; } for(int i=1;i<=n;i++) { printf("%d ",newa[i]); } return 0; }
|
也可以不预处理出来,通过库函数排序、判重,用的时候二分查找即可。
这是保序的写法,也就是映射后元素之间大小关系不变。
若是不需要保序,那么 map
或者哈希表就都可以做,好写很多。
排序
常见排序算法:
桶排
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include<bits/stdc++.h> using namespace std;
int* arr_sort(int *arr,int n) { int maxValue = arr[0]; for(int i = 1; i < n; ++i) { if(arr[i] > maxValue) { maxValue = arr[i]; } } const int bucketCnt = 10; vector<int> buckets[bucketCnt]; int bucketSize = 1; while(maxValue) { maxValue /= 10; bucketSize *= 10; } bucketSize /= 10; for(int i = 0; i < n; ++i) { int idx = arr[i] / bucketSize; buckets[idx].push_back(arr[i]); for(int j = int(buckets[idx].size())-1; j > 0; --j) { if(buckets[idx][j] < buckets[idx][j-1]) swap(buckets[idx][j], buckets[idx][j-1]); } } for(int i = 0, k = 0; i < bucketCnt; ++i) for(int j = 0; j < int(buckets[i].size()); ++j) arr[k++] = buckets[i][j]; return arr; } int* ArrSort(int *arr, int n) { int maxv; for(int i = 1; i <= n; ++i) maxv = max(maxv,arr[i]); const int bcnt = 10; vector<int> buc[bcnt]; int bsize = 1; while(maxv) { maxv /= 10; bsize *= 10; } bsize /= 10; for(int i = 1; i <= n; ++i) { int idx = arr[i] / bsize; buc[idx].emplace_back(arr[i]); for(int j = int(buc[idx].size())-1; j > 0; --j) if(buc[idx][j] < buc[idx][j-1]) swap(buc[idx][j], buc[idx][j-1]); } for(int i = 0, k = 1; i < bcnt; ++i) for(int j = 0; j < int(buc[i].size()); ++j) arr[k++] = buc[i][j]; return arr; }
int main() { int n; int* a; scanf("%d",&n); a = (int*)malloc(sizeof(int)*n+4); for(int i = 1; i <= n; ++i) scanf("%d",&a[i]); a = ArrSort(a,n); for(int i = 1; i <= n; ++i) printf("%d ", a[i]); free(a); return 0; }
|
先分桶,这里按照十个来分。
然后把元素丢到它对应的桶内,然后对桶内进行排序,这里使用插入排序。
然后按顺序合并即可。
排列
STL
next_permutation()
函数,该函数内前两个参数为左开右闭的一个排列区间的指针(如s.begin(),s.end()
),返回值为bool
类型,表示有无下一个排列,排列后的结果会返回原序列中并覆盖。
分块
分块是一种高效的思想,常用来处理区间问题。
1 2 3 4 5 6
| int sq = sqrt(n); for (int i = 1; i <= sq; ++i) { st[i] = n / sq * (i - 1) + 1; ed[i] = n / sq * i; }
|
sqrt 代表分的块的个数,每个块的长度不一定为 sqrt,例如n=12。
但是序列长度不一定是完全平方数,所以我们把最后一小块归入到原来的最后一块中块。
然后就是为每个元素确定它所属于的块:
1 2 3
| for (int i = 1; i <= sq; ++i) for (int j = st[i]; j <= ed[i]; ++j) bel[j] = i;
|
处理每个块的大小:
1 2
| for (int i = 1; i <= sq; ++i) size[i] = ed[i] - st[i] + 1;
|
例题
线段树能做的,为什么我分块不行?
P3372 【模板】线段树 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+8; int a[N]; struct block { int st,ed,sum,add; }e[N>>1]; int n,m,bel[N]; void init() { int sq=sqrt(n); for(int i=1;i<=sq;i++) { e[i].st=n/sq*(i-1)+1; e[i].ed=n/sq*i; } e[sq].ed=n; for(int i=1;i<=sq;i++) { for(int j=e[i].st;j<=e[i].ed;j++) bel[j]=i,e[i].sum+=a[j]; } } void modify(int l,int r,int x) { if(bel[l]==bel[r]) { for(int i=l;i<=r;i++) { a[i]+=x; e[bel[l]].sum+=x; } return; } for(int i=l;i<=e[bel[l]].ed;i++) { a[i]+=x; e[bel[l]].sum+=x; } for(int i=e[bel[r]].st;i<=r;i++) { a[i]+=x; e[bel[r]].sum+=x; } for(int i=bel[l]+1;i<bel[r];i++) e[i].add+=x; } int query(int l,int r) { int s=0; if(bel[l]==bel[r]) { for(int i=l;i<=r;i++) s+=a[i]+e[bel[l]].add; return s; } for(int i=l;i<=e[bel[l]].ed;i++) s+=a[i]+e[bel[i]].add; for(int i=e[bel[r]].st;i<=r;i++) s+=a[i]+e[bel[r]].add; for(int i=bel[l]+1;i<bel[r];i++) s+=e[i].sum+e[i].add*(e[i].ed-e[i].st+1); return s; } signed main() { scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); init(); while(m--) { int op,x,y,k; scanf("%lld%lld%lld",&op,&x,&y); if(op==1) { scanf("%lld",&k); modify(x,y,k); } else { printf("%lld\n",query(x,y)); } } return 0; }
|
嗯,比线段树好看不少,代码也简洁,思维难度也不高。
不过它们都有区间标记这个小技巧。
分块方式
第一种:
1 2 3 4 5 6
| int sq = sqrt(n); for (int i = 1; i <= sq; ++i) { st[i] = n / sq * (i - 1) + 1; ed[i] = n / sq * i; }
|
这里的 sq 为块的数量,根据块的数量来均摊块的长度,特别地,最后一个不完整的块将被归并到原来的最后的块中。
小优化:由于是 n 个块,因此可以开 2n 空间就足够。
第二种:
1 2 3 4 5
| for (int i = 1; i <= n; i++) { cin >> a[i]; id[i] = (i - 1) / len + 1; s[id[i]] += a[i]; }
|
这个的 len 为块的长度,根据长度来决定块的个数。