• 「POI2012」Leveling Ground


    判断有无解,显然就是判断是否 (forall i in [1,n] gcd(a,b) | h_i)。裴蜀定理随便证证就好了。

    然后对于一个序列问题,进行区间加减操作,一个非常简单的套路就是做出其差分数组,然后单点修改即可。为了保证最后一个数修改后为 (0),所以我们多加一个数 (h_{n+1}=-h_{n})

    考虑只有两种操作 (a,b)。先想一想一个数的时候我们应该怎么去处理。对于一个数 (s),对其进行操作,设未知数 (x,y) 表示 (a) 操作进行次数和 (b) 操作进行次数(负数就相当于减 (a)(b))相当于解一个等式:

    [ax+by=s ]

    显然这个问题可以直接用拓展欧几里得算法去做。不赘述。

    于是想让 (|x|+|y|) 最小。这个可以随便分类讨论一发,判断一下 (x,y) 分别取极值的时候就行了吧。或者说你也可以根据方程通解(看下面!)三分也行:

    [egin{cases} x=x_0 + k imes dfrac{b}{gcd(a,b)} \ y=y_0 + k imes dfrac{a}{gcd(a,b)} end{cases} ]

    找到这些解之后,我们还要考虑让:

    • 所有数变为 (0)
    • 答案最小。

    然而我们之前的做法并不能满足 (n >1) 的情况。对于这种情况的通用方法就是反悔贪心。

    考虑到之前那个方程的性质,(x,y) 一定满足某种关系(看通解也看的出来吧)。当前的堆里面储存上当前的方程的解的贡献减去修改一次 (x,y) 之后的贡献。

    为了让其满足所有数变为 (0),更改的时候考虑修改掉其 (x,y)。假设我们现在的 (sum x >0),我们取出操作次数最小的那个数,根据之前的方程通解增大 (x),减小 (y) 即可,然后再放回去。

    这样,当 (sum x) 等于 (0) 的时候,答案就出来了,即为:

    [sum_{i=1}^{n+1} dfrac{x_i+y_i}{2} ]

    注意要除以 (2),而且要输出答案的时候除。

    反悔贪心博客

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
    void exgcd(LL &x,LL &y,LL a,LL b)
    {
    	if(!b)
    	{
    		x=1,y=0;
    		return ;
    	}
    	exgcd(x,y,b,a%b);
    	LL tmp=x;
    	x=y;
    	y=tmp-a/b*y;
    }
    LL Abs(LL x){return x>0?x:-x;}
    struct Answer{
    	LL x,y;
    	Answer(LL X=0,LL Y=0){x=X,y=Y;}
    	bool operator < (Answer ano) const {return Abs(x)+Abs(y)<Abs(ano.x)+Abs(ano.y);}
    }p[100005];
    struct Factor{
    	LL val,pos;
    	Factor(LL V=0,LL P=0){val=V,pos=P;}
    	bool operator < (Factor ano) const {return val>ano.val;}
    };
    priority_queue<Factor> Q;
    LL n,a,b,s[100005];
    int main(){
    	scanf("%lld %lld %lld",&n,&a,&b);
    	for(LL i=1;i<=n;++i)	scanf("%lld",&s[i]);
    	LL d=gcd(a,b);
    	for(LL i=1;i<=n;++i)	if(s[i]%d)	return puts("-1")&0;
    	++n;
    	for(LL i=n;i;--i)	s[i]=s[i]-s[i-1];
    	LL x,y;
    	exgcd(x,y,a,b);
    	a/=d,b/=d;
    //	printf("%lld %lld %lld %lld
    ",x,y,a,b);
    //	for(LL i=1;i<=n;++i)	printf("%lld ",s[i]);
    //	puts("");
    	for(LL i=1;i<=n;++i)
    	{
    		LL sx=(x*s[i]/d%b+b)%b,sy=(s[i]/d-sx*a)/b;
    		p[i]=Answer(sx,sy);
    		sx-=b,sy+=a;
    		if(Answer(sx,sy)<p[i])	p[i]=Answer(sx,sy);
    		sy=(y*s[i]/d%a+a)%a,sx=(s[i]/d-sy*b)/a;
    		if(Answer(sx,sy)<p[i])	p[i]=Answer(sx,sy);
    		sx+=b,sy-=a;
    		if(Answer(sx,sy)<p[i])	p[i]=Answer(sx,sy);
    	}
    //	for(LL i=1;i<=n;++i)	printf("%lld %lld
    ",p[i].x,p[i].y);
    	LL lc=0;
    	for(LL i=1;i<=n;++i)	lc+=p[i].x;
    	lc/=b;
    	if(lc<0)
    	{
    		lc=-lc;
    		swap(a,b);
    		for(LL i=1;i<=n;++i)	swap(p[i].x,p[i].y);
    	}
    	for(LL i=1;i<=n;++i)	Q.push(Factor(Abs(p[i].x-b)+Abs(p[i].y+a)-Abs(p[i].x)-Abs(p[i].y),i));
    	for(LL i=1;i<=lc;++i)
    	{
    		Factor k=Q.top();
    		Q.pop();
    		LL now=k.pos;
    		p[now].x-=b,p[now].y+=a;
    		Q.push(Factor(Abs(p[now].x-b)+Abs(p[now].y+a)-Abs(p[now].x)-Abs(p[now].y),now));
    	}
    	LL ans=0;
    	for(LL i=1;i<=n;++i)	ans+=(Abs(p[i].x)+Abs(p[i].y));
    	printf("%lld",ans/2);
    	return 0;
    }
    
  • 相关阅读:
    数组作为方法参数时的一些意外情况
    pack://application:,,,/
    WPF 使用WinForm Chart控件
    WPF 后台绑定样式
    在转换为 UTC 时大于 DateTime.MaxValue 或小于 DateTime.MinValue 的 DateTime 值无法系列化为 JSON
    LINQ_to_SQL语法及实例大全
    C#编码好习惯,献给所有热爱c#的同学
    C#中OpenFileDialog的使用
    NET 2.0(C#)调用ffmpeg处理视频的方法
    SQLite Mysql 模糊查找(like)
  • 原文地址:https://www.cnblogs.com/amagaisite/p/13922595.html
Copyright © 2020-2023  润新知