Codeforces 1276E. Four Stones
每年一更任务达成√
怎么有人老喜欢在讲课的题里塞 CF3500 啊,发怒。
题目链接:E - Four Stones
题目大意:初始有四块石头分别放在数轴的 \(a_1,a_2,a_3,a_4\) 四个整点上(可能在某一个点上有多块石头),每一次操作中你可以选择两块不同的石头 \((a_x,a_y)\) 将 \(a_x\) 变为 \(2a_y-a_x\) 。要求经过不超过 \(1000\) 次操作后他们能落在目标位置 \(b_1,b_2,b_3,b_4\) 上(只需保证位置的可重集相同),过程中坐标绝对值不能超出 \(10^{18}\)。初始坐标绝对值不超过 \(10^9\)。
一些起手的思考
很难不看出这是一个对称操作,于是可以联想到差分数组。进一步可以想到无论怎么操作最后的位置一定是初始位置的线性组合,意识到相对距离的 \(\gcd\) 估计不会变化,可以得出一些初始判断是否有解的方法。
这个结论的证明是容易的,不妨设 \(a\) 是有序的,可以得出对 \(\forall x\in [1,4]\),有 \(\gcd(a_2-a_1,a_3-a_2,a_4-a_3)=\gcd_{i=1}^4\{|a_x-a_i|\}\)。考虑某次操作是以 \(a_x\) 为中心进行一次对称,那么等式右边的内容是不会产生变化的,于是可以得出差分数组的 \(\gcd\) 始终不变。
那么可以有一个初始转换,设差分数组的 \(\gcd\) 为 \(d\),就一定有 \(a_i\equiv r\pmod d\)。且操作过程中这个条件也始终成立,所以 \(b\) 也需要满足同样的要求,否则无解。于是我们可以对所有的位置下标做一个 \(x\to \frac{x-r}{d}\) 的映射,相当于钦定了差分数组的 \(\gcd\) 为 \(1\)。
另一方面,这个操作是可逆的,而且是要从一个无规律的初始状态 \(S\) 到达另一个无规律的目标状态 \(T\)。于是可以考虑构建的一个有序的中间状态 \(X\),先求出 \(S\to X\) 和 \(T\to X\)(或者是 \(X\to S,T\)),再通过逆操作变成 \(S\to X\to T\)。
进一步分析,根据一开始的思考,这是跟相对距离有关的题,所以比较能保证的是相对位置而不是绝对位置。于是对于这个中间状态多半只能保证一个相对位置,需要考虑两个有序状态之间的平移转移。
中间状态的选择以及问题的拆分
经过一顿分析,应该能察觉到选取所有点均在 \(x\) 或 \(x+1\) 上的中间状态是合适的。这里可以发现 \(x\) 和 \(x+1\) 的数量也会有一个限制,其根本原因是每次操作不改变位置的奇偶性,所以奇偶数对应数量不变。
于是我们需要考虑的就是如何把一个状态变成只在 \([x,x+1]\) 内的状态,以及如何把 \([x,x+1]\) 平移到 \([y,y+1]\)。
如何压缩
压缩应该是比膨胀更容易想的。因为膨胀还要考虑怎么到指定目标,压缩只需要保证不断缩小极差就好了。
于是着重思考怎么通过一轮比较少次数的作,把极差缩小为原来的 \(k<1\) 倍。
设现在从左到右四个点的坐标依次为 \(l,x,y,r\) 且 \(r-l>1\),考虑 \(t=\frac{x-l}{r-l}\) 在不同的取值范围下一次操作可以带来的影响
- 若 \(t > 0.5\),考虑将 \(l\) 沿 \(x\) 对折,\(x\)变为新的左端点,\(l'\) 变为新的右端点,极差变为原来的 \(t\) 倍
- 若 \(t<0.5\),考虑将 \(l\) 沿 \(x\) 对折,这可以将极差变为原来的 \(1-t\) 倍
可以发现若 \(x\) 和某个端点的距离不是太近,这样的对折操作会比较有效。
很明显可以想到 \(y\) 和端点之间的距离也可以利用同样的方式分析,所以要考虑的就是 \(x,y\) 均离两端点很近的情况。换句话说就是设置一个阈值 \(B\in (0,0.5)\),对线段 \([l,r]\) 的 \([B,1-B]\) 部分(也就是区间 \([l+B(r-l),r-B(r-l)]\))内没有点的情况进行讨论。
稍微手玩一下几种情况,如下图所示

设 \(x,y\) 离最近端点的距离分别为 \(\Delta_x,\Delta_y\),可以发现一次操作可以令 \(\Delta_x \to \Delta_x+2\Delta_y\) 或 \(\Delta_y \to \Delta_y+2\Delta_x\)。于是每次对较小的那一方进行操作,直到其中一个 \(\Delta > B(r-l)\) 为止。这样就能保证一轮 \(\log\) 次操作把极差变为原来的 \(1-B\) 倍。
考虑设立合适的阈值。首先为了保证不会出现一次操作后把 \(x\) 从 \([0,B]\) 处扔到 \([1-B,1]\) 处的情况,就需要 \(3B\le 1-B\),即 \(B\le 0.25\)。
如此操作,可以通过大概 \(-\log_{1-B} 10^9\) 轮,每轮 \(\log_2 10^9\) 次操作完成压缩的过程,所以 \(B=0.25\) 是最优选项。
如何平移
在压缩状态下很明显可以通过不断蠕动来实现 \(1\) 单位长度的平移,但是次数会爆炸。
于是需要一个先膨胀,再平移,最后压缩的流程,且平移操作中尽量不改变他们的相对位置关系,这样就能使用膨胀的逆操作进行压缩,保证移动的总距离是可控的。
考虑当前的四个点是 \(l,x,y,r\),可以得到一个比较好的平移方式:\((l,x,y,r)\to (r,2r-y,2r-x,2r-l)\to (2r-l,x+2r-2l,y+2r-2l,3r-2l)\)。即每轮把所有其他点以最右点为中心对称一次,这样做两轮,如此操作可以让所有点整体平移 \(2(r-l)\) 的距离。
于是现在只需要考虑膨胀操作如何进行了,设第 \(i\) 轮膨胀操作之后的极差为 \(R_i\),我们只需把需要移动的距离分解为若干个 \(2R_i\) 的和,并在进行到对应轮次的时候执行平移操作即可(根据一开始的奇偶性结论,需要平移的距离肯定可以是偶数)。
为了方便进行逆操作,我们采取一个比较有规律的膨胀方式:\(y\) 沿 \(l\) 对称,\(x\) 沿 \(r\) 对称。这样膨胀后的极差会变为 \(2r-x-(2l-y)=2(r-l)+(y-x)\),一定会是原来极差的 \(2\) 到 \(3\) 倍。而逆操作就是 \(l\) 沿 \(x\) 对称,\(r\) 沿 \(y\) 对称。
综上我们就能通过 \(O(\log)\) 次操作完成平移。
代码实现
可以通过计算发现操作次数好像卡得非常死,但实际上是非常充裕的,因为压缩过程中每轮都跑满 \(\log\) 次是非常不容易的。本人的代码最后最多也就操作了 \(387\) 次。
实现上基本上没有太大难度,按照上述的流程直接模拟即可。可以在写完一个模块后先进行输出调试来确认代码的正确性,本人所有输出调试相关的代码全在前几行了。
下面给出完整代码。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL a[2][4],g[2],r,o;
vector<pair<LL,LL>>d[2];
void printA(int o){return;
printf("A:");
for(int i=0;i<4;i++)printf(" %lld",a[o][i]);
printf("\n");
}
void rua(int o,LL x,LL y)
{
if(x==y)return;
d[o].push_back(make_pair(x,y));
// printf("%lld %lld\n",x,y);
}
void gao(int o)
{
while(true){
sort(a[o],a[o]+4);
printA(o);
if(a[o][0]+g[0]==a[o][3])break;
LL R=a[o][3]-a[o][0];
LL d1=min(a[o][1]-a[o][0],a[o][3]-a[o][1]),p1=a[o][1],o1;
LL d2=min(a[o][2]-a[o][0],a[o][3]-a[o][2]),p2=a[o][2],o2;
if(d1>d2)swap(d1,d2),swap(p1,p2);
while(4ll*d2<R){
if(p1-a[o][0]<a[o][3]-p1)o1=a[o][0];else o1=a[o][3];
if(p2-a[o][0]<a[o][3]-p2)o2=a[o][0];else o2=a[o][3];
if(o1==o2){
rua(o,p1,o1),p1=2ll*o1-p1;
rua(o,p1,p2),p1=2ll*p2-p1;
}
else{
rua(o,p1,p2),p1=2ll*p2-p1;
rua(o,p1,o2),p1=2ll*o2-p1;
}
a[o][1]=p1,a[o][2]=p2;
if(a[o][1]>a[o][2])swap(a[o][1],a[o][2]);
d1=min(a[o][1]-a[o][0],a[o][3]-a[o][1]),p1=a[o][1];
d2=min(a[o][2]-a[o][0],a[o][3]-a[o][2]),p2=a[o][2];
if(d1>d2)swap(d1,d2),swap(p1,p2);
}
if(p2==a[o][1])rua(o,a[o][0],a[o][1]),a[o][0]=2ll*a[o][1]-a[o][0];
else rua(o,a[o][3],a[o][2]),a[o][3]=2ll*a[o][2]-a[o][3];
}
sort(a[o],a[o]+4);
while(((a[o][0]-r)/g[0])&1){
rua(o,a[o][0],a[o][0]+g[0]);
a[o][0]+=2ll*g[0];
sort(a[o],a[o]+4);
}
}
void sh(int o)
{
LL dis=(a[1-o][0]-a[o][0])/2,R=a[o][3]-a[o][0];
while(R<=dis){
printA(o);
LL nxtR=2ll*(a[o][3]-a[o][0])+a[o][2]-a[o][1];
if(nxtR>dis)break;
rua(o,a[o][2],a[o][0]),a[o][2]=2ll*a[o][0]-a[o][2];
rua(o,a[o][1],a[o][3]),a[o][1]=2ll*a[o][3]-a[o][1];
sort(a[o],a[o]+4);
R=a[o][3]-a[o][0];
}
while(dis){
printA(o);
while(dis>=R){
for(int i=0;i<2;i++){
for(int j=0;j<3;j++){
rua(o,a[o][j],a[o][3]);
a[o][j]=2ll*a[o][3]-a[o][j];
}
sort(a[o],a[o]+4);
}
dis-=R;
}
if(R==g[0])break;
rua(o,a[o][0],a[o][1]),a[o][0]=2ll*a[o][1]-a[o][0];
rua(o,a[o][3],a[o][2]),a[o][3]=2ll*a[o][2]-a[o][3];
sort(a[o],a[o]+4);
R=a[o][3]-a[o][0];
}
printA(o);
}
int main()
{
for(int i=0;i<2;i++)for(int j=0;j<4;j++)
scanf("%lld",&a[i][j]),g[i]=__gcd(g[i],abs(a[i][j]-a[i][0]));
if(g[0]!=g[1])return printf("-1\n"),0;
if(g[0]==0)return printf("%d\n",a[1][0]==a[0][0]?0:-1),0;
r=(a[0][0]%g[0]+g[0])%g[0];
for(int i=0;i<2;i++)for(int j=0;j<4;j++){
if((a[i][j]-r)%g[0])return printf("-1\n"),0;
o+=(1-i*2)*(((a[i][j]-r)/g[0])&1);
}
if(o)return printf("-1\n"),0;
gao(0),gao(1);
if(a[0][0]<a[1][0])sh(0);
else sh(1);
printf("%d\n",(int)d[0].size()+(int)d[1].size());
for(auto pi:d[0])printf("%lld %lld\n",pi.first,pi.second);
reverse(d[1].begin(),d[1].end());
for(auto pi:d[1])printf("%lld %lld\n",2ll*pi.second-pi.first,pi.second);
}
交了四次才过,而且没过的原因都比较搞笑,感觉数据水炸了。


浙公网安备 33010602011771号