[题解]JOISC 2020 题解

D1T1 Building 4 LOJ UOJ

Solution

大胆猜想一个结论:可行的序列 \(\text{A}\) 中元素被选取的次数形成一个区间。

故可以 DP \(f_{i,0/1}\)\(g_{i,0/1}\) 分别表示就前 \(i\) 个数,\(\text{A}\) 中元素选取个数的最小和最大值,第二维表示第 \(i\) 个数来自 \(\text{A}\) 还是 \(\text{B}\)

输出方案时按照 DP 数组倒着推回去即可。

时空复杂度 \(O(N)\)

Source

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 1e6 + 5, INF = 0x3f3f3f3f;

int n, m, a[N], b[N], fi[N][2], fx[N][2];
char ans[N];

int main()
{
	read(n); m = n; n <<= 1;
	for (int i = 1; i <= n; i++) read(a[i]);
	for (int i = 1; i <= n; i++) read(b[i]);
	fx[1][0] = 1;
	for (int i = 2; i <= n; i++)
	{
		fx[i][0] = fx[i][1] = -INF;
		if (a[i - 1] <= a[i]) fx[i][0] = Max(fx[i][0], fx[i - 1][0] + 1);
		if (b[i - 1] <= a[i]) fx[i][0] = Max(fx[i][0], fx[i - 1][1] + 1);
		if (a[i - 1] <= b[i]) fx[i][1] = Max(fx[i][1], fx[i - 1][0]);
		if (b[i - 1] <= b[i]) fx[i][1] = Max(fx[i][1], fx[i - 1][1]);
	}
	fi[1][0] = 1;
	for (int i = 2; i <= n; i++)
	{
		fi[i][0] = fi[i][1] = INF;
		if (a[i - 1] <= a[i]) fi[i][0] = Min(fi[i][0], fi[i - 1][0] + 1);
		if (b[i - 1] <= a[i]) fi[i][0] = Min(fi[i][0], fi[i - 1][1] + 1);
		if (a[i - 1] <= b[i]) fi[i][1] = Min(fi[i][1], fi[i - 1][0]);
		if (b[i - 1] <= b[i]) fi[i][1] = Min(fi[i][1], fi[i - 1][1]);
	}
	if ((fx[n][0] < m || fi[n][0] > m) && (fx[n][1] < m || fi[n][1] > m))
		return puts("-1"), 0;
	int nowa = m;
	for (int i = n, op = fx[n][1] >= m && fi[n][1] <= m; i >= 1; i--)
	{
		ans[i] = op ? 'B' : 'A'; if (!op) nowa--;
		op = b[i - 1] <= (op ? b[i] : a[i]) &&
			fx[i - 1][1] >= nowa && fi[i - 1][1] <= nowa;
	}
	for (int i = 1; i <= n; i++) putchar(ans[i]);
	return puts(""), 0;
}

D1T2 Hamburg Steak LOJ UOJ

Solution

恶心题。随机复杂度是假的,我参考的是 cz_xuyixuan 的题解

考虑一维的情况,是个经典贪心,每次考虑所有没被覆盖的区间,选择一个最小的右端点放点,然后继续这个过程。

拓展到二维,我们尝试每次对于所有没被覆盖的矩形,在 \((\max L,\max D)(\max L,\min U)(\min R,\max D)(\min R,\min U)\) 中四选一。

这样的复杂度是 \(O(4^KN)\),足以通过此题。但这样选出的 \(K\) 个点中必然要存在一个点,它位于所有 \(K\) 个点的左上/左下/右上/右下角。

也就是说,如果存在一个矩形(注意不是输入给定的矩形)使得选出的 \(4\) 个点分别在矩形的 \(4\) 条边上,则这种情况不能被识别。

这时这个矩形的四个界为 \(x=\max L,x=\min R,y=\max U,y=\min D\),每个点的位置可以用一个变量来表示。设这些点分别为 \((x_0,\min D)(x_1,\max U)(\min R,y_0)(\max L,y_1)\)

考虑输入的一个矩形,如果它完全包含了 \(4\) 条边中的至少一条,则不管怎么样这个矩形都会被覆盖,可以删掉,否则这个矩形限制了这 \(4\) 个变量中的 \(1\) 个或 \(2\) 个变量。

离散化坐标之后,枚举 \(x_0\)。注意到固定了 \(x_0\) 之后变成了 \(K=3\) 的情况,这 \(3\) 个点中必然有左下角和右下角。故 \(y_0\)\(y_1\) 中至少有一者取 \(\min U\)

假设 \(y_0=\min U\),就可以用数据结构(线段树)维护限制条件,由 \(x_0\)\(y_0\) 的取值得出 \(x_1\)\(y_1\) 的取值范围。

注意这里忽略了一类形如「\(x_1\ge a\)\(y_1\ge b\)」的限制。对于这些限制条件,可以先忽略,让 \(x_1\)\(y_1\) 取到各自的最大值之后判断是否满足即可。

总时间复杂度 \(O(4^KN+N\log N)\)

Code

#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 2e5 + 5, M = N << 1, L = M << 2, INF = 1e9;

int n, K, tx[5], ty[5], mx, rx[M], my, ry[M], tn, liml[3][4][M], limr[3][4][M],
lt[M], rt[M], umL[M], umR[M];
bool vis[N];

struct rect
{
	int l, d, r, u;
} a[N], b[N], eg[4];

struct seg
{
	int tmax[L], tmin[L];
	
	void cover(int l, int r, int s, int e, int x, int y, int p)
	{
		if (e < l || s > r) return;
		if (s <= l && r <= e) return (void) (tmax[p] = Max(tmax[p], x),
			tmin[p] = Min(tmin[p], y));
		int mid = l + r >> 1;
		cover(l, mid, s, e, x, y, p2); cover(mid + 1, r, s, e, x, y, p3);
	}
	
	void wxh(int l, int r, int *a, int *b, int p)
	{
		if (l == r) return (void) (a[l] = tmax[p], b[r] = tmin[p]);
		int mid = l + r >> 1;
		tmax[p2] = Max(tmax[p2], tmax[p]); tmax[p3] = Max(tmax[p3], tmax[p]);
		tmin[p2] = Min(tmin[p2], tmin[p]); tmin[p3] = Min(tmin[p3], tmin[p]);
		wxh(l, mid, a, b, p2); wxh(mid + 1, r, a, b, p3);
	}
} T[3][4], TL;

bool in(rect b, rect a)
{
	return a.l <= b.l && b.r <= a.r && a.d <= b.d && b.u <= a.u;
}

bool inters(rect a, rect b)
{
	return a.r >= b.l && b.r >= a.l && a.u >= b.d && b.u >= a.d;
}

std::vector<int> cover(int x, int y)
{
	std::vector<int> res;
	for (int i = 1; i <= n; i++) if (!vis[i] && a[i].l <= x && x <= a[i].r
		&& a[i].d <= y && y <= a[i].u) vis[i] = 1, res.push_back(i);
	return res;
}

void recover(std::vector<int> res)
{
	for (int i = 0; i < res.size(); i++) vis[res[i]] = 0;
}

void dfs(int dep, int cnt)
{
	if (cnt == n)
	{
		for (int i = 1; i < dep; i++) printf("%d %d\n", tx[i], ty[i]);
		for (int i = dep; i <= K; i++) puts("1 1");
		exit(0);
	}
	if (dep > K) return;
	int maxl = 1, maxd = 1, minr = INF, minu = INF;
	for (int i = 1; i <= n; i++) if (!vis[i])
		maxl = Max(maxl, a[i].l), maxd = Max(maxd, a[i].d),
		minr = Min(minr, a[i].r), minu = Min(minu, a[i].u);
	std::vector<int> tmp;
	tmp = cover(tx[dep] = maxl, ty[dep] = maxd); dfs(dep + 1, cnt + tmp.size());
	recover(tmp);
	tmp = cover(tx[dep] = maxl, ty[dep] = minu); dfs(dep + 1, cnt + tmp.size());
	recover(tmp);
	tmp = cover(tx[dep] = minr, ty[dep] = maxd); dfs(dep + 1, cnt + tmp.size());
	recover(tmp);
	tmp = cover(tx[dep] = minr, ty[dep] = minu); dfs(dep + 1, cnt + tmp.size());
	recover(tmp);
}

int main()
{
	read(n); read(K);
	for (int i = 1; i <= n; i++) read(a[i].l), read(a[i].d),
		read(a[i].r), read(a[i].u), rx[++mx] = a[i].l, rx[++mx] = a[i].r,
			ry[++my] = a[i].d, ry[++my] = a[i].u;
	dfs(1, 0);
	std::sort(rx + 1, rx + mx + 1);
	mx = std::unique(rx + 1, rx + mx + 1) - rx - 1;
	std::sort(ry + 1, ry + my + 1);
	my = std::unique(ry + 1, ry + my + 1) - ry - 1;
	int maxl = 1, maxd = 1, minr = mx, minu = my;
	for (int i = 1; i <= n; i++)
	{
		a[i].l = std::lower_bound(rx + 1, rx + mx + 1, a[i].l) - rx;
		a[i].r = std::lower_bound(rx + 1, rx + mx + 1, a[i].r) - rx;
		a[i].d = std::lower_bound(ry + 1, ry + my + 1, a[i].d) - ry;
		a[i].u = std::lower_bound(ry + 1, ry + my + 1, a[i].u) - ry;
		maxl = Max(maxl, a[i].l); maxd = Max(maxd, a[i].d);
		minr = Min(minr, a[i].r); minu = Min(minu, a[i].u);
	}
	eg[0] = (rect) {minr, minu, maxl, minu};
	eg[1] = (rect) {minr, minu, minr, maxd};
	eg[2] = (rect) {maxl, minu, maxl, maxd};
	eg[3] = (rect) {minr, maxd, maxl, maxd};
	for (int i = 1; i <= n; i++)
		if (!in(eg[0], a[i]) && !in(eg[1], a[i]) && !in(eg[2], a[i]) &&
			!in(eg[3], a[i])) b[++tn] = a[i];
	n = tn;
	for (int R = 0; R < 3; R++) for (int S = 0; S < 4; S++)
		if (R != S) for (int i = 0; i < L; i++)
			T[R][S].tmax[i] = S && S <= 2 ? minu : minr,
			T[R][S].tmin[i] = S && S <= 2 ? maxd : maxl;
	for (int R = 0; R < 3; R++)
	{
		int A = R ? minu : minr, B = R ? maxd : maxl,
		le = A, ri = B;
		for (int i = 1; i <= n; i++)
		{
			bool otz = 0;
			int ml = R ? b[i].d : b[i].l, mr = R ? b[i].u : b[i].r;
			for (int S = 0; S < 4; S++)
			{
				if (R == S || !inters(eg[S], b[i])) continue;
				int l = S && S <= 2 ? b[i].d : b[i].l,
					r = S && S <= 2 ? b[i].u : b[i].r;
				if (inters(eg[R], b[i]))
				{
					otz = 1;
					T[R][S].cover(A, B, A, ml - 1, l, r, 1);
					T[R][S].cover(A, B, mr + 1, B, l, r, 1);
				}
				else
				{
					bool ot = 0;
					for (int V = 0; V < 4; V++)
						if (R != V && S != V && inters(eg[V], b[i]))
							ot = 1;
					if (!ot) T[R][S].cover(A, B, A, B, l, r, 1);
				}
			}
			if (!otz && inters(eg[R], b[i])) le = Max(le, ml), ri = Min(ri, mr);
		}
		for (int S = 0; S < 4; S++)
		{
			if (R == S) continue;
			T[R][S].wxh(A, B, liml[R][S], limr[R][S], 1);
			int l = S && S <= 2 ? minu : minr, r = S && S <= 2 ? maxd : maxl;
			for (int i = A; i < le; i++) liml[R][S][i] = r, limr[R][S][i] = l;
			for (int i = B; i > ri; i--) liml[R][S][i] = r, limr[R][S][i] = l;
		}
	}
	for (int i = minr - 1; i <= maxl + 1; i++) lt[i] = rt[i] = minu;
	for (int i = 1; i <= n; i++)
	{
		if (!inters(eg[3], b[i])) continue;
		if (inters(eg[1], b[i])) lt[b[i].r + 1] = Max(lt[b[i].r + 1], b[i].d);
		if (inters(eg[2], b[i])) rt[b[i].l - 1] = Max(rt[b[i].l - 1], b[i].d);
	}
	for (int i = minr; i <= maxl; i++) lt[i] = Max(lt[i], lt[i - 1]);
	for (int i = maxl; i >= minr; i--) rt[i] = Max(rt[i], rt[i + 1]);
	for (int i = 0; i < L; i++) TL.tmin[i] = maxd, TL.tmax[i] = minu;
	for (int i = 1; i <= n; i++)
		if (!inters(eg[0], b[i])) TL.cover(minr, maxl, minr, maxl, b[i].d, b[i].u, 1);
		else
		{
			TL.cover(minr, maxl, minr, b[i].l - 1, b[i].d, b[i].u, 1);
			TL.cover(minr, maxl, b[i].r + 1, maxl, b[i].d, b[i].u, 1);
		}
	TL.wxh(minr, maxl, umL, umR, 1);
	for (int i = minr; i <= maxl; i++)
	{
		int y = umR[i];
		if (liml[0][1][i] <= y && y <= limr[0][1][i]
		&& liml[1][0][y] <= i && i <= limr[1][0][y])
		{
			int L2 = Max(liml[0][2][i], liml[1][2][y]),
				R2 = Min(limr[0][2][i], limr[1][2][y]),
				L3 = Max(liml[0][3][i], liml[1][3][y]),
				R3 = Min(limr[0][3][i], limr[1][3][y]);
			if (L2 <= R2 && L3 <= R3 && rt[R3] <= R2)
			{
				printf("%d %d\n%d %d\n%d %d\n%d %d\n",
					rx[i], ry[minu], rx[minr], ry[y],
						rx[maxl], ry[R2], rx[R3], ry[maxd]);
				return 0;
			}
		}
		if (liml[0][2][i] <= y && y <= limr[0][2][i]
		&& liml[2][0][y] <= i && i <= limr[2][0][y])
		{
			int L1 = Max(liml[0][1][i], liml[2][1][y]),
				R1 = Min(limr[0][1][i], limr[2][1][y]),
				L3 = Max(liml[0][3][i], liml[2][3][y]),
				R3 = Min(limr[0][3][i], limr[2][3][y]);
			if (L1 <= R1 && L3 <= R3 && lt[L3] <= R1)
			{
				printf("%d %d\n%d %d\n%d %d\n%d %d\n",
					rx[i], ry[minu], rx[minr], ry[R1],
						rx[maxl], ry[y], rx[L3], ry[maxd]);
				return 0;
			}
		}
	}
	return 0;
}

D1T3 Sweeping LOJ UOJ

子任务 \(3\)(没有加点操作,\(X\) 递增,\(Y\) 递减)具有启发性。

考虑一次对矩形 \((0,0)-(x,y)\)\(x+y=N\))的右推或上推操作。显然只会影响这个矩形内的所有点。

可以注意到,当 \(X\) 递增,\(Y\) 递减时,包含在这个矩形内的点是一段区间,右推相当于这段区间内的 \(X\) 坐标改成 \(x\),上推为 \(Y\) 改成 \(y\)

易得右推和上推操作不影响 \(X\)\(Y\) 坐标的单调性,故可以用数据结构维护这些点,每次二分出影响到的点区间,支持区间修改。

对于子任务 \(4\)(没有加点操作),我们继续拓展上面的做法,用数据结构维护左下边界上的点(一个点在左下边界上,当且仅当不存在任意其他点的 \(X\)\(Y\) 坐标都比该点小)。

不难发现对矩形 \((0,0)-(x,y)\) 的右推或上推操作会导致这个矩形内的所有点都变成左下边界上的点。'

于是离线扫描线求出每个点首次被哪一次操作影响,每次矩形操作时要把这次操作首次影响的所有点都插入左下边界。

需要支持插入和区间修改,数据结构上二分和单点查询,可以使用平衡树维护,复杂度 \(O((M+Q)\log(M+Q))\)

对于正解,考虑线段树分治,即分成左右两半,先递归左侧,得出左侧操作结束之后,在左侧加入的所有点的坐标,然后用上面的方法处理右侧所有和左侧点有关的询问结果之后,再往右侧递归。复杂度 \(O((M+Q)\log^2(M+Q)\)

注意可以将初始的 \(M\) 个点当作 \(M\) 次加点操作,以加点操作为单位进行分治。

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 15e5 + 5;

int n, m, q, t[N], P[N], L[N], tot, Root, tl, que[N], sz[N], top, stk[N],
fl, fp[N];
std::vector<int> orz[N];
bool is[N];

struct point
{
	int x, y;
} pos[N], ans[N];

struct node
{
	int lc, rc, fa, pri;
	point pt, mark;
} T[N];

void down(int p)
{
	if (T[p].mark.x != -1)
	{
		if (T[p].lc) T[T[p].lc].pt.x = T[T[p].lc].mark.x = T[p].mark.x;
		if (T[p].rc) T[T[p].rc].pt.x = T[T[p].rc].mark.x = T[p].mark.x;
	}
	if (T[p].mark.y != -1)
	{
		if (T[p].lc) T[T[p].lc].pt.y = T[T[p].lc].mark.y = T[p].mark.y;
		if (T[p].rc) T[T[p].rc].pt.y = T[T[p].rc].mark.y = T[p].mark.y;
	}
	T[p].mark.x = T[p].mark.y = -1;
}

std::pair<int, int> split(int p, point u)
{
	if (!p) return std::make_pair(0, 0);
	std::pair<int, int> res;
	if (down(p), T[p].pt.x <= u.x && T[p].pt.y >= u.y) return res = split(T[p].rc, u),
		T[p].rc = res.first, res.first ? (T[res.first].fa = p) : 0,
			std::make_pair(p, res.second);
	else return res = split(T[p].lc, u),
		T[p].lc = res.second, res.second ? (T[res.second].fa = p) : 0,
			std::make_pair(res.first, p);
}

int merge(int p, int q)
{
	if (!p || !q) return p ^ q;
	if (down(p), down(q), T[p].pri < T[q].pri)
		return (T[p].rc = merge(T[p].rc, q)) ? (T[T[p].rc].fa = p) : 0, p;
	else return (T[q].lc = merge(p, T[q].lc)) ? (T[T[q].lc].fa = q) : 0, q;
}

void ins(int p, point pt)
{
	T[p] = (node) {0, 0, 0, rand() << 15 | rand(), pt, (point) {-1, -1}};
	std::pair<int, int> o = split(Root, pt);
	Root = merge(merge(o.first, p), o.second);
}

int fis(int p, point pt)
{
	int res = 0;
	while (p) down(p), p = T[p].pt.y > pt.y ? (res = p, T[p].rc) : T[p].lc;
	return res;
}

int las(int p, point pt)
{
	int res = 0;
	while (p) down(p), p = T[p].pt.x <= pt.x ? (res = p, T[p].rc) : T[p].lc;
	return res;
}

void wxh(point o, point pt)
{
	int x = las(Root, o); if (!x) return;
	std::pair<int, int> p = split(Root, T[x].pt), q;
	x = fis(p.first, o); q = x ? split(p.first, T[x].pt) : std::make_pair(0, p.first);
	if (q.second)
	{
		T[q.second].mark = pt;
		if (pt.x != -1) T[q.second].pt.x = pt.x;
		if (pt.y != -1) T[q.second].pt.y = pt.y;
	}
	Root = merge(merge(q.first, q.second), p.second);
}

void dfs(int x)
{
	down(x);
	if (T[x].lc) dfs(T[x].lc); if (T[x].rc) dfs(T[x].rc);
}

point query(int x)
{
	point res = T[x].pt;
	while (x)
	{
		if (T[x].mark.x != -1) res.x = T[x].mark.x;
		if (T[x].mark.y != -1) res.y = T[x].mark.y;
		x = T[x].fa;
	}
	return res;
}

void nealchen(int l, int r)
{
	int xl = P[l], xr, mid, xmid;
	for (int i = l; i <= r; i++) if (t[i] == 4) xr = P[i];
	if (xl == xr)
	{
		for (int i = l + 1; i <= r; i++)
			if (t[i] == 2)
			{
				if (pos[xl].x < n - L[i] && pos[xr].y <= L[i])
					pos[xl].x = n - L[i];
			}
			else if (t[i] == 3)
			{
				if (pos[xl].x <= L[i] && pos[xr].y < n - L[i])
					pos[xr].y = n - L[i];
			}
			else if (xl <= P[i] && P[i] <= xr) ans[i] = pos[xl];
		return;
	}
	for (int i = l; i <= r; i++) if (t[i] == 4 && P[i] == (xl + xr >> 1) + 1)
		{mid = i; break;}
	nealchen(l, mid - 1); xmid = xl + xr >> 1; Root = tl = fl = top = 0;
	for (int i = mid; i <= r; i++) if (t[i] == 2 || t[i] == 3)
	{
		que[++tl] = i; orz[i].clear();
		if (t[i] == 2) sz[i] = n - L[i]; else if (t[i] == 3) sz[i] = L[i];
	}
	std::sort(que + 1, que + tl + 1, [&](int x, int y) {return sz[x] < sz[y];});
	for (int i = xl; i <= xmid; i++) fp[++fl] = i;
	std::sort(fp + 1, fp + fl + 1, [&](int i, int j)
		{return pos[i].y > pos[j].y || (pos[i].y == pos[j].y && pos[i].x < pos[j].x);});
	for (int i = fl, mx = 1145141919; i >= 1; i--)
		if (pos[fp[i]].x <= mx) mx = pos[fp[i]].x, ins(fp[i], pos[fp[i]]), is[fp[i]] = 0;
		else is[fp[i]] = 1;
	for (int i = 1, j = 1; i <= fl; i++)
	{
		while (j <= tl && n - sz[que[j]] >= pos[fp[i]].y)
		{
			while (top && stk[top] > que[j]) top--;
			stk[++top] = que[j]; j++;
		}
		int l = 1, r = top;
		while (l <= r)
		{
			int mid = l + r >> 1;
			if (sz[stk[mid]] >= pos[fp[i]].x) r = mid - 1;
			else l = mid + 1;
		}
		if (l <= top && is[fp[i]]) orz[stk[l]].push_back(fp[i]);
	}
	for (int i = mid; i <= r; i++)
		if (t[i] == 2)
		{
			wxh((point) {n - L[i], L[i]}, (point) {n - L[i], -1});
			for (int j = 0; j < orz[i].size(); j++)
				ins(orz[i][j], (point) {n - L[i], pos[orz[i][j]].y}), is[orz[i][j]] = 0;
		}
		else if (t[i] == 3)
		{
			wxh((point) {L[i], n - L[i]}, (point) {-1, n - L[i]});
			for (int j = 0; j < orz[i].size(); j++)
				ins(orz[i][j], (point) {pos[orz[i][j]].x, n - L[i]}), is[orz[i][j]] = 0;
		}
		else if (t[i] == 1 && xl <= P[i] && P[i] <= xmid)
			ans[i] = is[P[i]] ? pos[P[i]] : query(P[i]);
	dfs(Root); for (int i = xl; i <= xmid; i++) if (!is[i]) pos[i] = query(i);
	nealchen(mid, r);
}

int main()
{
	read(n); read(m); read(q); tot = m;
	for (int i = 1; i <= m; i++) t[i] = 4, read(pos[i].x), read(pos[i].y), P[i] = i;
	while (q--)
	{
		m++; read(t[m]);
		if (t[m] == 1) read(P[m]); else if (t[m] <= 3) read(L[m]);
		else P[m] = ++tot, read(pos[tot].x), read(pos[tot].y);
	}
	nealchen(1, m);
	for (int i = 1; i <= m; i++) if (t[i] == 1) printf("%d %d\n", ans[i].x, ans[i].y);
	return 0;
}

D2T1 Chameleon’s Love LOJ UOJ

Solution

把每个点向其恋爱对象连边,这个图会形成若干个长度为偶数的有向环。

子任务 \(4\)(二分图一部的点已经给定)具有启发性。设二分图一部的点集为 \(S\),考虑一个 \(u\notin S\),查询 \(S+u\) 的结果必然为 \(N-1\)

而对于 \(S\) 是独立集并且没有同色的情况,这样的查询也适用:

(1)\(Query(S+u)=|S|-1\)\(S\) 中存在 \(u\) 的被恋爱对象,也存在和 \(u\) 同色的点。

(2)\(Query(S+u)=|S|+1\)\(S\) 中不存在和 \(u\) 有边的点,也不存在和 \(u\) 同色的点。

(3)\(Query(S+u)=|S|\):其他情况。

故可以二分 \(S\) 的一个最短前缀 \(S'\) 满足 \(S'+u\) 的查询结果为 \(|S'|-1\),则 \(S'\) 的最后一个元素 \(v\) 必然和 \(u\) 有边,或者和 \(u\) 同色。

判断 \(u,v\) 有边还是同色,可以查询 \(\{u,v\}\) 的补集,询问结果小于 \(N\) 则同色。

如果同色就找到了一个答案,否则在 \(S'-v\) 中继续二分找点。

最坏情况下需要找 \(3\) 次(分别为被恋爱对象、恋爱对象和同色点),可以通过子任务 \(4\)

对于正解,考虑每次随机一个加点顺序,实时维护集合 \(S\)

\(u\) 需要加入时,用上面的二分找到一个点 \(v\)

如果这样的 \(v\) 找不到,则把 \(v\) 加入 \(S\)

如果找到的 \(v\)\(u\) 同色,则找到一个答案,把 \(v\)\(S\) 中删掉。

否则不把 \(u\) 加入集合,继续下一个点。

注意到期望情况下,我们一次会找到 \(\frac N3\sim\frac{2N}3\) 组同色点(不太会具体分析),故对每个点进行二分的次数之和为期望 \(O(N)\)

询问次数 \(O(N\log N)\),本人不太会分析常数,实测最大的点询问次数约为 \(8,700\)

Code

#include <bits/stdc++.h>
#include "chameleon.h"

const int N = 1005;

typedef std::vector<int> vi;

int n, seq[N];
bool vis[N];

bool thesame(int x, int y)
{
	vi tmp; for (int i = 1; i <= n; i++) if (i != x && i != y) tmp.push_back(i);
	return Query(tmp) < (n >> 1);
}

vi del(vi a, int x)
{
	vi res; for (int i = 0; i < a.size(); i++) if (a[i] != x) res.push_back(a[i]);
	return res;
}

int gp(vi a, int x)
{
	int k = a.size(); a.push_back(x); int y = Query(a);
	if (y == k + 1) return 0;
	int l = 0, r = k - 1;
	while (l <= r)
	{
		int mid = l + r >> 1; vi tmp;
		for (int i = 0; i <= mid; i++) tmp.push_back(a[i]);
		tmp.push_back(x);
		if (y - k == Query(tmp) - (mid + 1)) r = mid - 1;
		else l = mid + 1;
	}
	return a[l];
}

void Solve(int _n)
{
	srand(20010910);
	n = _n; n <<= 1;
	while (114514 < 1919810)
	{
		int tot = 0;
		for (int i = 1; i <= n; i++) if (!vis[i]) seq[++tot] = i;
		std::random_shuffle(seq + 1, seq + tot + 1); vi cur;
		for (int i = 1; i <= tot; i++)
		{
			int u = seq[i], v = gp(cur, u);
			if (!v) {cur.push_back(u); continue;}
			if (thesame(u, v)) cur = del(cur, v), Answer(u, v), vis[u] = vis[v] = 1;
		}
		bool is = 1;
		for (int i = 1; i <= n; i++) is &= vis[i];
		if (is) return;
	}
}

D2T2 Making Friends on Joitter is Fun LOJ UOJ

Solution

用并查集维护强连通分量。容易证明每个强连通分量都是完全图,并且强连通分量之间的边总是符合「一个点连向一个分量内的所有点」的形式。

如果两个强连通分量之间有双向边就会发生合并,并且合并会连锁(如果有边 \(u\rightarrow v,w\rightarrow u\),则 \(v,w\) 合并之后的连通块会和 \(u\) 形成双向边而再次合并)。

大力模拟即可。每个连通块需要利用 std::set 记录出入边,合并这些边集可以使用启发式合并。

时间复杂度为 \(O((N+M)\log^2N)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

typedef long long ll;
typedef std::set<int>::iterator it;

const int N = 1e5 + 5, M = N * 3;

int n, m, fa[N], sze[N], X[M], Y[M];
ll ans;

std::set<int> in[N], out[N], in1[N], in2[N], out2[N];

int cx(int x)
{
	if (fa[x] != x) fa[x] = cx(fa[x]);
	return fa[x];
}

int getsize(int u) {return in[u].size() + out[u].size() + in1[u].size()
+ in2[u].size() + out2[u].size();}

int merge(int x, int y)
{
	if ((x = cx(x)) == (y = cx(y))) return x;
	if (getsize(x) < getsize(y)) std::swap(x, y);
	fa[y] = x; ans += 2ll * sze[x] * sze[y];
	ans -= 1ll * sze[x] * in1[x].size() + 1ll * sze[y] * in1[y].size();
	sze[x] += sze[y];
	std::vector<int> app; it tmp;
	for (it i = in[y].begin(); i != in[y].end(); i++)
		if (out[x].find(*i) != out[x].end()) app.push_back(*i);
	for (it i = out[y].begin(); i != out[y].end(); i++)
		if (in[x].find(*i) != in[x].end()) app.push_back(*i);
	for (it i = in[y].begin(); i != in[y].end(); i++)
		if (*i == x) out[x].erase(out[x].find(y));
		else in[x].insert(*i), out[*i].erase(out[*i].find(y)), out[*i].insert(x);
	for (it i = out[y].begin(); i != out[y].end(); i++)
		if (*i == x) in[x].erase(in[x].find(y));
		else out[x].insert(*i), in[*i].erase(in[*i].find(y)), in[*i].insert(x);
	for (it i = in1[y].begin(); i != in1[y].end(); i++) in1[x].insert(*i);
	for (it i = in2[y].begin(); i != in2[y].end(); i++)
		if (cx(X[*i]) == x)
		{
			out2[x].erase(out2[x].find(*i));
			if (tmp = in1[x].find(X[*i]), tmp != in1[x].end()) in1[x].erase(tmp);
		}
		else in2[x].insert(*i);
	for (it i = out2[y].begin(); i != out2[y].end(); i++)
		if (cx(Y[*i]) == x)
		{
			in2[x].erase(in2[x].find(*i));
			if (tmp = in1[x].find(X[*i]), tmp != in1[x].end()) in1[x].erase(tmp);
		}
		else out2[x].insert(*i);
	ans += 1ll * sze[x] * in1[x].size();
	for (int i = 0; i < app.size(); i++) x = merge(x, app[i]);
	return x;
}

int main()
{
	int x, y;
	read(n); read(m);
	for (int i = 1; i <= n; i++) fa[i] = i, sze[i] = 1;
	for (int i = 1; i <= m; i++)
	{
		read(x); read(y); int ix = cx(x), iy = cx(y); X[i] = x; Y[i] = y;
		if (ix == iy) {printf("%lld\n", ans); continue;}
		if (in1[iy].find(x) == in1[iy].end()) ans += sze[iy];
		out[ix].insert(iy); in[iy].insert(ix); in1[iy].insert(x);
		in2[iy].insert(i); out2[ix].insert(i);
		if (in[ix].find(iy) != in[ix].end()) merge(ix, iy);
		printf("%lld\n", ans);
	}
	return 0;
}

D2T3 Ruins 3 LOJ UOJ

Solution

下面定义一个 \(1\le i\le 2n\)\(i\)\(\text{A}\) 类当且仅当集合 \(A\) 包含 \(i\),否则为 \(\text{B}\) 类。

考虑把 \(1\sim2n\) 的数拆分成 \(n\) 个二元组,第 \(i\) 个二元组表示初始高度为 \(i\) 的石柱所在的位置。

然后分析之后发现我们要求的就是这些二元组有多少种取法,使得 \(i\)\(n\)\(1\),每次在第 \(i\) 到第 \(n\) 个二元组中剩下的所有元素中取出一个最大数,把这个最大数从二元组中删掉并加入集合 \(A\),求达到给定的集合 \(A\) 的方案数。

为了方便考虑,这个二元组序列倒过来,即第 \(i\) 次在前 \(i\) 个二元组中取一个最大数删掉并加入集合 \(A\)

这个问题看上去十分繁琐,先考虑如果确定了所有 \(\text{A}\) 类元素的位置之后,在第 \(i\) 次删掉的元素会是什么。

\(P_i\) 表示 \(A_i\) 所在的二元组编号。显然第 \(P_n\) 次删掉的元素必须是 \(A_n\)

接着考虑 \(A_{n-1}\) 在哪一次被删掉,如果 \(P_{n-1}\ne P_n\),则第 \(A_{n-1}\) 会在第 \(P_{n-1}\) 次被删掉。

否则第 \(P_{n-1}\) 次已经删掉了一个 \(A_n\),不可能同时再删掉一个 \(A_{n-1}\),只能留到下一次删,也就是 \(P_{n-1}+1\)

\(A_{n-2}\) 也一样,检查是否已经有元素在第 \(P_{n-2}\) 次被删过,如果没有就定为 \(P_{n-2}\),否则继续检查 \(P_{n-2}+1\),如果还是不合法就继续检查 \(P_{n-2}+2\) 直到合法。

也就是我们让 \(i\)\(n\)\(1\) 从大到小考虑,每次找到 \(P_i\) 之后第一个没有被占用的位置 \(j\),让 \(j\) 位置为 \(i\) 占用,即 \(A_i\) 在第 \(j\) 次被删除。

显然一个二元组只能最多包含 \(2\)\(\text{A}\) 类元素,并且前 \(i\) 个二元组至少有 \(i\) 个二类元素(否则会有至少一个 \(\text{A}\) 类元素无处安放)。

设第 \(i\) 次被删掉的 \(\text{A}\) 类元素为 \(R_i\),考虑 \(\text{B}\) 类元素的限制,显然的我们有如果 \(i\)\(\text{B}\) 类元素且在二元组 \(j\) 内,则对于所有的 \(k\ge j\) 都要满足 \(i<R_k\)

可以得到如果 \(i<j\)\(R_i>R_j\),则 \(R_i\)\(\text{B}\) 类元素的限制可以忽略。

故我们每次取出最小的 \(R_i\),然后在 \(i\) 的后面再找一个最小的 \(R_j\)\(j\) 后面继续找一个最小的 \(R_k\),依次类推,我们把每次找到的最小位置称为关键点。换句话说,\(i\) 是关键点当且仅当 \(R_i\) 为后缀 \(R[i\dots n]\) 的最小值。

不难发现我们只需考虑关键点带来的限制,并且去掉关键点之后剩下的每一段 \(\text{A}\) 类元素是独立的。设第 \(i\) 个关键点为 \(x_i\),由每一段的独立性我们可以发现前 \(x_i\) 个二元组中必然 \(\text{A}\) 类和 \(\text{B}\) 类元素各占一半。设第 \((x_{i-1},x_i]\) 个二元组中有 \(k_i\) 个二元组只由 \(\text{B}\) 类元素组成(它和只由 \(\text{A}\) 类元素组成的二元组个数是一样的),比 \(R_{x_i}\) 小的 \(\text{B}\) 类元素有 \(c_i\) 个,则为 \(\text{B}\) 类元素安排位置的方案数为(\(x_0=0\)):

\[\prod_i\frac1{2^{k_i}}A_{c_i-x_{i-1}}^{x_i-x_{i-1}} \]

\((x_{i-1},x_i]\) 内可以安排不超过 \(R_{x_i}\) 的元素,除掉前 \(x_{i-1}\) 个二元组内已经用过的。除以 \(2^{k_i}\) 的原因是两个 \(\text{B}\) 类元素组成的二元组两元互换之后没有区别。

实际上,如果关键点的位置和值以及 \(k_i\) 给定,由独立性也可以计算出对 \(\text{A}\) 类元素安排位置的方案数(\(y_i\) 为比 \(R_{x_i}\) 大的 \(\text{A}\) 类元素个数):

\[\prod_i\frac1{2^{k_i}}\binom{x_i-x_{i-1}-1}{2k_i}Cat(k_i)A_{y_i-(n-x_i)}^{x_i-x_{i-1}-1}(x_i-x_{i-1}-k_i) \]

即先枚举使用了 \(2\) 个或 \(0\)\(\text{A}\) 类元素的二元组集合,\(Cat\) 为卡特兰数,\(Cat(k_i)\) 限制了前 \(i\) 个二元组至少 \(i\)\(\text{A}\) 类元素的条件,\(A_{y_i-(n-x_i)}^{x_i-x_{i-1}-1}\) 则表示在比 \(R_{x_i}\) 大的数(需要扣除掉 \(R[x_i+1\dots n]\) 内的所有数)中选 \(x_i-x_{i-1}-1\) 个放在 \(x_i\) 的前面,\(\frac1{2^{k_i}}\) 同上。

最后的 \(x_i-x_{i-1}-k_i\) 表示枚举 \(R_{x_i}\) 安放在哪里(可以安放在第 \(x_{i-1}+1\) 到第 \(x_i\) 个二元组上)。但值得注意的是,如果安放在还没有 \(\text{A}\) 类元素的二元组集合上(有 \(k_i+1\) 个),则 \((x_{i-1},x_i]\) 内只由 \(\text{B}\) 类元素组成的二元组个数为 \(k_i\),否则安放在其余的 \(x_i-x_{i-1}-1\) 个二元组上,则 \((x_{i-1},x_i]\) 内只由 \(\text{B}\) 类元素组成的二元组个数为 \(k_i+1\)

于是我们有了一个思路:对关键点的位置和值进行 DP。\(f_{i,j}\) 表示 \(i\) 为关键点,满足 \(R_i=A_j\),确定第 \(i\) 个到第 \(n\) 个二元组的方案数。边界 \(f_{n,i}=1\)

转移即枚举 \(k<i,h<j\) 表示前一个关键点:

\[f_{k,h}+=f_{i,j}\times g_{i,j,k} \]

其中 \(g_{i,j,k}\) 表示一个关键点 \(t\) 满足 \(x_t=i\)\(x_{t-1}=k\)\(R_i=A_j\),为 \((x_{t-1},x_t]\) 内的二元组定值的方案数。如果能够求出 \(g\),这个 DP 就能前缀和 \(O(N^3)\) 解决。

\(g\) 其实就是结合上面计算 \((x_{t-1},x_t]\)\(\text{A}\)\(\text{B}\) 元素的分配。具体写出来比较麻烦,这里省略推导过程,直接给出:

设:

\[g'_i=\sum_j\frac1{2^j}\binom i{2j}Cat(j)i!(i+1)!(\frac{j+1}{2^j}+\frac{i-2j}{2^{j+1}}) \]

则(\(cnt_i\) 表示比 \(A_i\) 小的 \(\text{B}\) 类元素个数):

\[g_{i,j,k}=\binom{i-j}{i-k-1}\binom{cnt_j-k}{i-k}g'_{i-k-1} \]

答案为 \(f_{0,0}\)。复杂度 \(O(N^3)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 605, M = N << 1, djq = 1e9 + 7, I2 = 5e8 + 4;

int n, m, a[N], C[N][N], s[M], cnt[N], f[N][N], Cat[N], g[N], fac[N], ip2[N];

inline void add(int &a, const int &b) {if ((a += b) >= djq) a -= djq;}

int main()
{
	read(n); m = n << 1;
	for (int i = 1; i <= m; i++) s[i] = 1;
	for (int i = 1; i <= n; i++) read(a[i]), s[a[i]] = 0;
	for (int i = 1; i <= m; i++) s[i] += s[i - 1];
	for (int i = 1; i <= n; i++) cnt[i] = s[a[i]];
	for (int i = 0; i <= n; i++) C[i][0] = 1;
	for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++)
		add(C[i][j] = C[i - 1][j], C[i - 1][j - 1]);
	Cat[0] = fac[0] = ip2[0] = f[n][n] = 1;
	for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % djq,
		ip2[i] = 1ll * I2 * ip2[i - 1] % djq;
	for (int i = 1; i <= n; i++) for (int j = 0; j < i; j++)
		Cat[i] = (1ll * Cat[j] * Cat[i - j - 1] + Cat[i]) % djq;
	for (int i = 0; i < n; i++) for (int j = 0; (j << 1) <= i; j++)
		g[i] = (1ll * C[i][j << 1] * Cat[j] % djq * fac[i] % djq * ip2[j] % djq
			* fac[i + 1] % djq * (1ll * (j + 1) * ip2[j] % djq
				+ 1ll * (i - (j << 1)) * ip2[j + 1] % djq) + g[i]) % djq;
	for (int i = n; i >= 0; i--)
	{
		for (int j = n; j >= 0; j--) add(f[i][j], f[i][j + 1]);
		for (int j = 1; j <= i; j++) for (int k = 1; k <= i && k <= cnt[j] + 1; k++)
			f[k - 1][j - 1] = (1ll * f[i][j] * C[i - j][i - k] % djq
				* C[cnt[j] - k + 1][i - k + 1] % djq * g[i - k] + f[k - 1][j - 1]) % djq;
	}
	return std::cout << f[0][0] << std::endl, 0;
}

D3T1 Constellation 3 LOJ UOJ

Solution

考虑笛卡尔树 DP:\(f[l,r]\) 表示 \(X\in[l,r]\)\(Y\in[1,\min(A_{l-1},A_{r+1})]\) 这个矩形的答案。这里 \(A_0=A_{n+1}=N\)

易得 \(\max_{i=l}^rA_i<Y\le\min(A_{l-1},A_{r+1})\) 这一部分必然是一个空矩形,里面的点最多选一个。

如果不选则 \(A\) 最大值的两边独立,故这时答案为两边答案的和。

否则会有若干个矩形不能放其他点。具体地,设最上方的矩形中第 \(x\) 列放了点,则对于笛卡尔树 \(x\)\(mid\) 的路径上的任意一个点 \(y\),第 \(y\) 列上方的矩形内都不能放点。

这时候要计算的就是对于 \(x\)\(mid\) 的路径上的所有点,这些点不在该路径上的子节点 DP 值之和。

\(f'_u\) 表示 \(u\) 兄弟节点的 DP 值,就转化为路径求和,可以 BIT。

复杂度 \(O(N\log N)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

typedef unsigned long long ull;

const int N = 2e5 + 5, E = 20;

int n, a[N], m, RMQ[N][E], lc[N], rc[N], ToT, dfn[N], sze[N], Log[N] = {-1},
Root, X[N], Y[N], C[N], fa[N][E];
ull A[N], f[N];
std::vector<int> pt[N];

void change(int x, ull v)
{
	for (; x <= n; x += x & -x)
		A[x] += v;
}

ull ask(int x)
{
	ull res = 0;
	for (; x; x -= x & -x) res += A[x];
	return res;
}

int maxp(int l, int r)
{
	int k = Log[r - l + 1];
	return a[RMQ[l][k]] > a[RMQ[r - (1 << k) + 1][k]] ?
		RMQ[l][k] : RMQ[r - (1 << k) + 1][k];
}

int getpos(int x, int y)
{
	for (int i = 17; i >= 0; i--)
		if (fa[x][i] && a[fa[x][i]] < y) x = fa[x][i];
	return x;
}

int dfs(int l, int r, int fu)
{
	int u = maxp(l, r);
	dfn[u] = ++ToT; sze[u] = 1; fa[u][0] = fu;
	for (int i = 0; i < 17; i++) fa[u][i + 1] = fa[fa[u][i]][i];
	if (l < u) sze[u] += sze[lc[u] = dfs(l, u - 1, u)];
	if (u < r) sze[u] += sze[rc[u] = dfs(u + 1, r, u)];
	return u;
}

void dp(int u)
{
	if (lc[u]) dp(lc[u]); if (rc[u]) dp(rc[u]);
	if (lc[u]) change(dfn[lc[u]], f[rc[u]]), change(dfn[lc[u]] + sze[lc[u]], -f[rc[u]]);
	if (rc[u]) change(dfn[rc[u]], f[lc[u]]), change(dfn[rc[u]] + sze[rc[u]], -f[lc[u]]);
	f[u] = f[lc[u]] + f[rc[u]];
	for (int i = 0; i < pt[u].size(); i++)
	{
		int x = X[pt[u][i]], c = C[pt[u][i]];
		f[u] = Max(f[u], ask(dfn[x]) + f[lc[x]] + f[rc[x]] + c);
	}
}

int main()
{
	ull sum = 0;
	read(n);
	for (int i = 1; i <= n; i++) read(a[i]), RMQ[i][0] = i;
	read(m);
	for (int i = 1; i <= n; i++) Log[i] = Log[i >> 1] + 1;
	for (int j = 1; j <= 17; j++)
		for (int i = 1; i + (1 << j) - 1 <= n; i++)
			RMQ[i][j] = a[RMQ[i][j - 1]] > a[RMQ[i + (1 << j - 1)][j - 1]]
				? RMQ[i][j - 1] : RMQ[i + (1 << j - 1)][j - 1];
	Root = dfs(1, n, 0);
	for (int i = 1; i <= m; i++) read(X[i]), read(Y[i]), read(C[i]),
		pt[getpos(X[i], Y[i])].push_back(i), sum += C[i];
	return std::cout << (dp(Root), sum - f[Root]) << std::endl, 0;
}

D3T2 Harvest LOJ UOJ

Solution

单独考虑每个人不好处理,因为不同的人之间会互相影响。

所以单独考虑每棵树,不难发现每棵树是独立的。设第 \(i\) 棵树最先在 \(t_i\) 时刻被第 \(p_i\) 个人摘,则这棵树下一次在何时被哪个人摘就能容易确定。

也就是我们可以对每个人预处理出 \(nxt_i\)\(time_i\) 分别表示第 \(i\) 个人摘了某一棵树之后,这棵树会在 \(time_i\) 个时间单位之后被第 \(nxt_i\) 个人摘。

考虑建图,将 \(i\) 连向 \(nxt_i\),边权为 \(time_i\),可以得到一棵基环内向树森林。

问题就可以看成一棵 \(N\) 个节点的树和 \(M\) 个人,第 \(i\) 个人在 \(t_i\) 时刻从 \(p_i\) 点出发,每次询问在 \(T\) 时刻结束时点 \(V\) 被经过了多少次。

可以将询问离线之后对树上部分和环上部分分别处理。

树上部分比较简单,环上部分需要对于时间模环长的值进行处理,细节较多,这里略去,可以看代码。

复杂度为线性对数级别。

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

typedef long long ll;

const int N = 2e5 + 5, M = N << 1, L = 3e7 + 5;

int n, m, l, c, a[N], b[N], q, d[M], to[N], len[N], ecnt, nxt[N], adj[N],
go[N], vis[N], tot, bel[N], rt[N], ToT, pts, ut[N], tq, tm, A[N];
ll dis[N], ans[N], D[N];
std::vector<int> cir[N];
std::vector<ll> pt[N];

void change(int x, int v) {for (; x <= pts; x += x & -x) A[x] += v;}

int ask(int x) {int res = 0; for (; x; x -= x & -x) res += A[x]; return res;}

struct modify
{
	ll st, t;
} mt[N];

struct query
{
	int id, u; ll t;
} qt[N];

struct node
{
	int lc, rc, sum;
} T[L];

void change(ll l, ll r, ll pos, int v, int &p)
{
	if (!p) p = ++ToT; T[p].sum++;
	if (l == r) return;
	ll mid = l + r >> 1;
	if (pos <= mid) change(l, mid, pos, v, T[p].lc);
	else change(mid + 1, r, pos, v, T[p].rc);
}

int ask(ll l, ll r, ll s, ll e, int p)
{
	if (!p || e < l || s > r) return 0;
	if (s <= l && r <= e) return T[p].sum;
	ll mid = l + r >> 1;
	return ask(l, mid, s, e, T[p].lc) + ask(mid + 1, r, s, e, T[p].rc);
}

int mer(int u, int v)
{
	if (!u || !v) return u ^ v;
	T[u].sum += T[v].sum;
	T[u].lc = mer(T[u].lc, T[v].lc);
	T[u].rc = mer(T[u].rc, T[v].rc);
	return u;
}

std::vector<query> que[N];

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}

void dfs(int u, int r)
{
	for (int i = 0; i < pt[u].size(); i++) pt[r].push_back(pt[u][i] + dis[u]),
		change(0, 1e18, pt[u][i] + dis[u], 1, rt[u]);
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		dis[v] = dis[u] + len[v], dfs(v, r), rt[u] = mer(rt[u], rt[v]);
	for (int i = 0; i < que[u].size(); i++)
		ans[que[u][i].id] = ask(0, 1e18, 0, dis[u] + que[u][i].t, rt[u]);
}

void jiejuediao(int I)
{
	pts = cir[I].size(); tq = tm = 0; ll T = 0, tl = 0; int rt = 0;
	for (int i = 0; i < pts; i++) ut[i] = cir[I][i],
		D[i] = i ? D[i - 1] + len[ut[i - 1]] : 0, T += len[ut[i]];
	for (int i = 0; i < pts; i++)
		for (int j = 0; j < que[ut[i]].size(); j++)
			qt[++tq] = que[ut[i]][j];
	std::sort(qt + 1, qt + tq + 1, [&](query a, query b) {return a.t < b.t;});
	for (int i = 0; i < pts; i++) d[ut[i]] = i, A[i + 1] = 0;
	for (int i = 1; i <= tq; i++) qt[i].u = d[qt[i].u];
	for (int i = 0; i < pts; i++)
		for (int j = 0; j < pt[ut[i]].size(); j++)
		{
			ll td = pt[ut[i]][j];
			mt[++tm] = (modify) {(D[i] - td % T + T) % T, td};
		}
	std::sort(mt + 1, mt + tm + 1, [&](modify a, modify b) {return a.t < b.t;});
	for (int i = 1, j = 1; i <= tq; i++)
	{
		while (j <= tm && mt[j].t <= qt[i].t)
		{
			change(0, T - 1, mt[j].st, 1, rt);
			ll nxt = (mt[j].st + mt[j].t) % T;
			change(std::lower_bound(D, D + pts, mt[j].st) - D + 1, 1);
			change(std::lower_bound(D, D + pts, nxt) - D + 1, -1);
			tl += mt[j].t / T + (nxt < mt[j].st); j++;
		}
		ans[qt[i].id] = qt[i].t / T * (j - 1); ll md = qt[i].t % T;
		if (md <= D[qt[i].u]) ans[qt[i].id] +=
			ask(0, T - 1, D[qt[i].u] - md, D[qt[i].u], rt);
		else ans[qt[i].id] += ask(0, T - 1, 0, D[qt[i].u], rt)
			+ ask(0, T - 1, D[qt[i].u] - md + T, T - 1, rt);
		ans[qt[i].id] -= tl + ask(qt[i].u + 1);
	}
}

int main()
{
	int x, ToT = 0; ll t;
	read(n); read(m); read(l); read(c);
	for (int i = 1; i <= n; i++) read(a[i]);
	for (int i = 1; i <= m; i++) read(b[i]);
	for (int i = 1; i <= n; i++)
	{
		x = (a[i] - c % l + l) % l;
		to[i] = std::lower_bound(a + 1, a + n + 1, x + 1) - a - 1;
		if (to[i]) len[i] = c + x - a[to[i]];
		else to[i] = n, len[i] = l - a[to[i]] + x + c;
		add_edge(to[i], i);
	}
	for (int u = 1; u <= n; u++) if (!vis[u])
	{
		ToT++;
		for (int v = u; !vis[v]; v = to[v])
			if (vis[v] = ToT, vis[to[v]] == ToT)
			{
				tot++;
				for (int x = v; ; x = to[x])
					if (cir[bel[x] = tot].push_back(x), to[x] == v) break;
			}
	}
	for (int i = 1, j = 1, k = 1; i <= n + m; i++)
		d[i] = j <= n && (k > m || a[j] < b[k]) ? j++ : -(k++);
	for (int i = 1; i <= n + m; i++) if (d[i] > 0) x = d[i];
	for (int i = 1; i <= n + m; i++)
		if (d[i] > 0) x = d[i];
		else
		{
			int x1 = a[x], x2 = b[-d[i]];
			pt[x].push_back(x1 < x2 ? x2 - x1 : l - x1 + x2);
		}
	read(q);
	for (int i = 1; i <= q; i++) read(x), read(t), que[x].push_back((query) {i, x, t});
	for (int i = 1; i <= n; i++) if (!bel[i] && bel[to[i]])
		dis[i] = len[i], dfs(i, to[i]);
	for (int i = 1; i <= tot; i++) jiejuediao(i);
	for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
	return 0;
}

D3T3 Stray Cat UOJ

Solution:\(A\ge 3,B=0\)

\(B=0\) 表示每一步都必须沿着最短路走。

求出 \(0\) 到每个点 \(u\) 的最短路 \(dis_u\)

由于边权都为 \(1\),故对于任意一条边 \((u,v)\) 都满足 \(|dis_u-dis_v|\le 1\)

考虑对于边 \((u,v)\),假设它满足 \(dis_u\le dis_v\),把这条边的标记设为 \(dis_u\bmod 3\)

这样可以发现对于任意点 \(u>0\),它出发的边最多只有两种标记,其中一种标记 \(x\) 的边总是通向 \(dis\)\(u\) 小的点,另一种标记 \(y\) 的边总是通向 \(dis\) 不比 \(u\) 小的点,且如果 \(y\) 存在则必有 \((x+1)\bmod 3=y\)

于是可以根据当前点出发的标记种类来确定决策。

Solution:\(A=2,B=6,M=N-1\)

考虑继续沿用上面的方法,即如果 \(u\)\(v\) 的父亲,则把边 \((u,v)\) 的标记设为 \(dis_u\bmod 2\)

这样我们很容易得到决策:只要每次决策的标记和上一次不同,就可以走到目标。

但这样做会遇到的重要困难就是如果 \(S\) 的度数为 \(2\)(即只有一个子节点),就无法决定第一步往哪里走(否则若 \(y_0=1\) 就决策为 \(0\)\(y_1=1\) 就决策为 \(1\))。

从树是一条链的情况开始考虑,不妨尝试从 \(B=6\) 这个条件下手:我们可以用最多走错 \(3\) 步的代价确定往哪个方向走。

当树是一条链时,先往一个任意的方向一直走下去,如果在 \(3\) 步以内走到了叶子就说明走错了路,需要立刻转向。

设这条链上的标记从根到叶子形成的 \(01\) 字符串为 \(s\),则如果走了 \(3\) 步之后还没走到叶子,则在走错路的情况下我们能够得到 \(s\) 中的一个长度为 \(5\) 的子串(即经过的 \(4\) 个点所有相邻的边),走对路的情况下能够得到 \(s\) 的一个长度为 \(5\) 的子串的反串。

于是我们思考是否有一个性质足够好的字符串 \(s\),满足 \(s\) 的任意长度为 \(5\) 的子串 \(u\) 和任意长度为 \(5\) 的子串的反串 \(v\) 都满足 \(u\ne v\),这样我们就能通过走出的这个长度为 \(5\) 的串,判断出它是正串还是反串,从而得出是否走错了路。

答案是肯定的,令 \(s=(100110)^{\infty}\) 即可。

这样我们就解决了链的情况。

对于一般情况,对于一个不为根,度数也不为 \(2\) 的点 \(u\),我们还是让它满足 \((u,fa_u)\)\((u,son_u)\) 的标记不同。

而对于一条 \(u\)\(v\) 的极长的祖孙链(需要满足除 \(u\)\(v\) 之外所有点度数都为 \(2\)),我们让这条链的标记为 \(100110\) 的循环(如果 \((u,son_u)\) 的标记在之前已经被钦定为 \(1\))或 \(001101\) 的循环(如果 \((u,son_u)\) 的标记在之前已经被钦定为 \(0\))。可以按 DFS 顺序从上往下构造。

然后考虑怎么走。和树是链的情况一样,如果起点的度数不为 \(2\) 可以直接出发,否则找一个任意的方向出发,尝试走 \(3\) 步,如果 \(3\) 步之内走到了度数不为 \(2\) 的点,则根据 \(y\) 数组判定是否走错路(如果 \(y_0\)\(y_1\) 都不为 \(0\) 则走对了,否则走错),否则根据得到的长度为 \(5\)\(01\) 串判定是否走错路,如果走错路就返回起点从另一个方向出发,否则继续走。

注意一些实现细节,具体见代码。

Code Anthony

#include <bits/stdc++.h>
#include "Anthony.h"

namespace nealchen
{
	const int N = 2e4 + 5, M = N << 1, str[] = {1, 0, 0, 1, 1, 0};
	
	int n, m, a, b, ecnt = 1, nxt[M], adj[N], st[M], go[M], len, que[N], dis[N], w[N];
	bool vis[N];
	std::vector<int> res;
	
	void add_edge(int u, int v)
	{
		nxt[++ecnt] = adj[u]; adj[u] = ecnt; st[ecnt] = u; go[ecnt] = v;
		nxt[++ecnt] = adj[v]; adj[v] = ecnt; st[ecnt] = v; go[ecnt] = u;
	}
	
	void bfs()
	{
		vis[que[len = 1] = 1] = 1;
		for (int i = 1; i <= len; i++)
		{
			int u = que[i];
			for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
				if (!vis[v]) dis[que[++len] = v] = dis[u] + 1, vis[v] = 1;
		}
	}
	
	void dfs(int u, int fe)
	{
		int cnt = 0;
		for (int e = adj[u]; e; e = nxt[e]) if (e != fe) cnt++;
		if (cnt == 1 && u > 1)
		{
			if (w[u] == -1) w[u] = !res[(fe >> 1) - 1];
			for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e]) if (e != fe)
				w[v] = (w[u] + 1) % 6, res[(e >> 1) - 1] = str[w[v]], dfs(v, e ^ 1);
		}
		else for (int e = adj[u]; e; e = nxt[e]) if (e != fe)
			res[(e >> 1) - 1] = fe ? res[(fe >> 1) - 1] ^ 1 : 1, dfs(go[e], e ^ 1);
	}
	
	std::vector<int> init()
	{
		for (int i = 0; i < m; i++) res.push_back(-1);
		memset(w, -1, sizeof(w));
		if (a >= 3 && b == 0)
		{
			bfs();
			for (int e = 2; e <= ecnt; e += 2)
			{
				int u = st[e], v = go[e]; if (dis[u] > dis[v]) std::swap(u, v);
				res[(e >> 1) - 1] = dis[u] % 3;
			}
			return res;
		}
		return dfs(1, 0), res;
	}
}

std::vector<int> Mark(int N, int M, int A, int B, std::vector<int> U, std::vector<int> V)
{
	nealchen::n = N; nealchen::m = M; nealchen::a = A; nealchen::b = B;
	for (int i = 0; i < M; i++) nealchen::add_edge(U[i] + 1, V[i] + 1);
	return nealchen::init();
}

Code Catherine

#include <bits/stdc++.h>
#include "Catherine.h"

namespace Lagoon
{
	int a, b, lst, cnt, S; bool firs, right_dir, wrong_dir, ret;
}

int Move(std::vector<int> y)
{
	using namespace Lagoon;
	if (a >= 3 && b == 0)
	{
		if ((y[0] && !y[1] && !y[2]) || (y[0] && y[1] && !y[2])) return 0;
		if ((!y[0] && y[1] && !y[2]) || (!y[0] && y[1] && y[2])) return 1;
		if ((!y[0] && !y[1] && y[2]) || (y[0] && !y[1] && y[2])) return 2;
		return -1;
	}
	if (!firs)
	{
		firs = 1;
		if (y[0] + y[1] != 2)
		{
			if (y[0] && y[1] != 1) return right_dir = 1, lst = 0;
			if (y[0] != 1 && y[1]) return right_dir = 1, lst = 1;
		}
		return cnt = 1, S = (y[1] ? !y[0] : 0) | ((y[1] ? 1 : 0) << 1),
			lst = y[1] ? 1 : 0;
	}
	if (right_dir) return lst = (y[0] + y[1] == 1 ? y[1] : lst ^ 1);
	if (wrong_dir && !cnt) return right_dir = 1, lst = y[1];
	if (y[0] + y[1] != 1) (y[0] && y[1] ? right_dir : wrong_dir) = 1;
	else if (cnt == 3)
	{
		int s = S | (y[1] ? (1 << 4) : 0);
		(s == 25 || s == 12 || s == 22 || s == 11 ||
			s == 5 || s == 18 ? wrong_dir : right_dir) = 1;
	}
	if (right_dir) return lst = (y[0] + y[1] == 1 ? y[1] : lst ^ 1);
	if (wrong_dir) return lst = (S >> cnt) & 1, cnt--, (ret ? lst : (ret = 1, -1));
	return cnt++, S |= (y[1] ? (1 << cnt) : 0), lst = y[1];
}

void Init(int A, int B) {Lagoon::a = A; Lagoon::b = B;}

D4T1 Capital City LOJ UOJ

Solution

易得对于点 \(u\) 会有一个限制,也就是选颜色 \(C_u\) 就必须选 \(u\)\(lca_{C_u}\) 路径上出现过的所有颜色。\(lca_{c}\) 表示颜色为 \(c\) 的所有点的 LCA。

对每种颜色新建一个点,和所有这种颜色的点连成一个 SCC,就转化成一个点向一条祖先到子孙的路径连边,可以倍增优化建图。

答案即为没有出度且包含新点个数最少的 SCC 中新点的个数减 \(1\)

\(O(N\log N)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 2e5 + 5, E = 21, M = 1e7 + 5;

int n, k, ecnt, nxt[M], adj[M], go[M], dep[N], dfn[M], low[M], times, fa[N][E],
Log[N], bel[M], sze[M], ans = 1919810, tot, top, stk[M];
std::vector<int> a[N], son[N];
bool is[M];

int o1(int x, int l) {return x * 19 - l;}

int o2(int x) {return 19 * n + x;}

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}

void dfs(int u, int fu)
{
	dep[u] = dep[fa[u][0] = fu] + 1;
	for (int i = 0; i < 18; i++) fa[u][i + 1] = fa[fa[u][i]][i];
	for (int i = 0; i < son[u].size(); i++)
		if (son[u][i] != fu) dfs(son[u][i], u);
}

int lca(int u, int v)
{
	if (dep[u] < dep[v]) std::swap(u, v);
	for (int i = 18; i >= 0; i--)
		if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
	if (u == v) return u;
	for (int i = 18; i >= 0; i--)
		if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}

int jm(int u, int d)
{
	for (int i = 18; i >= 0; i--) if ((d >> i) & 1) u = fa[u][i];
	return u;
}

void tarjan(int u)
{
	dfn[u] = low[u] = ++times; stk[++top] = u;
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		if (!dfn[v]) tarjan(v), low[u] = std::min(low[u], low[v]);
		else if (!bel[v]) low[u] = std::min(low[u], dfn[v]);
	if (dfn[u] == low[u])
	{
		bel[u] = ++tot; sze[tot] = u > 19 * n; int v;
		while (v = stk[top--], v != u) bel[v] = tot, sze[tot] += v > 19 * n;
	}
}

int main()
{
	int x, y; read(n); read(k); Log[0] = -1;
	for (int i = 1; i <= n; i++) Log[i] = Log[i >> 1] + 1;
	for (int i = 1; i < n; i++) read(x), read(y),
		son[x].push_back(y), son[y].push_back(x);
	dfs(1, 0);
	for (int i = 1; i <= n; i++) read(x), a[x].push_back(i),
		add_edge(o1(i, 0), o2(x)), add_edge(o2(x), o1(i, 0));
	for (int i = 1; i <= k; i++)
	{
		int u = 0;
		for (int j = 0; j < a[i].size(); j++)
			u = j ? lca(u, a[i][j]) : a[i][j];
		for (int j = 0; j < a[i].size(); j++)
		{
			int D = dep[a[i][j]] - dep[u], k = Log[D + 1];
			add_edge(o2(i), o1(a[i][j], k));
			add_edge(o2(i), o1(jm(a[i][j], D - (1 << k) + 1), k));
		}
	}
	for (int u = 1; u <= n; u++)
		for (int T = 1; T <= 18; T++)
			if (dep[u] >= (1 << T)) add_edge(o1(u, T), o1(u, T - 1)),
				add_edge(o1(u, T), o1(fa[u][T - 1], T - 1));
	for (int u = 1; u <= 19 * n + k; u++) if (!dfn[u]) tarjan(u);
	for (int u = 1; u <= 19 * n + k; u++)
		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
			if (bel[u] != bel[v]) is[bel[u]] = 1;
	for (int i = 1; i <= tot; i++) if (!is[i] && sze[i] && sze[i] < ans) ans = sze[i];
	return std::cout << ans - 1 << std::endl, 0;
}

D4T2 Legendary Dango Maker LOJ UOJ

Solution

找出所有的美丽串,有占用相同格子的美丽串之间连边,我们要求的就是这张图的最大独立集。

众所周知最大独立集是一个 NP-Hard 问题,此题我们考虑模拟退火。

每次扩展的方式为:在当前状态下随机一个新点,把这个点加入,并把这个点相邻的所有点删掉,如果比当前状态优就转移,否则以一定概率转移。

本人取的参数是初温为 \(1145141919810\),末温为 \(10^{-200}\),温度变化率为 \(0.999999\),对于每个温度扩展 \(5\) 次,大概跑个十几分钟就跑出来了(

这样可以得到除第 \(5\) 个点之外的满分解。对于第 \(5\) 个点,需要以第一遍得出的解为初态再跑一遍退火。

Code

下面为退火部分的代码:

long double T = 1145141919810.0;
while (T > 1e-200)
{
	for (int W = 1; W <= 5; W++)
	{
		int u;
		while (u = (rand() << 15 | rand()) % tot + 1, vis[u]);
		vis[u] = 1; int mcur = cur + 1;
		std::vector<int> orz;
		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
			if (vis[v]) vis[v] = 0, mcur--, orz.push_back(v);
		if (mcur > cur)
		{
			cur = mcur; if (cur > ans) ans = cur;
			if (ans >= Z)
			{
				for (int i = 1; i <= tot; i++) if (vis[i]) s[X[i]][Y[i]] = mark[i];
				for (int i = 1; i <= n; i++)
				{
					for (int j = 1; j <= m; j++) putchar(s[i][j]);
					printf("\n");
				}
				return 0;
			}
		}
		else if (rand() < exp((mcur - cur) / T) * RAND_MAX)
			cur = mcur;
		else
		{
			vis[u] = 0;
			for (int i = 0; i < orz.size(); i++) vis[orz[i]] = 1;
		}
	}
	T *= 0.999999;
}

D4T3 Treatment Project LOJ UOJ

Solution

把时间看作 \(X\) 轴,位置看作 \(Y\) 轴,问题转化为有 \(M\) 个形如 \(X=T,Y\in[L,R]\) 的障碍物,求最小放障碍物的代价之和使得直线 \(X=0\) 上的点无法到达 \(X=+\infty\)。注意 \((X,Y)\) 可以到达 \((X+1,Y-1)(X+1,Y)(X+1,Y+1)\) 三点。

这很像平面图最小割问题,尝试转成最短路。

考虑转成这样的模型:

(1)起点满足 \(L=1\),终点满足 \(R=N\)

(2)\((T_1,L_1,R_1)\) 连向 \((T_2,L_2,R_2)\) 当且仅当 \(L_2\le R_1+1-|T_1-T_2|\),画出来为 \((T_2,L_2)\) 在一个等腰直角三角形内,这样保证了不会从 \((T_1,R_1)\)\((T_2,L_2)\) 的缝隙之间穿过去。

将坐标旋转 \(45\) 度之后就变成一个点向一个矩形连边,可以使用 「NOI2019」弹跳 的技巧使用线段树 + std::set 做到 \(O(M\log^2M)\) 的复杂度。

Code

#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

typedef long long ll;

const int N = 2e5 + 5, M = N << 2;
const ll INF = 1e18;

int m, n, nx, ny, t[N], l[N], r[N], c[N], px[M], py[M];
ll dis[N], ans = INF;

struct point
{
	int x, y;
} le[N], ri[N];

struct elem {int x; friend inline bool operator < (elem i, elem j)
{return le[i.x].y < le[j.x].y || (le[i.x].y == le[j.x].y && i.x < j.x);}};

struct node
{
	int u; ll dis;
	
	friend inline bool operator < (node x, node y)
	{
		return x.dis > y.dis;
	}
};

std::priority_queue<node> pq;

std::set<elem> pts[M];

void ins(int l, int r, int pos, int i, int p)
{
	pts[p].insert((elem) {i}); if (l == r) return;
	int mid = l + r >> 1;
	if (pos <= mid) ins(l, mid, pos, i, p2);
	else ins(mid + 1, r, pos, i, p3);
}

void del(int l, int r, int pos, int i, int p)
{
	pts[p].erase(pts[p].find((elem) {i})); if (l == r) return;
	int mid = l + r >> 1;
	if (pos <= mid) del(l, mid, pos, i, p2);
	else del(mid + 1, r, pos, i, p3);
}

int fp(int l, int r, int pos, int p)
{
	if (l == r) return pts[p].empty() ? 0 : pts[p].begin()->x;
	int mid = l + r >> 1;
	if (pos <= mid) return fp(l, mid, pos, p2);
	int x1 = pts[p2].empty() ? 0 : pts[p2].begin()->x, x2 = fp(mid + 1, r, pos, p3);
	return le[x1].y < le[x2].y ? x1 : x2;
}

int main()
{
	read(m); read(n); le[0].y = 1919810;
	for (int i = 1; i <= n; i++) read(t[i]), read(l[i]), read(r[i]), read(c[i]), r[i]++,
		le[i] = (point) {l[i] - t[i], l[i] + t[i]},
			ri[i] = (point) {r[i] - t[i], r[i] + t[i]},
				px[++nx] = le[i].x, px[++nx] = ri[i].x,
					py[++ny] = le[i].y, py[++ny] = ri[i].y, dis[i] = INF;
	std::sort(px + 1, px + nx + 1); nx = std::unique(px + 1, px + nx + 1) - px - 1;
	std::sort(py + 1, py + ny + 1); ny = std::unique(py + 1, py + ny + 1) - py - 1;
	for (int i = 1; i <= n; i++)
	{
		le[i].x = std::lower_bound(px + 1, px + nx + 1, le[i].x) - px;
		le[i].y = std::lower_bound(py + 1, py + ny + 1, le[i].y) - py;
		ri[i].x = std::lower_bound(px + 1, px + nx + 1, ri[i].x) - px;
		ri[i].y = std::lower_bound(py + 1, py + ny + 1, ri[i].y) - py;
		ins(1, nx, le[i].x, i, 1);
	}
	for (int i = 1; i <= n; i++) if (l[i] == 1) pq.push((node) {i, dis[i] = c[i]}),
		del(1, nx, le[i].x, i, 1);
	while (!pq.empty())
	{
		node t = pq.top(); pq.pop(); int i = t.u, j;
		while (j = fp(1, nx, ri[i].x, 1), le[j].y <= ri[i].y)
			del(1, nx, le[j].x, j, 1), pq.push((node) {j, dis[j] = dis[i] + c[j]});
	}
	for (int i = 1; i <= n; i++) if (r[i] > m && dis[i] < ans) ans = dis[i];
	return std::cout << (ans == INF ? -1 : ans) << std::endl, 0;
}
posted @ 2020-08-04 21:18  epic01  阅读(472)  评论(0编辑  收藏  举报