差分是前缀和的逆运算。 d 1... n d_{1...n} d1...n是差分数组, a 1... n a_{1...n} a1...n是原数组。关系如下式。
d i = a i − a i − 1 d_i=a_i-a_{i-1} di=ai−ai−1
则通过 d 1... n d_{1...n} d1...n数组求 a i a_i ai,为求 d 1... n d_{1...n} d1...n数组的前缀和。
a i = ∑ j = 1 i d j a_i = \sum_{j=1}^id_j ai=j=1∑idj
通过 d 1... n d_{1...n} d1...n数组求前缀和为
s u m i = ∑ i = 1 n ∑ j = 1 i d i = ∑ i = 1 n ( n − i + 1 ) d i sum_i=\sum_{i=1}^n{\sum_{j=1}^id_i}=\sum_{i=1}^n(n-i+1)d_i sumi=i=1∑nj=1∑idi=i=1∑n(n−i+1)di
P1083 借教室
求第一个不能减的区间。
二分借教室操作数,用差分数组 O ( n ) O(n) O(n)借教室,总时间复杂度为 O ( n log m ) O(n\log m) O(nlogm)
#include
#define debug(__x) cout<<#__x<<"="<<__x<
#define endl '\n'
using namespace std;
using ll = long long;
const int maxn = 1e6+5;
struct Lease{
ll d;
int s,t;
Lease(ll x,int y,int z):d(x),s(y),t(z) {}
};
vector<Lease> range;
ll a[maxn]; // 差分数组
int n,m;
void sub(int l,int r){ // 借教室操作
for(int i=l;i<=r;++i){
a[range[i].s]-=range[i].d;
a[range[i].t+1]+=range[i].d;
}
}
void add(int l,int r){ // 撤销借教室操作
for(int i=l;i<=r;++i){
a[range[i].s]+=range[i].d;
a[range[i].t+1]-=range[i].d;
}
}
bool judge(){ // 判断是否有一天教室数不够借
int now = 0;
for(int i=1;i<=n;++i){
now += a[i];
if(now<0)return false;
}
return true;
}
int main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(false);
cin>>n>>m;
for(int now,last=0,i=1;i<=n;++i){
cin>>now;
a[i] = now - last;
last = now;
}
for(int d,s,t,i=0;i<m;++i){
cin>>d>>s>>t;
range.emplace_back(d,s,t);
}
sub(0,m-1);
if(judge()){
cout<<0<<endl;
return 0;
}
cout<<-1<<endl;
int l=0,r=m-1,mid;
add(l,r);
while(l < r){
mid = l+r>>1;
sub(l,mid);
// debug(l);debug(r);
if(judge()){
l = mid + 1;
}else{
r = mid;
add(l,mid);
}
}
cout<<l+1<<endl;
}
就是在树上进行差分操作,实现功能与差分一样,可以 O ( 1 ) O(1) O(1)实现区间修改。分为点差分和边差分。
点差分
cnt[u]+=x
可理解为u到树跟的路径上所有节点的权重加x。在把(u,v)
路径上的点权加x的操作如下。
cnt[u] += x;
cnt[v] += x;
cnt[lca] -= x;
cnt[fa[lca]] -= x;
用差分数组求点权
v a l [ u ] = ∑ v ∈ S u b T r e e r o o t = u c n t [ v ] val[u]=\sum_{v\in SubTree_{root=u}}cnt[v] val[u]=v∈SubTreeroot=u∑cnt[v]
边差分
把每个点连到它的父节点的边权放在该点上。原理和点分治一样。在把(u,v)
路径上的边权加x的操作如下。
cnt[u] += x;
cnt[v] += x;
cnt[lca] -= 2*x;
在一棵树上,在给定点对的路径上的点权加1。求最大点权。树上差分最适合修改多查询少的题。这题只用1次查询。
#include
#define debug(__x) cout<<#__x<<"="<<__x<
#define endl '\n'
using namespace std;
using ll = long long;
const int maxn = 5e4+5;
vector<int> G[maxn];
int f[maxn][15];
int cnt[maxn];
int dep[maxn];
int ans=0;
void dfs(int u,int fa){
f[u][0] = fa;
dep[u] = dep[fa] + 1;
for(int i=1;i<15;++i)
f[u][i] = f[f[u][i-1]][i-1];
for(int son : G[u]){
if(son == fa)continue;
dfs(son,u);
}
}
int lca(int x,int y){
if(dep[x] < dep[y]) swap(x,y);
int t = dep[x] - dep[y];
for(int i=0;i<15;++i)
if(t&(1<<i))
x = f[x][i];
if(x==y) return x;
for(int i=14;i>=0;--i){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
void sum(int u,int fa){
for(int son : G[u]){
if(son == fa)continue;
sum(son,u);
cnt[u] += cnt[son];
}
}
int main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(false);
int n,k;
cin>>n>>k;
for(int u,v,i=1;i<n;++i){
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,0);
while(k--){
int u,v;
cin>>u>>v;
int ac = lca(u,v);
++cnt[u];
++cnt[v];
--cnt[ac];
--cnt[f[ac][0]];
}
sum(1,0); // 把差分数组转换为每个点的点权
for(int i=1;i<=n;++i){
if(cnt[i]>ans) ans = cnt[i];
}
cout<<ans<<endl;
}