差分,即序列中相邻两数之差,本文章讲解几个经典例题,帮助各位理解差分在算法竞赛中的应用,若遇到新的差分思维题,在此更新。
例题1:堆积木问题
题目来源:
https://www.luogu.com.cn/problem/P5019
题意:
给定一个序列
a
[
n
]
a[n]
a[n],每次操作可使区间
[
l
,
r
]
[l,r]
[l,r]内各个数字减一,问最少操作数。
思路:
两个坑之间的相对"深度"差决定此区间的操作数,以第一个坑的深度作为基准值,即可得到绝对"深度",故而考虑差分。
for(int i=2;i<=n;i++) if(a[i]>a[i-1]) ans+=a[i]-a[i-1];
cout<<ans+a[1];
类似地:
https://codeforces.com/contest/1700/problem/C
此题的操作数也是差分相加减,修改前缀,加上基准值即可。
例题2:差分空间问题
题目来源:
https://codeforces.com/contest/1705/problem/D
题意:
给定01序列s,当
s
[
i
−
1
]
≠
s
[
i
+
1
]
s[i-1]\neq s[i+1]
s[i−1]=s[i+1]时,
s
[
i
]
s[i]
s[i]取反,问s变成t的最少操作数。
思路:
直接在数组中找规律难度较大,又因为
s
[
i
]
s[i]
s[i]取反后两边差分值会变化,所以可以在差分数组中找规律。我们用数组构造差分空间,(注意,由于是01序列,差分是以模2意义下的),不难发现我们每次取反在差分数组中即为交换0,1。所以比较差分数组中0和1的个数及距离即可。
code:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define LL long long
#define PII pair<int, int>
#define PDD pair<double,double>
using namespace std;
typedef long long readtype;
const int maxn = 1e5 + 5;
const int inf = INT_MAX;
const LL mod = 1e9 + 7;
const double eps=1e-6;
LL T;
inline readtype read(){
readtype X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
inline int double_cmp(double a,double b)
{
if(fabs(a-b)<eps) return 0;
if(a<b) return -1;
return 1;
}
int main(){
T = read();
while(T--)
{
int n = read();
int cnt_a=0,cnt_b=0;
string s1,s2;
cin>>s1;
cin>>s2;
s1='$' +s1;
s2='$' +s2;
if(s1[1]!=s2[1] || s1[n]!=s2[n])
{
cout<<-1<<endl;
continue;
}
vector<int> a(n+1,0);
vector<int> b(n+1,0);
vector<int> diff_a(n+1,0);
vector<int> diff_b(n+1,0);
for(int i=1;i<=n;i++)
{
a[i]=s1[i]-'0';
diff_a[i]=a[i]^a[i-1];
b[i]=s2[i]-'0';
diff_b[i]=b[i]^b[i-1];
if(i!=1)
{
cnt_a+=diff_a[i];
cnt_b+=diff_b[i];
}
}
if(cnt_a==cnt_b)
{
LL res=0;
int i=2,j=2;
while(i<=n && j<=n)
{
if(diff_b[j] && diff_a[i])
{
res+=abs(i-j);
i++;
j++;
}
else if(diff_b[j] && !diff_a[i]) i++;
else if(!diff_b[j] && diff_a[i]) j++;
else
{
i++;
j++;
}
}
cout<<res<<endl;
}
else
{
cout<<-1<<endl;
}
}
return 0;
}
例题3:差分空间问题
题目来源:https://ac.nowcoder.com/acm/contest/38727/J
题意:
给定一个012的环,若顺时针方向两数之差在模3的意义下等于1,则减数可加上1,问能否使得该环中各个数相等。
思路:
模拟的时候可以发现,各个数之间的差值比较重要,而难以找到规律。故而构造差分数组,在差分数组中找规律。推导过程略,各位可以自行推导,注意把握原数组变化时对应的差分数组如何变化。最终结论就是差分数组中1的个数要不少于2,而0可以忽略掉。
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define LL long long
#define PII pair<int, int>
#define PDD pair<double,double>
using namespace std;
const int maxn = 1e5 + 5;
const int inf = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
int T;
inline int read(){
int X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
int main(){
T = read();
while(T--)
{
int n = read();
vector<int> a(n+1);
for(int i = 1; i <= n; i++)
a[i]=read();
vector<int> b;
for(int i=2;i<=n;i++)
b.pb((a[i]-a[i-1]+3)%3);
b.pb((a[1]-a[n]+3)%3);
int cnta=0,cntb=0;
for(auto num:b)
{
if(num==1)
cnta++;
else if(num==2)
cntb++;
}
if(cnta>=cntb)
puts("Yes");
else
puts("No");
}
return 0;
}