又名:如何将简单的证明变得又臭又长且仅仅是看上去高级一点。
1 分析
定义 1.1
定义一个函数
Sum(l,r,a)(l≤r,l,r∈[1,n]),满足:
Sum(l,r,a)=∑i=lrai
定义 1.2
s=nSum(1,n,a)
定理 1.1
对于区间
[l,r],若满足
Sum(l,r,a)=(r−l+1)×s,则至多
⌈k−1r−l⌉ 次分配操作使得
∀i∈[l,r],ai=s 且其余数值不变。
证明 1.1
进行操作:
对于
∀i∈[1,n],ai→ai−s。
此时原定理等价于:
对于区间
[l,r],若满足
Sum(l,r,a)=0,则至多
⌈k−1r−l⌉ 次分配操作使得
∀i∈[l,r],ai=0 且其余数值不变。
定义 1.1.1
定义一个函数
f(l,r,x,a,t) 表示
是否满足至多
t 次操作使得
al=x,∀i∈[l+1,r],ai=0 且其余数值不变。
特殊的,
f(l,l,x,a,0)=[al=x]。
定理成立等价于若满足
f(l,r,0,a,⌈k−1r−l⌉)=1。
考虑任意情况下的
f(l,r,x,a,t)。
- r−l+1≤k
f(l,r,x,a,t)=[x=Sum(l,r,a)][t≥1]
具体的,若满足
x=Sum(l,r,a),则对
[l,l+k−1] 进行一次分配操作。
分配后的数值满足:
∀i∈[l+1,r],ai=0
区间
[r+1,l+k−1] 中的数不变。
- Sum(l,l+k−1,a)≥x
定义 1.1.2
∀i∈[l+1,l+k−1),bi=0
bl+k−1=Sum(l,l+k−1,a)−x
∀i∈[1,l)∧(l+k−1,n],bi=ai
f(l,r,x,a,t)=f(l+k−1,r,0,b,t−1)
具体的,先对
[l,l+k−1] 进行一次分配操作。
分配后的数值满足:
∀i∈[l+1,l+k−1),ai=0
al+k−1=Sum(l,l+k−1,a)−x
此时再对后面的部分进行操作,等价于
f(l+k−1,r,0,b)。
- Sum(l,l+k−1,a)<x
f(l,r,x,a,t)=f(l+k−1,r,al+k−1+x−Sum(l,l+k−1,a),a,t−1)
具体的,先对
[l+k−1,r] 进行分配操作。
分配后的数值满足:
al+k−1=al+k−1+x−Sum(l,l+k−1,a),a)
∀i∈(l+k−1,r],ai=0
能否完成等价于
f(l+k−1,r,al+k−1+x−Sum(l,l+k−1,a),a,t−1)。
此时对
[l,l+k−1] 进行一次分配操作。
分配后的数值满足:
∀i∈(l,l+k−1],ai=0
考虑待证命题
f(l,r,0,a,⌈k−1r−l⌉)=1。
性质 1.1.1
对于情况
2,3 将
f(l,r,x,a,t) 归纳为
f(l′,r′,x′,a′,t′) 时,满足
r′−l′=r−l−(k−1)。
性质 1.1.2
对于情况
2,3 将
f(l,r,x,a,t) 归纳为
f(l′,r′,x′,a′,t′) 时,满足
Sum(l′,r′,a′)=Sum(l′,r′,a)。
性质 1.1.3
对于情况
2,3 将
f(l,r,x,a,t) 归纳为
f(l′,r′,x′,a′,t′) 时,满足
x′+Sum(l,r,a)−Sum(l′,r′,a)=x。
由性质 1.1.1 可得,在变成情况 1 前归纳次数不超过
⌈k−1r−l⌉−1,因此在得到情况 1 时
[t≥1] 总是满足。
由性质 1.1.2,1.1.3 可得,由于初始状态
Sum(l,r,a)=x=0,每次归纳后得到的
f(l′,r′,x′,a′,t′) 总是满足
Sum(l′,r′,a′)=Sum(l′,r′,a)=x′+Sum(l,r,a)−x=x′,因此在得到情况 1 时
[x=Sum(l,r,a)] 总是满足。
总上,
f(l,r,0,a,⌈k−1r−l⌉)=1,原命题得证。
定理 1.2
对于相邻的两个区间
[l1,r1],[l2,r2](1≤l1≤r1≤1n,1≤l2≤r2≤n,l2=r1+1),满足
Sum(l1,r1,a)=(r1−l1+1)×s
Sum(l2,r2,a)=(r2−l2+1)×s
,若分别操作的次数小于将两个区间合并为一个区间操作的次数,当且仅当两个区间的
len−1 为
k−1 的倍数。
形式化的,若满足
⌈k−1len1−1⌉+⌈k−1len2−1⌉<⌈k−1len1+len2−1⌉
当且仅当
len1−1≡0(modk−1)
len2−1≡0(modk−1)
证明 1.2
定义 1.2.1
定义
r1,r2 表示区间长度除以
k−1 的余数,即:
len1−1≡r1(modk−1)
len2−1≡r2(modk−1)
可以得到
len1+len2−1≡r1+r2+1(modk−1)
原定理等价于:
⌈k−1r1⌉+⌈k−1r2⌉<⌈k−1r1+r2+1⌉
由定义可得
r1,r2∈[0,k−1)
r1+r2+1∈[1,2k−2)
因此
1≤⌈k−1r1+r2+1⌉≤2
分两种情况讨论。
- ⌈k−1r1+r2+1⌉=1
此时若
⌈k−1r1⌉+⌈k−1r2⌉<⌈k−1r1+r2+1⌉,当且仅当
⌈k−1r1⌉=⌈k−1r2⌉=0,即
r1=r2=0,即
len1−1≡0(modk−1)
len2−1≡0(modk−1)
- ⌈k−1r1+r2+1⌉=2
此时若
⌈k−1r1⌉+⌈k−1r2⌉<⌈k−1r1+r2+1⌉,即
⌈k−1r1⌉+⌈k−1r2⌉≤1,则总是满足
r1=0 或
r2=0。不失一般性的,设
r1=0,则
r1+r2+1=r2+1∈[1,k)≤k−1,因此
⌈k−1r1+r2+1⌉=1,与条件不符,因此此情况不可能发生。
总上,原定理成立。
对原问题的分析
将原数组分为若干个区间
[li,ri],满足
Sum(li,ri,a)=(ri−li+1)×s
答案为
∑i⌈k−1ri−li⌉
由定理 1.2 可得,可以一直合并区间直到满足
len−1≡0(modk−1)
此时答案为
∑ik−1ri−li=k−1n−∑ik−11
因此答案与区间数量成负相关,贪心地尽可能多的划分区间即可。
Sum(j,i,a)=(i−j+1)×s
i−j≡0(modk−1)
证明 1.3
考虑反证。
Sum(p,i,a)=(i−p+1)×s
i−p≡0(modk−1)
则
Sum(p,j−1,a)=(j−1−p+1)×s
j−1−p≡0(modk−1)
此时可以划分出区间
[p,j−1]。
因此原方案中对于
[1,p−1] 和
[i+1,n] 部分采用与现方案相同的划分方式,而原方案中
[p,i] 可以划分为两个区间,现方案中只划分为了一个区间。与现方案比原方案更优的假设不符。
因此划分区间
[i,j] 最优。
2 实现
考虑如何实现上述贪心过程找到每个
i 匹配的端点
j。
进行操作:
对于
∀i∈[1,n],ai→ai−s。
Sum(j,i,a)=0
i≡j(modk−1)
条件一可以重新刻画为
Sum(1,j−1,a)=Sum(1,i,a)
因此维护二元组
(Sum(1,i,a),imodk−1),
j 即为最大的下标,满足
(Sum(1,j−1,a),jmodk−1)=(Sum(1,i,a),imodk−1)
使用 map 记录每个二元组最近一次出现的位置即可,时间复杂度
O(nlogn)。
由于原题中数组成环,最后枚举起点,可以实现
O(n2) 的算法。
考虑倍增加速,计算
fi,j 表示以
i 为起点划分
2j 个区间的端点,可以将原算法改进为
O(nlogn)。
输出方案参照证明 1.1 中将问题化归的方式递归即可,时间复杂度
O(n)。
总时间复杂度
O(nlogn)。
3 代码
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
int Tim=1;
const int N=2e5+5;
int n,k;
int a[N];
int s;
int nxt[N][20];
map<pair<int,int>,int> vis;
void print(int l,int r,int x){
if(l==r){
return;
}
if(r-l+1<=k){
a[(l-1)%n+1]=x;
for(int i=l+1;i<=r;i++){
a[(i-1)%n+1]=0;
}
cout<<(l-1)%n<<' ';
for(int i=l;i<l+k;i++){
cout<<a[(i-1)%n+1]+s<<' ';
}
cout<<'\n';
return;
}
int sum=0;
for(int i=l;i<l+k;i++){
sum+=a[(i-1)%n+1];
}
if(sum>=x){
a[(l-1)%n+1]=x;
for(int i=l+1;i<l+k-1;i++){
a[(i-1)%n+1]=0;
}
a[(l+k-1-1)%n+1]=sum-x;
cout<<(l-1)%n<<' ';
for(int i=l;i<l+k;i++){
cout<<a[(i-1)%n+1]+s<<' ';
}
cout<<'\n';
print(l+k-1,r,0);
return;
}
print(l+k-1,r,a[(l+k-1-1)%n+1]+x-sum);
a[(l-1)%n+1]=x;
for(int i=l+1;i<l+k;i++){
a[(i-1)%n+1]=0;
}
cout<<(l-1)%n<<' ';
for(int i=l;i<l+k;i++){
cout<<a[(i-1)%n+1]+s<<' ';
}
cout<<'\n';
}
void Solve(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
s+=a[i];
}
s/=n;
for(int i=1;i<=n;i++){
a[i]-=s;
a[i+n]=a[i];
}
for(int i=1,sum=0;i<=2*n;i++){
sum+=a[i];
nxt[i][0]=vis[{sum,(i-1)%(k-1)}];
vis[{sum,i%(k-1)}]=i;
for(int j=1;j<=18;j++){
nxt[i][j]=nxt[nxt[i][j-1]][j-1];
}
}
int ans=(n-1+k-1-1)/(k-1),pos=-1;
for(int r=n+1;r<=2*n;r++){
int l=r,tot=0;
for(int j=18;j>=0;j--){
if(nxt[l][j]>=r-n){
l=nxt[l][j];
tot+=(1<<j);
}
}
int len=r-l;
int res=(len-tot)/(k-1)+(n-len+k-1-1)/(k-1);
if(res<ans){
ans=res;
pos=r;
}
}
cout<<ans<<'\n';
if(pos==-1){
print(1,n,0);
return;
}
int r=pos;
while(r>pos-n){
int l=max(pos-n,nxt[r][0]);
print(l+1,r,0);
r=l;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
while(Tim--){
Solve();
}
return 0;
}