[POI2012] WYR-Leveling Ground

一、题目

点此看题

二、解法

考虑原数组的差分数组 \(c_i\),那么操作变成任意选取差分数组的两个位置,把其中一个增加 \(a/b\),把另一个减少 \(a/b\),要求最后把差分数组变成全 \(0\) 的最小步数。

单独考虑每个位置,是一个二元一次方程的形式:\(ax_i+by_i=c_i\),我们想要让 \(|x_i|+|y_i|\) 最小,只需要讨论 \(x_i/y_i\)\(0\) 右侧的最小值 \(/\) \(0\) 左侧的最大值这四种情况。

但是这样忽略了全局的限制 \(\sum x_i=0\),考虑调整法,如果 \(\sum x_i>0\),那么我们找到一个代价最小的位置,让它的 \(x_i\) 减少 \(\frac{b}{\gcd (a,b)}\)\(y_i\) 增加 \(\frac{a}{\gcd(a,b)}\),循环这个过程 \(\frac{\sum x_i}{b'}\) 次就得到了最优解(\(\sum x_i<0\) 同理)

这样做为什么是对的呢?因为每个位置的代价关于调整次数是单增的,所以贪心选取代价最小的位置就是最优的。

可以证明 \(|\sum x_i|\leq nb'\),这说明调整次数不会超过 \(n\) 次,所以时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 100005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,a,b,w[M],sx[M],sy[M];
struct node
{
	int x,c;
	bool operator < (const node &b) const
		{return c>b.c;}
};priority_queue<node> q;
int Abs(int x) {return x>0?x:-x;}
int f(int i)
{
	return Abs(sx[i]-b)+Abs(sy[i]+a)
	-Abs(sx[i])-Abs(sy[i]);
}
int exgcd(int a,int b,int &x,int &y)
{
	if(!b) {x=1;y=0;return a;}
	int d=exgcd(b,a%b,y,x);
	y-=x*(a/b);return d;
}
signed main()
{
	n=read();a=read();b=read();
	for(int i=1;i<=n;i++) w[i]=read();
	for(int i=++n;i;i--) w[i]=w[i]-w[i-1];
	int x=0,y=0,d=exgcd(a,b,x,y);a/=d;b/=d;
	for(int i=1;i<=n;i++)
	{
		int c=w[i];
		if(c%d) {puts("-1");return 0;}
		int X=((x*c/d)%b+b)%b,Y=(c/d-a*X)/b;
		sx[i]=X;sy[i]=Y;
		//
		X-=b;Y+=a;
		if(Abs(X)+Abs(Y)<Abs(sx[i])+Abs(sy[i]))
			sx[i]=X,sy[i]=Y;
		//
		Y=((y*c/d)%a+a)%a;X=(c/d-Y*b)/a;
		if(Abs(X)+Abs(Y)<Abs(sx[i])+Abs(sy[i]))
			sx[i]=X,sy[i]=Y;
		//
		Y-=a;X+=b;
		if(Abs(X)+Abs(Y)<Abs(sx[i])+Abs(sy[i]))
			sx[i]=X,sy[i]=Y;
	}
	int ans=0,s=0;
	for(int i=1;i<=n;i++) s+=sx[i];s/=b;
	if(s<0) s=-s,swap(a,b),swap(sx,sy);
	for(int i=1;i<=n;i++) q.push({i,f(i)});
	while(s--)
	{
		int x=q.top().x;q.pop();
		sx[x]-=b;sy[x]+=a;
		q.push({x,f(x)});
	}
	for(int i=1;i<=n;i++)
		ans+=Abs(sx[i])+Abs(sy[i]);
	printf("%lld\n",ans/2);
}
posted @ 2022-07-26 15:04  C202044zxy  阅读(197)  评论(0编辑  收藏  举报