[题解][LOJ #3156~#3161]NOI2019 简要题解集合

  • 这次 NOI 成功把我这个场外菜鸡选手区分出去了 QwQwQ

Address

D1T1

Solution:斜率优化 DP

  • 定义状态 f [ u ] [ i ] f[u][i] f[u][i] 表示 i i i 时刻在点 u u u 刚刚上车
  • g [ u ] [ i ] g[u][i] g[u][i] 表示 i i i 时刻在点 u u u 刚刚下车
  • 显然有用的状态只有 O ( m ) O(m) O(m)
  • 对于 f [ u ] [ i ] f[u][i] f[u][i] 直接枚举其在哪个时刻哪个点下车即可转移
  • 对于 g [ u ] [ i ] g[u][i] g[u][i] 我们有
  • g [ u ] [ i ] = min ⁡ j ≥ i { g [ u ] [ j ] + A × ( j − i ) 2 + B × ( j − i ) + C } g[u][i]=\min_{j\ge i}\{g[u][j]+A\times(j-i)^2+B\times(j-i)+C\} g[u][i]=jimin{g[u][j]+A×(ji)2+B×(ji)+C}
  • 这是一个显然的斜率优化,对每个点 u u u 开一个单调队列维护凸壳即可解决问题
  • O ( n + m ) O(n+m) O(n+m)
  • 只是不知道为什么 sb 暴力能水到 95 ~ 100 分

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 Min(const T &a, const T &b) {return a < b ? a : b;}

typedef long long ll;

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

int n, m, A, B, C, tot = 1, ecnt, nxt[M], adj[M], go[M], lst[N], len[N];
ll f[M], g[M];

struct point
{
	int u, tm, id;
} a[M];

struct pt
{
	ll x, y;
} p[M];

std::vector<int> conv[N];
std::map<int, int> tim[N];

bool check(int i, int j, int k)
{
	return (p[j].x - p[i].x) * (p[k].y - p[i].y)
		- (p[j].y - p[i].y) * (p[k].x - p[i].x) <= 0;
}

ll calc(int t, int i)
{
	return p[i].y - p[i].x * A * 2 * t;
}

inline bool comp(point a, point b)
{
	return a.tm > b.tm;
}

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

int main()
{
	int x, y, s, t;
	read(n); read(m); read(A); read(B); read(C);
	a[tot = 1] = (point) {1, 0, 1}; tim[1][0] = 1;
	while (m--)
	{
		read(x); read(y); read(s); read(t);
		if (!tim[x].count(s)) tot++, a[tim[x][s] = tot] = (point) {x, s, tot};
		if (!tim[y].count(t)) tot++, a[tim[y][t] = tot] = (point) {y, t, tot};
		add_edge(tim[x][s], tim[y][t]);
	}
	std::sort(a + 1, a + tot + 1, comp);
	for (int i = 1; i <= tot; i++)
	{
		int u = a[i].id, t = a[i].tm, x = a[i].u;
		if (x == n) {g[u] = t; continue;}
		f[u] = g[u] = 1e18;
		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
			f[u] = Min(f[u], g[v]);
		if (f[u] < 1e18)
		{
			p[u] = (pt) {t, f[u] + A * t * t + B * t};
			while (lst[x] + 1 < len[x]
				&& check(u, conv[x][len[x] - 1], conv[x][len[x] - 2]))
					len[x]--, conv[x].pop_back();
			len[x]++; conv[x].push_back(u);
		}
		while (lst[x] + 1 < len[x]
			&& calc(t, conv[x][lst[x]]) >= calc(t, conv[x][lst[x] + 1]))
				lst[x]++;
		if (lst[x] < len[x])
			g[u] = calc(t, conv[x][lst[x]]) + A * t * t - B * t + C;
	}
	return std::cout << g[1] << std::endl, 0;
}

D1T2

Solution:DP + 多项式插值

  • 不难想到 50 分的 DP
  • f [ l ] [ r ] [ i ] f[l][r][i] f[l][r][i] 表示区间 [ l , r ] [l,r] [l,r] 的最大值为 i i i 的方案数
  • f [ l ] [ r ] [ i ] = ∑ l ≤ m i d ≤ r , A m i d ≤ i ≤ B m i d , ∣ ( m i d − l ) − ( r − m i d ) ∣ ≤ 2 ( ∑ j ≤ i f [ l ] [ m i d − 1 ] [ j ] ) × ( ∑ j &lt; i f [ m i d + 1 ] [ r ] j ] ) f[l][r][i]=\sum_{l\le mid\le r,A_{mid}\le i\le B_{mid},|(mid-l)-(r-mid)|\le 2}(\sum_{j\le i}f[l][mid-1][j])\times(\sum_{j&lt;i}f[mid+1][r]j]) f[l][r][i]=lmidr,AmidiBmid,(midl)(rmid)2(jif[l][mid1][j])×(j<if[mid+1][r]j])
  • 由于有 ∣ ( m i d − l ) − ( r − m i d ) ∣ ≤ 2 |(mid-l)-(r-mid)|\le 2 (midl)(rmid)2 的限制
  • 所以实际上合法的 [ l , r ] [l,r] [l,r] 很少,可拿 50 分
  • 对于取值范围很大的情况,我们也不难想到把所有的取值区间离散化成 O ( n ) O(n) O(n)
  • f [ l ] [ r ] [ i ] [ j ] f[l][r][i][j] f[l][r][i][j] 表示区间 [ l , r ] [l,r] [l,r] 的最大值在第 i i i 段内且为 j j j
  • 这看上去仿佛复杂度更劣了,但是可以通过简单的归纳得出
  • j j j 位于第 i i i 段内的前提下, f [ l ] [ r ] [ i ] [ j ] f[l][r][i][j] f[l][r][i][j] 是关于 j j j r − l r-l rl 次多项式
  • 所以利用多项式进行 DP , F [ l ] [ r ] [ i ] F[l][r][i] F[l][r][i] 表示区间 [ l , r ] [l,r] [l,r] 的最大值在第 i i i 段内,方案数关于最大值的 r − l r-l rl 次多项式
  • 回到一开始的转移
  • 发现如果 A m i d ≤ i ≤ B m i d A_{mid}\le i\le B_{mid} AmidiBmid (这里的 A A A B B B 是离散过的)
  • 那么我们相当于要对 F [ l ] [ m i d − 1 ] [ i ] F[l][mid-1][i] F[l][mid1][i] F [ m i d + 1 ] [ r ] [ i ] F[mid+1][r][i] F[mid+1][r][i] 各求一遍多项式前缀和之后乘起来
  • (多项式 f ( x ) f(x) f(x) 的前缀和即为 g ( x ) = ∑ i = 0 x f ( i ) g(x)=\sum_{i=0}^xf(i) g(x)=i=0xf(i)
  • 对于多项式前缀和
  • 我们可以先通过插值,预处理出多项式 f k ( x ) = ∑ i = 0 x i k f_k(x)=\sum_{i=0}^xi^k fk(x)=i=0xik
  • 这样就能 O ( l 2 ) O(l^2) O(l2) 地求一个 l l l 次多项式的前缀和
  • 其他实现细节略
  • 使用记忆化搜索实现 DP ,复杂度为
  • O ( n ∑ 状 态 F [ l ] [ r ] [ … &ThinSpace; ] 有 效 ( r − l ) 2 ) O(n\sum_{状态F[l][r][\dots]有效}(r-l)^2) O(nF[l][r][](rl)2)
  • 复杂度看上去好像不太好分析
  • 由于 ∣ ( m i d − l ) − ( r − m i d ) ∣ ≤ 2 |(mid-l)-(r-mid)|\le 2 (midl)(rmid)2
  • [ l , r ] [l,r] [l,r] 转到 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 ] [ r ] [mid+1][r] [mid+1][r] 转不超过 O ( log ⁡ n ) O(\log n) O(logn) 次之后会转到 [ i , i ] [i,i] [i,i]
  • [ l , r ] [l,r] [l,r] 确定的情况下合法的 m i d mid mid 最多 3 3 3
  • 所以转移到第 i i i 层会有不超过 O ( 3 i ) O(3^i) O(3i) 个长度为 O ( n 2 i ) O(\frac n{2^i}) O(2in) 的区间 ,其中 [ 1 , n ] [1,n] [1,n] 在第 0 0 0
  • 所以复杂度不超过
  • O ( n ∑ 状 态 F [ l ] [ r ] [ … &ThinSpace; ] 有 效 ( r − l ) 2 ) ≤ O ( n ∑ i = 0 log ⁡ n ( n 2 i ) 2 × 3 i ) O(n\sum_{状态F[l][r][\dots]有效}(r-l)^2)\le O(n\sum_{i=0}^{\log n}(\frac n{2^i})^2\times3^i) O(nF[l][r][](rl)2)O(ni=0logn(2in)2×3i)
  • O ( n 3 ∑ i = 0 log ⁡ n ( 3 4 ) i ) = O ( n 3 ) O(n^3\sum_{i=0}^{\log n}(\frac34)^i)=O(n^3) O(n3i=0logn(43)i)=O(n3)

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 = 305, M = N << 1, L = 2250, ZZQ = 1e9 + 7;

int n, a[N], b[N], real[M], tot, prod[N], tmp[N], mpt[N], pmt[N], ans, id[N][N], T;

std::vector<int> f[L][M], cof[N];

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % ZZQ;
		a = 1ll * a * a % ZZQ;
		b >>= 1;
	}
	return res;
}

void calc(int n)
{
	for (int i = 0; i <= n; i++) prod[i] = !i, tmp[i] = 0;
	for (int i = 0; i < n; i++)
		for (int j = i; j >= 0; j--)
			prod[j + 1] = (prod[j + 1] + prod[j]) % ZZQ,
			prod[j] = (1ll * ZZQ * ZZQ - 1ll * prod[j] * i) % ZZQ;
	int res = 0;
	for (int i = 0; i < n; i++)
	{
		res = (res + qpow(i, n - 2)) % ZZQ;
		for (int j = 0; j <= n; j++) mpt[j] = prod[j];
		int divi = 1;
		for (int j = 0; j < n; j++) if (i != j)
			divi = 1ll * divi * (i - j + ZZQ) % ZZQ;
		divi = qpow(divi, ZZQ - 2);
		for (int j = n; j > 0; j--)
			pmt[j - 1] = mpt[j], mpt[j - 1] = (1ll * i * mpt[j] + mpt[j - 1]) % ZZQ;
		for (int j = 0; j < n; j++)
			tmp[j] = (1ll * divi * pmt[j] % ZZQ * res + tmp[j]) % ZZQ;
	}
	for (int i = 0; i < n; i++) cof[n - 2].push_back(tmp[i]);
}

int calcval(std::vector<int> A, int x)
{
	int n = A.size(), res = 0;
	for (int i = n - 1; i >= 0; i--)
		res = (1ll * res * x + A[i]) % ZZQ;
	return res;
}

std::vector<int> poly_sum(std::vector<int> A, bool is)
{
	std::vector<int> res;
	int n = A.size();
	for (int i = 0; i <= n; i++) res.push_back(0);
	for (int i = 0; i < n; i++)
		for (int j = 0; j <= i + 1; j++)
			res[j] = (1ll * A[i] * cof[i][j] + res[j]) % ZZQ;
	if (is) for (int i = 0; i < n; i++)
		res[i] = (res[i] - A[i] + ZZQ) % ZZQ;
	return res;
}

void DP(int l, int r)
{
	if (l > r || id[l][r]) return;
	id[l][r] = ++T; int u = id[l][r];
	for (int i = 1; i < tot; i++)
		for (int j = 0; j <= r - l; j++)
			f[u][i].push_back(0);
	for (int mid = l; mid <= r; mid++)
	{
		if (abs((mid << 1) - l - r) > 2) continue;
		DP(l, mid - 1); DP(mid + 1, r);
		int pl = 0, pr = 0;
		for (int i = 1; i < tot; i++)
		{
			std::vector<int> poly_l, poly_r;
			if (l < mid)
			{
				poly_l = poly_sum(f[id[l][mid - 1]][i], 0);
				int delta = (pl - calcval(poly_l, real[i] - 1) + ZZQ) % ZZQ;
				poly_l[0] = (poly_l[0] + delta) % ZZQ;
			}
			else poly_l.push_back(1);
			if (mid < r)
			{
				poly_r = poly_sum(f[id[mid + 1][r]][i], 1);
				int delta = (pr - calcval(poly_r, real[i]) + ZZQ) % ZZQ;
				poly_r[0] = (poly_r[0] + delta) % ZZQ;
			}
			else poly_r.push_back(1);
			if (a[mid] <= i && i <= b[mid])
			{
				std::vector<int> delta;
				for (int j = 0; j <= r - l; j++) delta.push_back(0);
				for (int j = 0; j < poly_l.size(); j++)
					for (int k = 0; k < poly_r.size(); k++)
						delta[j + k] = (1ll * poly_l[j] * poly_r[k]
							+ delta[j + k]) % ZZQ;
				for (int j = 0; j <= r - l; j++)
					f[u][i][j] = (f[u][i][j] + delta[j]) % ZZQ;
			}
			if (l < mid) pl = calcval(poly_l, real[i + 1] - 1);
			if (mid < r) pr = calcval(poly_r, real[i + 1]);
		}
	}
}

int main()
{
	read(n);
	for (int i = 1; i <= n; i++)
		read(a[i]), read(b[i]),
		real[++tot] = a[i], real[++tot] = b[i] + 1;
	std::sort(real + 1, real + tot + 1);
	tot = std::unique(real + 1, real + tot + 1) - real - 1;
	for (int i = 1; i <= n; i++)
	{
		a[i] = std::lower_bound(real + 1, real + tot + 1, a[i]) - real;
		b[i] = std::lower_bound(real + 1, real + tot + 1, b[i] + 1) - real - 1;
	}
	for (int i = 2; i <= n + 1; i++) calc(i);
	DP(1, n);
	for (int i = 1; i < tot; i++)
	{
		std::vector<int> res = poly_sum(f[1][i], 0);
		ans = (ans + (calcval(res, real[i + 1] - 1)
			- calcval(res, real[i] - 1) + ZZQ) % ZZQ) % ZZQ;
	}
	return std::cout << ans << std::endl, 0;
}

D1T3

Solution:模拟费用流(实际上是贪心)

  • 好题, orz 现场切掉此题的神仙们
  • 考虑构建一张网络流图
  • 所有 n n n 个点拆成两个 i 1 i_1 i1 i 2 i_2 i2
  • S S S 向所有的 i 1 i_1 i1 连边,容量 1 1 1 费用 a i a_i ai
  • 所有的 i 2 i_2 i2 T T T 连边,容量 1 1 1 费用 b i b_i bi
  • i 1 i_1 i1 i 2 i_2 i2 连边,容量 1 1 1 费用 0 0 0
  • 外加两个虚拟点 A A A B B B
  • &lt; i 1 , A , 1 , 0 &gt; &lt;i_1,A,1,0&gt; <i1,A,1,0>
  • &lt; B , i 2 , 1 , 0 &gt; &lt;B,i_2,1,0&gt; <B,i2,1,0>
  • &lt; A , B , K − L , 0 &gt; &lt;A,B,K-L,0&gt; <A,B,KL,0>
  • 不难发现这个图跑 K K K 的流量后的最大费用就是答案
  • 考虑每次只增广 1 1 1 的流量,可以发现有五种情况
  • (1) S → i 1 → i 2 → T S\rightarrow i_1\rightarrow i_2\rightarrow T Si1i2T
  • 在序列上对应了当 a i a_i ai b i b_i bi 都还没被选上时,将 a i a_i ai b i b_i bi 都选上
  • (2) S → i 1 → A → B → j 2 → T S\rightarrow i_1\rightarrow A\rightarrow B\rightarrow j_2\rightarrow T Si1ABj2T
  • 在序列上即当下标不对应的对数 c n t &lt; K − L cnt&lt;K-L cnt<KL 时将 a i a_i ai b j b_j bj 都选上,然后 c n t cnt cnt 加一
  • 不过这里我们为了保证 c n t cnt cnt 一定会加一,钦定这时 i ≠ j i\ne j i̸=j a j a_j aj b i b_i bi 都没有被选过
  • (3) S → i 1 → i 2 → B → A → j 1 → j 2 → T S\rightarrow i_1\rightarrow i_2\rightarrow B\rightarrow A\rightarrow j_1\rightarrow j_2\rightarrow T Si1i2BAj1j2T
  • 这在序列上还是对应将 a i a_i ai b j b_j bj 都选上,但是没有 c n t &lt; K − L cnt&lt;K-L cnt<KL 的限制,且 c n t cnt cnt 这时要减一
  • 且在这之前 a j a_j aj b i b_i bi 都已经被选上
  • (4) S → i 1 → A → j 1 → j 2 → T S\rightarrow i_1\rightarrow A\rightarrow j_1\rightarrow j_2\rightarrow T Si1Aj1j2T
  • c n t cnt cnt 不变,把 a i a_i ai b j b_j bj 都选上,需要保证 a j a_j aj 已经被选上但 b i b_i bi 还未被选上
  • (5) S → i 1 → i 2 → B → j 2 → T S\rightarrow i_1\rightarrow i_2\rightarrow B\rightarrow j_2\rightarrow T Si1i2Bj2T
  • c n t cnt cnt 不变,把 a i a_i ai b j b_j bj 都选上,需要保证 a j a_j aj 还未被选上但 b i b_i bi 已经被选上
  • (6) S → i 1 → A → j 1 → j 2 → B → k 2 → T S\rightarrow i_1\rightarrow A\rightarrow j_1\rightarrow j_2\rightarrow B\rightarrow k_2\rightarrow T Si1Aj1j2Bk2T
  • 这种情况一定与之前讨论的情况中的一种等价,故这种情况没有实际意义
  • 我们有了一个贪心策略,即重复 K K K 次,下面四种情况中选贡献最大的
  • (1)选出 a i a_i ai b i b_i bi
  • (2)在 a j a_j aj b i b_i bi 未被选出且 c n t &lt; K − L cnt&lt;K-L cnt<KL 的情况下,选出 a i a_i ai b j b_j bj ,并让 c n t cnt cnt 加一
  • (3)在 a j a_j aj b i b_i bi 都被选出的情况下,选出 a i a_i ai b j b_j bj ,并让 c n t cnt cnt 减一
  • (4)在 a j a_j aj b i b_i bi 恰有其一被选出的情况下,选出 a i a_i ai b j b_j bj ,这时 c n t cnt cnt 不变
  • 用几个优先队列即可实现
  • O ( ( ∑ n ) log ⁡ n ) O((\sum n)\log n) O((n)logn)

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;

int n, k, l, a[N], b[N];
bool visa[N], visb[N];

struct node
{
	int id, val;
	
	friend inline bool operator < (node a, node b)
	{
		return a.val < b.val;
	}
} INF;

std::priority_queue<node> no_ab, no_a, no_b, is_a, is_b;

void work()
{
	ll ans = 0; int cnt = 0;
	read(n); read(k); read(l);
	for (int i = 1; i <= n; i++) read(a[i]);
	for (int i = 1; i <= n; i++) read(b[i]);
	memset(visa, 0, sizeof(visa)); memset(visb, 0, sizeof(visb));
	while (!no_ab.empty()) no_ab.pop(); while (!no_a.empty()) no_a.pop();
	while (!no_b.empty()) no_b.pop(); while (!is_a.empty()) is_a.pop();
	while (!is_b.empty()) is_b.pop();
	for (int i = 1; i <= n; i++)
		no_ab.push((node) {i, a[i] + b[i]}),
		no_a.push((node) {i, a[i]}), no_b.push((node) {i, b[i]});
	for (int i = 1; i <= k; i++)
	{
		while (!no_ab.empty() && (visa[no_ab.top().id] || visb[no_ab.top().id]))
			no_ab.pop();
		while (!no_a.empty() && (visa[no_a.top().id] || visb[no_a.top().id]))
			no_a.pop();
		while (!no_b.empty() && (visa[no_b.top().id] || visb[no_b.top().id]))
			no_b.pop();
		while (!is_a.empty() && (visa[is_a.top().id] || !visb[is_a.top().id]))
			is_a.pop();
		while (!is_b.empty() && (!visa[is_b.top().id] || visb[is_b.top().id]))
			is_b.pop();
		node s1 = no_ab.empty() ? INF : no_ab.top(), s2, s3, s4, s5;
		if (no_a.empty() || no_b.empty()) s2 = s3 = INF;
		else s2 = no_a.top(), s3 = no_b.top();
		if (is_a.empty() || is_b.empty()) s4 = s5 = INF;
		else s4 = is_a.top(), s5 = is_b.top();
		if ((cnt == k - l || s1.val >= s2.val + s3.val) && s1.val >= s4.val + s5.val
			&& s1.val >= s4.val + s3.val && s1.val >= s2.val + s5.val)
		{
			ans += s1.val; visa[s1.id] = visb[s1.id] = 1;
			continue;
		}
		if (s2.val + s3.val > s4.val + s5.val && s2.val > s4.val && s3.val > s5.val
			&& cnt < k - l)
		{
			ans += s2.val + s3.val; visa[s2.id] = visb[s3.id] = 1;
			is_a.push((node) {s3.id, a[s3.id]});
			is_b.push((node) {s2.id, b[s2.id]});
			cnt++; continue;
		}
		if (s5.val > s3.val && s4.val > s2.val)
		{
			ans += s4.val + s5.val; visa[s4.id] = visb[s5.id] = 1;
			cnt--; continue;
		}
		if (s4.val + s3.val > s2.val + s5.val)
			ans += s4.val + s3.val, visa[s4.id] = visb[s3.id] = 1,
			is_a.push((node) {s3.id, a[s3.id]});
		else ans += s2.val + s5.val, visa[s2.id] = visb[s5.id] = 1,
			is_b.push((node) {s2.id, b[s2.id]});
	}
	printf("%lld\n", ans);
}

int main()
{
	INF = (node) {0, -0x3f3f3f3f};
	int T; read(T);
	while (T--) work();
	return 0;
}

D2T1

Solution:K-D Tree + Dijkstra 单源最短路

  • 看到二维区间以及空间限制只有 128M ,不难想到 K-D Tree 优化连边
  • 注意到 Dijikstra 的算法流程中重复的一步是从优先队列中取出一个点 u u u
  • 然后更新与 u u u 相连的点的最短路
  • 而回到本题,一个二维区间可以表示成 K-D Tree 上的 O ( n ) O(\sqrt n) O(n ) 个子树
  • 于是取出点 u u u 后转移到这 O ( n ) O(\sqrt n) O(n ) 个子树即可,具体地,可以在这些子树上打取 min ⁡ \min min 的标记
  • O ( n log ⁡ n + m n ) O(n\log n+m\sqrt n) O(nlogn+mn )
  • 一个剪枝:在 K-D Tree 上查找 O ( n ) O(\sqrt n) O(n ) 个子树时如果无法更新子树里任何点的 d i s dis dis 就 return 掉
  • 建议把 K-D Tree 写成线段树的形式,使得点 u u u 转移到的一定是一些子树而没有孤立点

Code

#include <bits/stdc++.h>

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

template <class T>
inline void write(const T &res)
{
	if (res > 9) write(res / 10);
	putchar(res % 10 + '0');
}

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 = 7e4 + 5, M = N << 1, W = 15e4 + 5, INF = 0x3f3f3f3f;

int n, m, w, h, X[N], Y[N], op, lc[M], rc[M], minx[M], maxx[M], miny[M], maxy[M],
tot, pt[M], dis[M], Root, t[W], L[W], R[W], D[W], U[W], cnt, pts, tmp,
mark[M];
bool vis[M];

std::vector<int> bel[N];

struct point
{
	int x, y, id;
} a[N];

struct node
{
	int u, dis;
	
	friend inline bool operator < (const node &a, const node &b)
	{
		return a.dis > b.dis;
	}
};

std::priority_queue<node> pq;

inline bool comp(const point &a, const point &b)
{
	if (!op) return a.x < b.x || (a.x == b.x && a.y < b.y);
	else return a.y < b.y || (a.y == b.y && a.x < b.x);
}

int build(int l, int r, int dir)
{
	int p;
	if (l == r)
	{
		p = a[l].id;
		minx[p] = maxx[p] = a[l].x;
		miny[p] = maxy[p] = a[l].y;
		mark[p] = INF;
		return p;
	}
	int mid = l + r >> 1; op = dir;
	std::sort(a + l, a + r + 1, comp);
	p = ++pts;
	lc[p] = build(l, mid, dir ^ 1);
	rc[p] = build(mid + 1, r, dir ^ 1);
	minx[p] = Min(minx[lc[p]], minx[rc[p]]);
	maxx[p] = Max(maxx[lc[p]], maxx[rc[p]]);
	miny[p] = Min(miny[lc[p]], miny[rc[p]]);
	maxy[p] = Max(maxy[lc[p]], maxy[rc[p]]);
	mark[p] = INF;
	return p;
}

inline void relax(const int &u, const int &d)
{
	if (!vis[u] && d < dis[u]) dis[u] = d, pq.push((node) {u, d});
}

inline void get_point(const int &l, const int &r, const int &d,
	const int &u, const int &p)
{
	if (r < minx[p] || l > maxx[p] || u < miny[p] || d > maxy[p]
		|| mark[p] <= tmp) return;
	if (l <= minx[p] && maxx[p] <= r && d <= miny[p] && maxy[p] <= u)
		return mark[p] = Min(mark[p], tmp), relax(p, tmp);
	mark[lc[p]] = Min(mark[lc[p]], mark[p]);
	mark[rc[p]] = Min(mark[rc[p]], mark[p]);
	get_point(l, r, d, u, lc[p]); get_point(l, r, d, u, rc[p]);
}

int main()
{
	int x;
	read(n); read(m); read(w); read(h); pts = n;
	for (int i = 1; i <= n; i++)
		read(a[i].x), read(a[i].y), a[i].id = i,
		X[i] = a[i].x, Y[i] = a[i].y;
	for (int i = 1; i <= m; i++)
	{
		read(x); read(t[i]); read(L[i]);
		read(R[i]); read(D[i]); read(U[i]);
		bel[x].push_back(i);
	}
	memset(dis, INF, sizeof(dis));
	dis[1] = 0; pq.push((node) {1, 0});
	Root = build(1, n, 1);
	while (!pq.empty())
	{
		node x = pq.top(); pq.pop();
		int u = x.u;
		if (vis[u]) continue; vis[u] = 1;
		if (lc[u]) relax(lc[u], dis[u]), mark[lc[u]] = Min(mark[lc[u]], mark[u]);
		if (rc[u]) relax(rc[u], dis[u]), mark[rc[u]] = Min(mark[rc[u]], mark[u]);
		if (u > n) continue;
		for (int i = 0; i < bel[u].size(); i++)
		{
			int it = bel[u][i];
			tmp = dis[u] + t[it];
			get_point(L[it], R[it], D[it], U[it], Root);
		}
	}
	for (int i = 2; i <= n; i++) write(dis[i]), putchar('\n');
	return 0;
}

D2T2

Solution:组合数学 + DP

  • 这是一个打表乱搞可过的题,不过这里讲正解
  • 不难发现每次操作是把前 A A A 张牌和后 n − A n-A nA 张牌等概率随机地归并
  • 即前 A A A 张牌之间相对顺序不改变,后 n − A n-A nA 张也不变
  • 考虑这个操作的逆过程
  • 也就是这 n n n 张牌中随机选 A A A 张牌放到前面,剩下 n − A n-A nA 张牌放到后面
  • 连续 m m m 次操作就相当于 b a s e base base 2 2 2 的基数排序
  • 于是问题转化为有 n n n m m m 位二进制数,对于从高到低(以下省略)第 i i i 位,随机 n n n 个数中的 A i A_i Ai 个该位为 0 0 0 ,其他数的该位为 1 1 1
  • 询问转化为求第 c c c 个二进制数的排名的 t y p e type type 次幂的期望值
  • (排名为数值第一关键字,下标第二关键字)
  • 如果 t y p e = 1 type=1 type=1
  • 那么可以预处理出对于任意 1 ≤ i &lt; j ≤ n 1\le i&lt;j\le n 1i<jn ,第 i i i 和第 j j j 个数相同的概率(显然对于所有的 i , j i,j i,j 都一样),记为 p p p
  • 那么第 i i i 个数严格小于第 j j j 个数的概率就是 1 − p 2 \frac{1-p}2 21p
  • i i i 个数小于等于第 j j j 个数的概率为 1 + p 2 \frac{1+p}2 21+p
  • 询问结果就是
  • ( c − 1 ) × 1 + p 2 + ( n − c ) × 1 − p 2 + 1 (c-1)\times\frac{1+p}2+(n-c)\times\frac{1-p}2+1 (c1)×21+p+(nc)×21p+1
  • 否则 t y p e = 2 type=2 type=2
  • 注意到 E ( r a n k 2 ) = 2 E ( ( r a n k − 1 2 ) ) + 3 E ( r a n k ) − 2 E(rank^2)=2E(\binom{rank-1}2)+3E(rank)-2 E(rank2)=2E((2rank1))+3E(rank)2
  • E ( r a n k ) E(rank) E(rank) 前面已经求出来了
  • E ( ( r a n k − 1 2 ) ) E(\binom{rank-1}2) E((2rank1)) 的组合意义是有多少对满足 i &lt; j , i ≠ c , j ≠ c i&lt;j,i\ne c,j\ne c i<j,i̸=c,j̸=c ( i , j ) (i,j) (i,j) 使得 c c c 的排名比 i i i j j j 都大
  • 同样地可以分 a c &gt; a i , a c &gt; a j a_c&gt;a_i,a_c&gt;a_j ac>ai,ac>aj a c ≥ a i , a c &gt; a j a_c\ge a_i,a_c&gt;a_j acai,ac>aj a c ≥ a i , a c ≥ a j a_c\ge a_i,a_c\ge a_j acai,acaj 三种情况讨论,显然这三种情况出现的概率与 c , i , j c,i,j c,i,j 无关
  • 记上面三种情况出现的概率分别为 p 1 , p 2 , p 3 p_1,p_2,p_3 p1,p2,p3
  • a i a_i ai 为第 i i i 个二进制数
  • 可以用简单容斥求出 a i , a j , a c a_i,a_j,a_c ai,aj,ac 三者互不相同的概率,将其乘上 1 3 \frac 13 31 好像就是 p 1 p_1 p1
  • 但上面忽略了 a c &gt; a i = a j a_c&gt;a_i=a_j ac>ai=aj 的概率,这个概率可以数位 DP 求
  • f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1] 表示 a i a_i ai a j a_j aj 的前 i i i 位相等, a i a_i ai a j a_j aj 的前 i i i 位等于 / 小于 a c a_c ac 的前 i i i 位的概率
  • f [ i ] f[i] f[i] 直接转移到 f [ i + 1 ] f[i+1] f[i+1]
  • p 1 p_1 p1 即为 a i , a j , a c a_i,a_j,a_c ai,aj,ac 三者互不相同的概率乘 1 3 \frac 13 31 再加上 f [ m ] [ 1 ] f[m][1] f[m][1]
  • p 2 p_2 p2 p 1 p_1 p1 加上 a c = a i &gt; a j a_c=a_i&gt;a_j ac=ai>aj 的概率(可以利用 f [ m ] [ 1 ] f[m][1] f[m][1] 计算出)
  • p 3 p_3 p3 p 2 p_2 p2 加上 a c = a j &gt; a i a_c=a_j&gt;a_i ac=aj>ai 的概率再加上 a c , a i , a j a_c,a_i,a_j ac,ai,aj 两两相同的概率
  • E ( ( r a n k − 1 2 ) ) = ( c − 1 2 ) × p 3 + ( c − 1 ) × ( n − c ) × p 2 + ( n − c 2 ) × p 1 E(\binom{rank-1}2)=\binom{c-1}2\times p_3+(c-1)\times(n-c)\times p_2+\binom{n-c}2\times p_1 E((2rank1))=(2c1)×p3+(c1)×(nc)×p2+(2nc)×p1
  • O ( m ) O(m) O(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 = 5e5 + 5, ZZQ = 998244353, I2 = 499122177, I3 = 332748118;

int n, m, typ, a[N], q, same2 = 1, same3 = 1, invn, invn2, invn3, less, leq,
dif_same, less_all, less_leq, leq_all, f[N][2];

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % ZZQ;
		a = 1ll * a * a % ZZQ;
		b >>= 1;
	}
	return res;
}

int main()
{
	int x;
	read(n); read(m); read(typ);
	for (int i = 1; i <= m; i++) read(a[i]);
	invn = qpow(n, ZZQ - 2);
	invn2 = qpow(1ll * n * (n - 1) % ZZQ, ZZQ - 2);
	invn3 = qpow(1ll * n * (n - 1) % ZZQ * (n - 2) % ZZQ, ZZQ - 2);
	for (int i = 1; i <= m; i++)
	{
		same2 = (1ll * (n - a[i] - 1) * (n - a[i]) + 1ll * (a[i] - 1) * a[i])
			% ZZQ * invn2 % ZZQ	* same2 % ZZQ;
		same3 = (1ll * (n - a[i] - 2) * (n - a[i] - 1) % ZZQ * (n - a[i])
			+ 1ll * (a[i] - 2) * (a[i] - 1) % ZZQ * a[i])
			% ZZQ * invn3 % ZZQ * same3 % ZZQ;
	}
	less = 1ll * I2 * (1 - same2 + ZZQ) % ZZQ;
	leq = 1ll * I2 * (1 + same2) % ZZQ;
	dif_same = (same2 - same3 + ZZQ) % ZZQ;
	less_all = 1ll * I3 * (1 - 3ll * dif_same % ZZQ - same3 + ZZQ + ZZQ) % ZZQ;
	f[0][0] = 1;
	for (int i = 0; i < m; i++)
	{
		int x = a[i + 1];
		f[i + 1][1] = ((1ll * (n - x - 1) * (n - x) + 1ll * (x - 1) * x)
			% ZZQ * invn2 % ZZQ	* f[i][1] + f[i + 1][1]) % ZZQ;
		f[i + 1][0] = ((1ll * (n - x - 2) * (n - x - 1) % ZZQ * (n - x)
			+ 1ll * (x - 2) * (x - 1) % ZZQ * x)
			% ZZQ * invn3 % ZZQ * f[i][0] + f[i + 1][0]) % ZZQ;
		f[i + 1][1] = (1ll * (n - x) * invn % ZZQ * (x - 1) % ZZQ * x % ZZQ
			* invn3 % ZZQ * n % ZZQ * f[i][0] + f[i + 1][1]) % ZZQ;
	}
	less_all = (f[m][1] + less_all) % ZZQ;
	less_leq = (1ll * dif_same - f[m][1] + less_all + ZZQ) % ZZQ;
	leq_all = (2ll * (dif_same - f[m][1] + ZZQ) + less_all + same3) % ZZQ;
	read(q);
	while (q--)
	{
		read(x);
		int ans = (1ll * (x - 1) * leq + 1ll * (n - x) * less + 1) % ZZQ;
		if (typ == 1) {printf("%d\n", ans); continue;}
		ans = (3ll * ans + ZZQ - 2) % ZZQ;
		ans = (1ll * (x - 1) * (x - 2) % ZZQ * leq_all
			+ 2ll * (x - 1) * (n - x) % ZZQ * less_leq
			+ 1ll * (n - x) * (n - x - 1) % ZZQ * less_all + ans) % ZZQ;
		printf("%d\n", ans);
	}
	return 0;
}

D2T3

Solution:整体二分 + 随机化

  • 先说特殊性质 B 的做法(一棵树,除 0 0 0 外每个点都向编号更小的点连了恰好一条边)
  • 考虑整体二分
  • 即过程 s o l v e ( S , l , r ) solve(S,l,r) solve(S,l,r) 表示为点集 S S S 确定父亲,已知它们的父亲编号在 [ l , r ] [l,r] [l,r]
  • 如果 l = r l=r l=r 直接返回,点集 S S S 内所有点的父亲全部确定
  • 否则令 m i d = ⌊ l + r 2 ⌋ mid=\lfloor\frac{l+r}2\rfloor mid=2l+r
  • 把编号从 l l l m i d mid mid 的点全部执行 modify 操作
  • 对于一个点 u ∈ S u\in S uS
  • 如果 u ≤ m i d u\le mid umid 或者 u u u 点在这 m i d − l + 1 mid-l+1 midl+1 次 modify 操作之后由白变黑
  • 那么 u u u 的父亲一定在 [ l , m i d ] [l,mid] [l,mid] 内,否则一定在 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]
  • 往下递归即可, modify 和 query 的次数都是 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 正解的做法基于这个整体二分
  • 不难发现,在一个点向编号更小的点连了奇数条边时,这个整体二分能够找出至少一条边
  • 而如果一个点向编号更小的点连了偶数条边时
  • 考虑能否打乱所有点的顺序,使得这个点向编号更小的点连的边数为奇数
  • 于是我们有了一个基本思路
  • 即每次随机生成一个 0 0 0 n − 1 n-1 n1 的排列,然后根据这个排列重定义每个点的编号
  • 然后还是整体二分
  • 可以计算出:期望意义下随机的一个排列,对于任意一个点 u u u (需保证以 u u u 为端点的边还没全部被找出来)
  • u u u 向编号更小的点连的边数为奇数的概率至少 1 3 \frac 13 31
  • 故我们算法的具体过程为首先有一个 0 0 0 n − 1 n-1 n1 的排列 p p p 满足 p i = i p_i=i pi=i (否则特殊性质为 B 的任务过不去)
  • 重复执行下面的操作
  • (1)如果 p i = u p_i=u pi=u 则把点 u u u 的编号定为 i i i
  • (2)在重新编号之后的图上跑整体二分,然后如果已经找出所有 m m m 条边就返回
  • (3)使用 check 操作去掉满足 u u u 的所有出边都已被找出的点 u u u
  • (4)把 p p p 内的元素改为所有还未被删掉的所有点并随机打乱
  • 注意上面的每一次循环相当于是把找到的所有边都删掉了
  • 故我们需要对于这一次循环找到的所有边,在后续过程中去掉这些边的影响
  • 来分析一波复杂度
  • 每次当还剩下 M M M 条边时,由于我们用 check 操作去掉了孤立点,所以点数不超过 O ( M ) O(M) O(M)
  • 我们会用 O ( M log ⁡ M ) O(M\log M) O(MlogM) 的复杂度去进行整体二分
  • 我们有 T ( M ) = T ( M − X M ) + O ( M log ⁡ M ) T(M)=T(M-X_M)+O(M\log M) T(M)=T(MXM)+O(MlogM) ,其中 X M X_M XM 的期望至少为 M 3 \frac M3 3M
  • 类似于求 K-th ,我们有 T ( M ) = O ( M log ⁡ M ) T(M)=O(M\log M) T(M)=O(MlogM)

Code

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

const int N = 2e5 + 5, M = N << 2;

int n, m, ecnt, nxt[M], adj[N], st[M], go[M], tmp[N], to[N], perm[N];
bool vis[N];

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

bool eg(int u, int l, int r)
{
	bool res = query(u);
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		if (!vis[v] && to[v] >= l && to[v] <= r) res ^= 1;
	return res;
}

void dfs(std::vector<int> que, int l, int r)
{
	int n = que.size();
	if (l == r)
	{
		for (int i = 0; i < n; i++)
			if (l < que[i]) add_edge(perm[l], perm[que[i]]);
		return;
	}
	int mid = l + r >> 1;
	std::vector<int> le, ri;
	for (int i = l; i <= mid; i++) modify(perm[i]);
	for (int i = 0; i < n; i++)
		if (que[i] <= mid || eg(perm[que[i]], l, mid)) le.push_back(que[i]);
		else ri.push_back(que[i]);
	for (int i = l; i <= mid; i++) modify(perm[i]);
	dfs(le, l, mid); dfs(ri, mid + 1, r);
}

void explore(int N, int M)
{
	n = N; m = M;
	if (n <= 500)
	{
		for (int i = 0; i < n - 1; i++)
		{
			modify(i);
			for (int j = i + 1; j < n; j++)
				if (vis[j] ^ query(j))
					report(i, j), vis[j] ^= 1;
		}
		return;
	}
	for (int i = 0; i < n; i++) perm[i] = i;
	while (1)
	{
		for (int i = 0; i < n; i++) to[perm[i]] = i;
		int lst = ecnt;
		std::vector<int> fake;
		for (int i = 0; i < n; i++) fake.push_back(i);
		dfs(fake, 0, n - 1);
		if (ecnt == m) return;
		for (int i = lst + 1; i <= ecnt; i++)
		{
			if (check(st[i])) vis[st[i]] = 1;
			if (check(go[i])) vis[go[i]] = 1;
		}
		int tn = 0;
		for (int i = 0; i < n; i++)
			if (!vis[perm[i]]) tmp[tn++] = perm[i];
		n = tn;
		for (int i = 0; i < n; i++) perm[i] = tmp[i];
		std::random_shuffle(perm, perm + n);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值