奇怪装置「APIO 2019」(推柿子+求区间并)

chedan

看一眼题,感觉题目描述的不是很清楚啊。对于每段时间到底是从头开始算还是从时间开始算啊?

看一眼样例发现是从头开始算。

然后推了一波式子在最后一步脑抽了一下就没推出来/kk。

题目描述

考古学家发现古代文明留下了一种奇怪的装置。该装置包含两个屏幕,分别显示两个整数 \(x\)\(y\)

经过研究,科学家对该装置得出了一个结论:该装置是一个特殊的时钟,它从过去的某个时间点开始测量经过的时刻数 \(t\),但该装置的创造者却将 \(t\) 用奇怪的方式显示出来。若从该装置开始测量到现在所经过的时刻数为 \(t\),装置会显示两个整数:\(x = ((t + \lfloor \frac{t}{B} \rfloor) \bmod A)\),与 \(y = (t \bmod B)\)。这里 \(\lfloor x\rfloor\) 是下取整函数,表示小于或等于 \(x\) 的最大整数。

考古学家通过进一步研究还发现,该装置的屏幕无法一直工作。实际上,该装置的屏幕只在 \(n\) 个连续的时间区间段中能正常工作。第 \(i\) 个时间段从时刻 \(l_i\) 到时刻 \(r_i\) 。现在科学家想要知道有多少个不同的数对 \((x, y)\) 能够在该装置工作时被显示出来。

两个数对 \((x_1, y_1)\)\((x_2, y_2)\) 不同当且仅当 \(x_1 \not = x_2\)\(y_1 \not = y_2\)

对于全部数据,\(1\le n\le 10^6,1\le A,B\le 10^{18},0\le l_i\le r_i\le 10^{18},r_i

Analysis

看到取模所以这种东西多半是有循环节的。

打一打表也不难发现它有循环节。

而循环节是从 \((0,0)\) 开始的所以一定是在下一个 \((0,0)\) 前结束。这打表也可以看出来。

我一开始打算打表找规律,然后发现这东西有的时候是\(AB\),有的时候是\(AB/2\),有的时候还直接就是\(B\)。嗯...不可做。

不过在打表的过程中我找到了其中一个规律:循环节 \(T\)\(B\) 的倍数(这不废话吗)。

之后我又对所有 \(B\) 的倍数的 \(x\) 值打表,试图找规律,无果。

教练讲了一下:解决 \(T + \lfloor\frac{T}{B}\rfloor \equiv 0 (\bmod A)\) 即可。

我:可是这有个取整符号很不好搞。

教练:你不是推出来\(B|T\)了吗?

我:!@#¥%……&*

然后推一波柿子:

\(T=Bk\)

\(Bk + \lfloor\frac{Bk}{B}\rfloor = Bk + k \equiv 0(\bmod A)\)

\(k(B+1)=sA\)

\(k=\frac{sa}{B+1}\)

求最小的\(s\)使得\(k\)是整数

显然\(s=\frac{B+1}{gcd(A,B+1)}\)

\(k=\frac{A}{gcd(A,B+1)}\)

\(T=\frac{AB}{gcd(A,B+1)}\)

求出循环节\(T\)后就是个区间并问题了。

求区间并的两种做法:

1,线段树维护 复杂度\(O(nlogn)\)

2,排序 复杂度\(O(nlogn)\)但常数很小。

这里用第二种方法。

将所有区间按左端点排序,现在存一个区间并的左端点和右端点。

如果现在一个区间的左端点大于你存的区间并的右端点加1说明断开了。更新答案再更新新的区间并。

如果不大于说明这还是一个区间,更新区间并的右端点的最大值(注意我们已经按左端点排序了)。

一定小优化:如果左端点相同,按右端点从大到小排序这样如果左端点相同直接取第一个后面的就可以直接continue了。

如何将题目转换为区间求并?

其实有了循环节我们就可以取模。

而又要分两种情况:

第一种情况是两端点在一个循环节的两边。这样要处理两个区间(坑了我挺久的)。

第二种情况是两端点在两个循环节之间且其之间没有循环节,这样直接取模就行。

然后求区间并即可。

注意如果循环节很大(大于\(rmax\))可以直接不管去求区间并。

Code(码风稍微有点毒瘤)

#include 

using namespace std;

typedef long long ll;

const ll MAX = 1e18;
const int N = 1000010; 

namespace IO{
	template  void read(T &x) {
		T f = 1;
		char ch = getchar();
		for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
		for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
		x *= f;
	}
	template  void write(T x) {
		if (x > 9) write(x / 10);
		putchar(x % 10 + '0');
	}
	template  void print(T x) {
		if (x < 0) putchar('-'), x = -x;
		write(x);
		putchar('\n');
	}
} using namespace IO;

namespace ZCR{
	struct seg{
		ll l, r;
	}sy[N];
	int n;
	ll A, B;
	ll T;//循环节 
	ll ans;
	ll L, R;
	ll gcd(ll a, ll b) {
		return b == 0 ? a : gcd(b, a % b);
	}
	bool cmp(seg a, seg b) {
		if (a.l != b.l) return a.l < b.l;
		return a.r > b.r;
	}
	void MAIN() {
		read(n); read(A); read(B);
		for (int i = 1; i <= n; ++i) {
			read(sy[i].l); read(sy[i].r);
		}
		T = A / gcd(A, B + 1);
		if (MAX / B > T) {
			T *= B;
			for (int i = 1; i <= n; ++i) {
				ll val = sy[i].r / T * T;
				if (sy[i].l < val) {
					sy[i].l %= T;
					sy[i].r %= T;
					sy[++n].l = sy[i].l;
					sy[n].r = T - 1;
					sy[i].l = 0;
				} else {
					sy[i].l %= T;
					sy[i].r %= T;
				}
			}
		}
		sort(sy + 1, sy + 1 + n, cmp);
		L = sy[1].l;
		R = sy[1].r;
		for (int i = 2; i <= n; ++i) {
			if (sy[i].l == sy[i - 1].l) continue;
			if (sy[i].l > R + 1) ans += R - L + 1, L = sy[i].l, R = sy[i].r;
			else if (sy[i].r > R) R = sy[i].r;
		}
		ans += R - L + 1;
		print(ans);
	}
} using namespace ZCR;
int main() {
	MAIN(); 
	return 0;
}

总结

做题时最好把式子都写下来这样子比较一目了然,不容易漏信息。

像这道题我看出循环节是\(B\)的倍数然后硬是没把那个取整搞掉就比较可惜了。

你可能感兴趣的:(奇怪装置「APIO 2019」(推柿子+求区间并))