AMPPZ 2011 Ants(Looking for a challenge?)

Ants

在这里插入图片描述

题意

有一棵树,从0时刻开始,有两只蚂蚁分别从树的左右两侧出发遍历这棵树。左蚂蚁从一条边的下方爬到上方需要2秒,从上方爬到下方需要1秒,右蚂蚁的爬行速度是左蚂蚁的两倍。蚂蚁如果碰到对方就会立刻调头继续爬行,蚂蚁如果返回地面也会调头继续爬行。计算两只蚂蚁第二次相遇的时间。

输入

t 组数据,每组数据包含一个整数 n 代表边数和一个 n/2 个字符的字符串。这个字符串本质上是一个大十六进制数,如果转化为二进制数就可以表示这棵树的信息,具体表示方法为假设右蚂蚁不动的情况下左蚂蚁的运动轨迹,当当前位置为1时表示蚂蚁向上爬,否则表示蚂蚁向下爬。

1 ≤ t ≤ 1000 ,   2 ≤ n ≤ 100 , 000 , 000 1 \le t \le 1000,\ 2 \le n \le 100,000,000 1t1000, 2n100,000,000 输入数据量不超过 50 MB。注意空间限制为 6 MB,远远小于输入数据量。

输出

输出一个最简分式 p/q ,表示答案。如果答案为整数,q = 1

样例

1
28
fb1da30d1b7230

282/5

样例解释

转化为二进制即为上图的树。

1111 1011 0001 1101 1010 0011 0000 1101 0001 1011 0111 0010 0011 0000

解法

首先我们观察到,显然,左边的蚂蚁将在第一次相遇后首先回到地面,因为右蚂蚁需要向上爬的比率总是较高。

于是就可以轻松嘴出算法的大概流程:

  1. 读入树的大小。于是我们就知道了有n条上升的边和n条下降的边。
  2. 当读入树的信息时,我们就可以计算出左蚂蚁的行动轨迹和需要的时间。由于上升和下降边的个数是已知的,所以右蚂蚁走到当前位置的时间也是可以算出来的。所以当两只蚂蚁第一次相遇的时候,我们就可以知道。
  3. 当两只蚂蚁第一次相遇后,按照右蚂蚁的行动轨迹继续模拟,直到左蚂蚁回到地面。左蚂蚁回到地面的时间是可以计算出来的,而根据我们先前的观察,此时右蚂蚁还没有回到地面。
  4. 然后继续模拟右蚂蚁的运动,直到两只蚂蚁再次相遇。

听上去真的非常轻松啊?现在来形式化地描述一下这个流程。

用实数 a a a 表示左蚂蚁目前经过了多少条边,用实数 k k k 表示左蚂蚁目前的高度,用 t g t_g tg 表示左蚂蚁向上爬需要的时间, t d t_d td 表示左蚂蚁向下爬需要的时间。于是有到达某个点的总时间的公式:
[ a , k , t g , t d ] = a + k 2 t g + a − k 2 t d [a,k,t_g,t_d] = \frac{a+k}{2}t_g+\frac{a-k}{2}t_d [a,k,tg,td]=2a+ktg+2aktd
于是就可以列出两只蚂蚁第一次相遇的方程
[ a 1 , k 1 , 2 , 1 ] = [ 2 n − a 1 , k , 1 , 1 2 ] [a_1,k_1,2,1]=[2n-a_1,k,1,\frac{1}{2}] [a1,k1,2,1]=[2na1,k,1,21]
如前所述,我们可以在读入的时候判断相遇点是否会发生在当前这条边上。当蚂蚁处在节点 ( a , k ) (a,k) (a,k) 的时候,令 b = 1 b=1 b=1 表示这是一条上升边, b = − 1 b=-1 b=1 表示这是一条下降边,于是上式就满足:
a 1 = a + ε , k 1 = k + b ε , ε = 6 n − 9 a − k 9 + b a_1=a+\varepsilon,k_1=k+b\varepsilon,\\ \varepsilon = \frac{6n-9a-k}{9+b} a1=a+ε,k1=k+bε,ε=9+b6n9ak
如果 0 ≤ ε < 1 0 \le \varepsilon < 1 0ε<1, 我们就找到了第一个相遇的位置。于是就可以求出相遇的时间:
t 1 = [ a 1 , k 1 , 2 , 1 ] = 3 a 1 + k 1 2 t_1=[a_1,k_1,2,1]=\frac{3a_1+k_1}{2} t1=[a1,k1,2,1]=23a1+k1
然后,我们让左蚂蚁回头,它将在 [ a 1 , − k 1 , 2 , 1 ] = t 1 − k 1 [a_1,-k_1,2,1]=t_1-k_1 [a1,k1,2,1]=t1k1 秒回到树根。此时,右蚂蚁的位置为:
[ 2 n − a 1 , − k 1 , 1 , 1 2 ] = t 1 − k 1 2 [2n-a_1,-k_1,1,\frac{1}{2}]=t_1-\frac{k_1}{2} [2na1,k1,1,21]=t12k1
于是我们可以知道,左蚂蚁的确会比右蚂蚁回到树根。

我们继续模拟右蚂蚁的运动,右蚂蚁从 ( a ′ , k ′ ) (a',k') (a,k) 开始,到 ( a 2 , k 2 ) (a_2,k_2) (a2,k2) 的运动满足:
a 2 = a ′ + ε ′ , k 2 = k ′ + b ′ ε ′ . [ a 1 , − k 1 , 2 , 1 ] + [ 2 n − a 2 , k 2 , 2 , 1 ] = [ a 2 − a 1 , k 2 − k 1 , 1 , 1 2 ] , ε ′ = 12 n − 9 ( a ′ − a 1 ) + ( k ′ − k 1 ) 9 − b ′ a_2=a'+\varepsilon', k_2=k'+b'\varepsilon'.\\ [a_1,-k_1,2,1]+[2n-a_2,k_2,2,1]=[a_2-a_1,k_2-k_1,1,\frac{1}{2}],\\ \varepsilon'=\frac{12n-9(a'-a_1)+(k'-k_1)}{9-b'} a2=a+ε,k2=k+bε.[a1,k1,2,1]+[2na2,k2,2,1]=[a2a1,k2k1,1,21],ε=9b12n9(aa1)+(kk1)
如果 0 ≤ ε ′ ≤ 1 0\le \varepsilon' \le 1 0ε1, 那么我们就找到了第二个相遇点。这个时间就是
t 2 = [ a 2 − a 1 , k 2 − k 1 , 1 , 1 2 ] = 3 ( a 2 − a 1 ) + ( k 2 − k 1 ) 4 t_2=[a_2-a_1,k_2-k_1,1,\frac{1}{2}]=\frac{3(a_2-a_1)+(k_2-k_1)}{4} t2=[a2a1,k2k1,1,21]=43(a2a1)+(k2k1)
所以,从0秒开始到第二次相遇的总时间为:
t 1 + t 2 = 3 ( a 1 + a 2 ) + ( k 1 + k 2 ) 4 t_1+t_2=\frac{3(a_1+a_2)+(k_1+k_2)}{4} t1+t2=43(a1+a2)+(k1+k2)
注意到 ε \varepsilon ε 总是 1 9 + b ′ \frac{1}{9+b'} 9+b1 的倍数。于是 ε ′ \varepsilon' ε 9 − b ( 9 + b ) ( 9 − b ′ ) \frac{9-b}{(9+b)(9-b')} (9+b)(9b)9b 。所以 a 1 , a 2 , k 1 , k 2 a_1,a_2,k_1,k_2 a1,a2,k1,k2 都是 1 800 \frac{1}{800} 8001 的倍数。于是,所有数字都可以用实际数值的 800 倍在 64 位整数的限制下表示。

算法的时间复杂度为 O ( n ) O(n) O(n) ,空间复杂度为 O ( 1 ) O(1) O(1)

代码

#include <iostream>
#include <algorithm>
#include <string>

using ll = long long;

ll T, n, x;

ll hextooct(char a) {
	if (a <= '9' && a >= '0') {
		return ll(a-'0');
	}
	return 10+ll(a-'a');
}

ll gcd(ll a, ll b) { 
	ll t;
	while(b != 0) { 
		t = a%b;
		a = b;
		b = t; 
	}
	return a; 
}

int main(int argc, char *argv[]) {  
	std::ios::sync_with_stdio(false);
	std::cin.tie(0); 
	std::cin >> T;
	while (T--) {
		std::cin >> n;
		ll nn = n * 800;
		ll k = 0, a = 0, eps, eps2, a1, k1, a2, k2;
		ll t1 = -1, t2 = -1, t;
		std::cin.get();
		for (int i = 0; i < n/2; i++) {
			x = hextooct(std::cin.get());
			for (int j = 0; j < 4; j++) {
				ll b = ((x >>(3-j) & 1ll)?1:(-1));
				if (t1 < 0) {
					eps = (6*nn-9*a-k)/(9+b);
					if (eps < 800 && eps >= 0) {
						a1 = a+eps, k1 = k+eps*b;
						t1 = (3*a1+k1)/(2);
					}
				} else if(t2 < 0) {
					eps2 = (12*nn-9*(a-a1)+(k-k1))/(9-b);
					if (eps2 < 800 && eps2 >= 0) {
						a2 = a+eps2, k2 = k+eps2*b;
						t2 = (3*(a2-a1)+(k2-k1))/(4);
					}
				}
				k += 800 * b;
				a += 800;
			}
		}
		t = t1 + t2;
		ll divisor = gcd(t, 800);
		std::cout << t/divisor << '/' << 800/divisor << '\n';
	}
}

吐槽

打算开个新栏目整点有趣的算法题!

感觉自己还是需要写一点算法题获得一点虚假的成就感… 但是老年选手 codeforces 手速跟不上,project euler 又做不动,leetcode 又觉得无聊… 有点… 尴尬啊

然后最近找到了清晰版本的 Looking for a challenge? 的拍照pdf,打算做一做… 因为这个题解质量和题目质量好像很高… 而且基本都是思维题(真的吗),适合我这种数据结构废物

其实 AGC 也不错,但是 AGC 太多了,做不动…

以及图书馆的荐购功能是真的牛逼…

在这里插入图片描述

我现在在 szkopul 上的题均 dirt 数肯定超过 20 了…

代码细节还挺多的…

不会一个字符一个字符读入错了一万遍

不会位运算又错了一万遍(读入16进制数之后我竟然从后往前用2模它 真的老了

这个 k 和 a 应该在算 eps 之后更新,又错了一发

最后一共交了 14 发才过…

这个东西在 cf 叫 2014-2015 CT S02E07: Codeforces Trainings Season 2 Episode 7 ,找了一年

后来牛逼网友教了我怎么在 gym 按题目名字搜题 然后我发现我笔记里其实有这个链接(

我搜 amppz 题解,搜到一篇英文博客,我一看怎么这么眼熟,结果是我今年一月自己写的,可能是什么垃圾站自动翻译成英文了…

写这种东西比写 Leetcode 快乐多了…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值