Codeforces 1842I. Tenzing and Necklace
神仙题。本题解参考官方题解进行编写,并补充了最后比较关键的怎么调整
题目链接:I - Tenzing and Necklace
题目大意:给定一个环,环上有
开场的三个结论
首先钦定必须断开
- 设
x\lt y ,令A=S_x,B=S_y 。可以通过只对B_2,B_3,\dots,B_m 进行调整,使得对\forall 1\le i\le m ,均能满足A_i\le B_i ,且调整前后对应的花费不变。同理,也可以通过只对A_2,A_3,\dots,A_m 进行调整使其满足对应条件; - 在
A,B 满足A_i\le B_i 的基础上,可以进一步只对B_2,B_3,\dots,B_m 进行调整,使得对\forall 1\le i\lt m ,均能满足A_i\le B_i\le A_{i+1} 。同理,也可以通过只对A_2,A_3,\dots,A_m 进行调整使其满足对应条件; - 对任意
y\gt A_2 ,一定有S_{A_2} 对应的花费不比S_y 大。
前三个结论的证明
这几个结论的详细证明可参考官方题解,这里提供几个配图进行辅助说明,不感兴趣可以跳过。
结论一
设
取环上的某一段进行说明,若此时存在
我们把这种情况画出来,如图所示,红色代表方案
这时我们发现,把黄色部分的每对红蓝点进行对调,是依旧能符合同色点之间的距离要求的。而把所有蓝色点都变到对应红色点的位置上,正好能符合
在对应的花费上,由于
结论二
在
同样考虑第一次出现
原理和结论一的证明类似,可以发现将紫色线画出的几对红蓝点进行一一互换能保证满足条件。
结论三
对任意
找到最小的
按照箭头所示将蓝色断点全部移动到对应位置,即可完成调整。由于
找出某个 m 对应的最优解
以上几点实际上证明了这么一件事情,当我们需要求某个
具体的,我们先在原问题上找到必须断开第
根据之前的结论,一定存在一个最优解
令
在求解时,我们可以先令
对于 m 为其他值的情况
官方题解对这种情况的说明较为简略,我们分两部分进行补充说明。
全局最优解的序列长度调整
设我们一开始求出的原始解为
考虑第一个出现
此时对上图中绿色区间内的红蓝点集合进行对调,首先还是和之前的证明思路一样,对调后同色点间的距离仍然满足条件,且花费不会产生变化,所以可以通过一次调整使得最优解的序列长度
找到断边个数为 m-1 或 m+1 的最优解
于是我们只需要分别找到断边个数为
这个问题看上去可以采用 wqs 二分的思想做,即给所有边钦定一个固定的增量,按照之前我们求原始解的方式去求出一个对应条件下的最优解,并判断边条数是否满足要求,在此基础上进行二分。但这题存在一个致命的问题,就是不一定存在断边条数恰好符合要求的增量,比如样例一。
不过貌似是存在某种很牛的调整法求出对应初始解的,且 MagicalFlower 应该是采用的这种做法,感兴趣的大佬可自行了解。
下面介绍另一种做法。
对于
那么对绿色范围内的点进行调整即可做到让蓝点均落在对应红点的区域内。
对于
在求出两种情况的初始解后,再次采用之前分治的处理方式即可求出断边个数为
实现细节
由于一开始运行
在分治过程中由于
代码写得很丑,放出来污染一下各位看官的眼睛。具体实现还是参考 std 比较好(在官方英文题解里),std 在很多细节方面都做了非常好的简化。
#include<bits/stdc++.h>
using namespace std;
#define N 500050
#define LL long long
const LL inf=1e18;
int T,n,m,k,a[N];
int pre[N<<1],b[N],p[N];
LL g[N];
vector<int>A;
LL ans,res;
struct rua
{
int q[N],l,r;
LL f[N];
void init(){l=1,r=0;}
void popL(int x){while(l<=r && q[l]<x)l++;}
void popR(LL x){while(l<=r && x<=f[q[r]])r--;}
void ins(int x){q[++r]=x;}
int fr(){return q[l];}
bool emp(){return l>r;}
}Q;
void sol(vector<int>L,vector<int>R)
{
if(L[0]>R[0])return;
b[1]=L[0]+R[0]>>1;
int preL=b[1],preR=b[1],cur=preL;
Q.init();
Q.f[b[1]]=a[b[1]];
for(int i=2;i<=m;i++){
int l=L[i-1],r=R[i-1];
for(int j=l;j<=r;j++){
while(cur<j && cur<=preR){
Q.popR(Q.f[cur]);
Q.ins(cur);
cur++;
}
Q.popL(max(j-k,preL));
if(Q.emp())g[j]=inf;
else{
g[j]=Q.f[Q.fr()]+a[j];
pre[j+i]=Q.fr()+i-1;//这里在记录前驱的方案时进行了坐标平移,否则由于区间之间是有交的,会产生覆盖
}
}
for(int j=l;j<=r;j++)
Q.f[j]=g[j];
preL=l,preR=r,cur=preL;
Q.init();
}//以上是常规的 DP 部分,由于是环,所以接下来还要考虑开头和末尾之间的距离
while(cur<n+b[1] && cur<=preR){
Q.popR(Q.f[cur]);
Q.ins(cur);
cur++;
}
Q.popL(max(n+b[1]-k,preL));
if(Q.emp() || Q.f[Q.fr()]>=inf)return;
b[m]=Q.fr();
for(int i=m-1;i>1;i--)
b[i]=pre[b[i+1]+i+1]-i;//求方案时需要还原之前的平移
if(res>Q.f[Q.fr()]){
res=Q.f[Q.fr()];
for(int i=1;i<=m;i++)
p[i]=b[i];
if(p[1]==0){
for(int i=1;i<m;i++)
p[i]=p[i+1];
p[m]=n;
}
}// 因为求初始可行解的时候需要记录用来做求全局最优解时的依据,所以需要记录方案
vector<int>nxtL,nxtR;
for(int i=1;i<=m;i++){
nxtL.push_back(L[i-1]);
if(i>1)nxtR.push_back(b[i]);
else nxtR.push_back(b[i]-1);
}
sol(nxtL,nxtR);
nxtL.clear(),nxtR.clear();
for(int i=1;i<=m;i++){
if(i>1)nxtL.push_back(b[i]);
else nxtL.push_back(b[i]+1);
nxtR.push_back(R[i-1]);
}
sol(nxtL,nxtR);
nxtL.clear(),nxtR.clear();
}
void init()
{
A.clear();
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
a[0]=a[n];
Q.init();
Q.ins(0);
for(int i=1;i<=n;i++){
Q.popL(i-k);
Q.f[i]=Q.f[Q.fr()]+a[i];
pre[i]=Q.fr();
Q.popR(Q.f[i]);
Q.ins(i);
}
ans=Q.f[n];
int x=n;
while(x){
A.push_back(x);
x=pre[x];
}
A.push_back(0);
reverse(A.begin(),A.end());
vector<int>nxtL,nxtR;
for(int i=1;i<A.size();i++){
nxtL.push_back(A[i-1]);
nxtR.push_back(A[i]);
}
nxtL[0]=1;
res=inf;
m=nxtL.size();
sol(nxtL,nxtR);
ans=min(ans,res);
//以上是对边数为 m 这一情况的求解
nxtL.clear();
nxtR.clear();
nxtL.push_back(0);
nxtR.push_back(0);
for(int i=1;i<A.size();i++){
nxtL.push_back(A[i-1]);
nxtR.push_back(A[i]);
}
nxtL[1]=1;
res=inf,m++;
sol(nxtL,nxtR);//这里是先求一个 m+1 的初始可行解
if(res<inf){//如果存在,在此基础上进行求边数为 m+1 的最优解
nxtL.clear();
nxtR.clear();
for(int i=1;i<=m;i++){
nxtL.push_back(p[i-1]);
nxtR.push_back(p[i]);
}
nxtL[0]=1;
res=inf;
sol(nxtL,nxtR);
ans=min(ans,res);
}
if(((int)A.size()-2)*k>=n){//判断可不可以 m-1
nxtL.clear();
nxtR.clear();
nxtL.push_back(0);
nxtR.push_back(0);
for(int i=2;i<(int)A.size()-1;i++){
nxtL.push_back(A[i-1]);
nxtR.push_back(A[i]);
}
res=inf,m-=2;
sol(nxtL,nxtR);
if(res<inf){//同上,进一步求 m-1 的最优解
nxtL.clear();
nxtR.clear();
for(int i=1;i<=m;i++){
nxtL.push_back(p[i-1]);
nxtR.push_back(p[i]);
}
nxtL[0]=1;
res=inf;
sol(nxtL,nxtR);
ans=min(ans,res);
}
}
printf("%lld\n",ans);
nxtL.clear();
nxtR.clear();
}
int main()
{
scanf("%d",&T);
while(T--)init();
}