3.更进一步,在线区间处理
我们刚刚提到的是优化离线区间处理的方法,但在实战中,数组值往往是不断更新的,这要求我们找到在线处理区间更新与求值的方法。
我们会很容易的想到利用数据结构,线段树可以帮我们解决几乎所有的这样的问题,但线段树冗长的代码对调试与做题效率产生了挑战。
但我们又会注意到,几乎所有的DP+数据结构题都是单点修改的,这是因为在单一时间里,我们只会处理单一位次上的数据,这告诉我们,在大部分场景下,我们都可以用树状数组取代线段树。
同时,当题目涉及到极值更新时,我们也可以使用堆和优先队列帮助我们解题。更多的,set和map也可以发挥很大的用处。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+10,INF=1e18;
int n,C,m,f[N],ans;
int a1[N],c1[N],a2[N],c2[N];
int lowbit(int x){
return x&(-x);
}
void add(int x,int *a,int *c){
while(x<=n){
c[x]=a[x];
for(int i=1;i<lowbit(x);i*=2){
c[x]=max(c[x],c[x-i]);
}
x+=lowbit(x);
}
}
int query(int x,int y,int *a,int *c){
int nowcnt=-INF;
while(x<=y){
nowcnt=max(nowcnt,a[y]);
y--;
while(x<=y-lowbit(y)){
nowcnt=max(nowcnt,a[y]);
y-=lowbit(y);
}
}
return nowcnt;
}
signed main(){
cin>>n>>C>>m;
for(int i=1;i<=n;i++){
a1[i]=-INF;
a2[i]=-INF;
f[i]=-INF;
}
a1[1]=C;
f[1]=0;
add(1,a1,c1);
add(1,a2,c2);
int a,b;
for(int i=1;i<=m;i++){
cin>>a>>b;
int maxx1=query(1,a,a1,c1),maxx2=query(a,n,a2,c2);
int nowmax=max(maxx1-C*a,maxx2+C*a)+b;
if(nowmax<=f[a]) continue;
f[a]=nowmax;
a1[a]=f[a]+C*a,a2[a]=f[a]-C*a;
add(a,a1,c1),add(a,a2,c2);
}
for(int i=1;i<=n;i++){
ans=max(ans,f[i]);
}
cout<<ans;
}
4.根号分治-两条腿走路
在设计程序时,我们往往会碰到这么一种情况:我们有两种算法,一种在当前数据较小时表现优(我们往往通过在线新创立表格,间接转移,更新,来做到这一点),另一种在数据较大时表现优(对于这种情况,我们直接转移即可)。但是,两种算法都不能独立解决问题,这时候,我们可以请出根号分治。
对于当前数据较小的情况和数据较大的情况,我们分开考虑。更具体的,假设当前数据小于根号N,我们就用第一种转移方法,反之,我们就运用第二种更新方法,这会让整体时间复杂度降到 �(�∗����(�))O(n∗sqrt(n)) 。
例题:ATcoder335F --Hop Sugoroku
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,mod=998244353;
int n,a[N],dp[N],now[1011][1011],cnt;
int sq;
signed main(){
cin>>n;
sq=sqrt(n);
dp[1]=1;
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j=1;j<=sq;j++){
dp[i]=(now[j][i%j]+dp[i])%mod;
}
if(a[i]>sq){
for(int j=i+a[i];j<=n;j+=a[i]){
dp[j]=(dp[j]+dp[i])%mod;
}
}
else{
now[a[i]][i%a[i]]=(now[a[i]][i%a[i]]+dp[i])%mod;
}
cnt=(cnt+dp[i])%mod;
}
cout<<cnt<<endl;
}