【JZOJ6011】天天爱跑步

description

长跑的目的不是更快,而是更强。 ——zjp’s blog
zjp最近迷上了长跑。为了防止被zjp强锋吹拂,小狗们决定躲到狗窝里去,现在已知有n条狗在一个二维平面直角坐标系的第一象限内。
狗是一种特殊的生物,每只在(x, y)的狗走一步只能到达(x + y, y),(x, y +x),(x − y, y),(x, y − x)这四个位置中的任意一个。并且任何时候,狗都不能在坐标轴上或在到达其它象限内的位置。
每个狗窝只能容纳一条狗,我们知道n个狗窝的坐标(也在第一象限内),每条狗不一定要到其对应编号的狗窝。
经过狗精密的计算发现,当所有狗到达狗窝的步数和最小时,狗是最安全的,尽管有的狗可能要走较多的步数。
现在,你只需要告诉他们:所有狗都到达狗窝的最小步数和。


analysis

  • 虚树+++哈希,这题有点神仙……但不难打

  • 先定义平面上的点向外移动指横或纵坐标变大,向里移动指横或纵坐标变小,而且容易知道移动可逆

  • 思考一下,(x,y)(x,y)(x,y)可以向外移动到(x+y,y),(x,y+x)(x+y,y),(x,y+x)(x+y,y),(x,y+x),但只能向里移动到两种中的一个(因为x−y≤0x-y≤0xy0y−x≤0y-x≤0yx0

  • 那么任意一个点都是向外扩展出两个点且向里扩展出一个点,把它们全部连起来,其实就是二叉树

  • 把所有点的横纵坐标除掉所有横纵坐标的gcdgcdgcd,此时所有的点都可从(1,1)(1,1)(1,1)移动得到(数据保证合法)

  • 因为移动是可逆的,所以狗窝可以视作和狗一样可以在二叉树上移动,那么把狗窝也算可移动的点

  • 观察(x−y,y),(x,y−x)(x-y,y),(x,y-x)(xy,y),(x,yx)并手动模拟点向里移动的过程,发现其实就是(x,y)(x,y)(x,y)两数求gcdgcdgcd的过程

  • x>yx>yx>y,具体就是,xxx变成xmodyxmodyxmodyyyy再变成ymodxymodxymodx,这样下去

  • 这样变化,一定是xxx先比yyy大,再yyyxxx大下去,在二叉树上的变化就是扭来扭去,这个会变化log⁡\loglog

  • 我们用哈希只把每个点和中间的转折点给建出来,边权即为x+yx+yx+y,这样建出一棵虚树,根为(1,1)(1,1)(1,1)

  • 把狗权值视作111,狗窝权值视作−1-11,那么类似树上差分,让111−1-11配对,同一子树里尽量配对最优

  • 这个一步步向上配对就可以,回溯时记录该子树内还有几个狗或窝没配对即可


code

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#define mod 3000007
#define MAXN 3000005
#define ll long long
#define reg register ll
#define fo(i,a,b) for (reg i=a;i<=b;++i)
#define fd(i,a,b) for (reg i=a;i>=b;--i)
#define rep(i,a,b) for (reg i=last[a][b];i;i=next[i][b])
#define O3 __attribute__((optimize("-O3")))

using namespace std;

vector<ll>f[MAXN][2];
ll tr[MAXN],depth[MAXN];
ll n,m,bz,tot,ans,total;

struct node
{
	ll x,y;
}a[MAXN];

struct point
{
	ll x,y;
}hash[mod+5];

O3 inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0' || '9'<ch){if (ch=='-')f=-1;ch=getchar();}
	while ('0'<=ch && ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
O3 inline ll add(ll x,ll y)
{
	ll now=((x%mod)*258280327+(y%mod)+1000000007)%mod;
	while (hash[now].x && (hash[now].x!=x || hash[now].y!=y))(now+=1)%=mod;
	if (hash[now].x==0)hash[now].x=x,hash[now].y=y,bz=0,depth[now]=x+y;
	return now;
}
O3 inline void link_tree(ll x,ll y)
{
	bz=1;
	ll now=add(x,y),las,temp;
	if (bz)return;
	while (x!=y)
	{
 		las=add(x,y);
		if (x>y)x%=y,temp=0;
		else y%=x,temp=1;
		if (x==0 || y==0)x=y=1;
		bz=1,now=add(x,y);
		f[now][temp].push_back(las);
		if (bz)return;
	}
}
O3 inline ll gcd(ll x,ll y)
{
	return !(x%y)?y:gcd(y,x%y);
}
O3 inline bool cmp(ll x,ll y)
{
	return depth[x]>depth[y];
}
O3 inline void dfs(ll x)
{
	ll cnt=0;
	fo(j,0,1)
	{
		if (!f[x][j].size())continue;
		fo(i,0,f[x][j].size()-1)dfs(f[x][j][i]);
		sort(f[x][j].begin(),f[x][j].end(),cmp);
		ll las=f[x][j][0],val=tr[las],bz=j?hash[x].x:hash[x].y;
		fo(i,1,f[x][j].size()-1)
		{
			ans+=abs(val)*((depth[las]-depth[f[x][j][i]])/bz);
			val+=tr[f[x][j][i]],las=f[x][j][i];
		}
		cnt+=val,ans+=abs(val)*((depth[las]-depth[x])/bz);
	}
	tr[x]+=cnt;
}
O3 int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	n=read();
	fo(i,1,n<<1)tot=gcd(tot,gcd(a[i].x=read(),a[i].y=read()));
	fo(i,1,n<<1)a[i].x/=tot,a[i].y/=tot,link_tree(a[i].x,a[i].y);
	fo(i,1,n)++tr[add(a[i].x,a[i].y)];
	fo(i,n+1,n<<1)--tr[add(a[i].x,a[i].y)];
	dfs(add(1,1));
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值