• [POI2012] WYRLeveling 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);
    }
    
  • 相关阅读:
    Java 的类加载顺序
    单链表之一元多项式求和C++实现
    顺序线性表之大整数求和C++实现
    线性表之单链表C++实现
    NOIP 2009 潜伏者
    JDOJ 2782: 和之和
    浅谈前、中、后缀表达式
    CF13B Letter A
    洛谷 P5015 标题统计
    NOIP 2013 转圈游戏
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16521066.html
Copyright © 2020-2023  润新知