[ARC122C] Calculator

前言

现在是22:10,离比赛结束过去了10分钟。

题目

AtCoder

题目大意:

给一个目标正整数 \(N\),你初始有两个数 \(x=0,y=0\) ,有四种操作:

  1. \(x\) 替换为 \(x+1\)
  2. \(y\) 替换为 \(y+1\)
  3. \(x\) 替换为 \(x+y\)
  4. \(y\) 替换为 \(x+y\)

要求在 \(130\) 次操作内将 \(x\) 变为目标数 \(N\)

\(1\le N\le 10^{18}.\)

讲解

先放一个大数镇楼:679891637638612257。

我们先随便玩一玩发现,为了达到较大的目标状态,我们肯定需要反复使用操作 \(3,4\)

我反复用的操作顺序是 \(4,3\)

假设初始状态为 \((1,1)\),那么之后的状态为:\((1,2),(3,2),(3,5),(8,5),(8,13)...\)

很显然的斐波拉契数列。

自然我们想到将 \(N\) 拆分为多个斐波拉契数相加,在适当的时机加上 \((1,1)\),当然根据斐波拉契数位置的奇偶性不同,我们也可能要加上 \((2,1)\)

也就是说我们将 \(x\)\(y\) 也拆分为多个斐波拉契数。

比如我们看这个例子:\(N=11=8+3\)

\((0,0;0,0),(0,1;0,1),(0,1;0,2),(0,3;0,2),(1,3;1,2),(1,3;2,5),(3,8;2,5).\)

分号前为 \(x\),之后为 \(y\)

最终状态:\(x=8+3=11,y=2+5=7.\)

到这里,我们的思路已经基本成型,但是这样还不够,细节上还需要优化,不然会被卡。

比如我们并不需要加上 \((1,1)\),因为 \((0,1)\) 经过一轮 \(4,3\) 后可以变成 \((1,1)\)\((1,0)\) 经过一轮 \(4,3\) 后可以变成 \((2,1)\)。单独加 \((1,1)\)\((2,1)\) 显然没有一轮 \(4,3\) 操作优,因为 \(4,3\) 操作是共用的。

详见代码。

代码

//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;

typedef long long LL;
const int MAXN = 100005;
LL n,f[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int c[MAXN],tot,ans[MAXN],anstot;

void Add(int x){ans[++anstot] = x;}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read();
	f[1] = 1; f[2] = 2;
	for(int i = 3;i <= 88;++ i) f[i] = f[i-1] + f[i-2];
	for(int i = 88;i >= 1;-- i) if(n >= f[i]) c[++tot] = i,n -= f[i];
	for(int i = 1;i <= tot;++ i)
	{
		if(i == tot)
		{
			if(c[tot] & 1) Add(2);
			else Add(1);
			for(int j = 1;j <= (c[tot]+1) / 2;++ j) Add(4),Add(3);
		}
		else
		{
			if(c[i] & 1) Add(2);
			else Add(1);
			for(int j = 1;j <= ((c[i]+1)/2-(c[i+1]+1)/2);++ j) Add(4),Add(3);
		}
	}
	Put(anstot,'\n');
	LL x = 0,y = 0;
	for(int i = 1;i <= anstot;++ i) 
	{
		Put(ans[i],'\n');
		if(ans[i] == 1) x++;
		else if(ans[i] == 2) y++;
		else if(ans[i] == 3) x += y;
		else y += x;
	}
	Put(anstot,'\n');
	return 0;
}
//679891637638612257

后记

赛时因为造了一个大于 \(10^{18}\) 的数,操作数达到 \(132\) 而怀疑人生。

不过赛时人确实有点麻了,想什么都不清楚,迷迷糊糊地就开始敲代码,结果WA到怀疑人生。

倦了。

posted @ 2021-06-12 22:39  皮皮刘  阅读(51)  评论(0编辑  收藏  举报