【集训队作业】IOI 2020 集训队作业 试题泛做 3

Atcoder Grand Contest 30

Problem C. Coloring Torus

考虑 K K K 4 4 4 的倍数的情况,取偶数 N = K 2 N=\frac{K}{2} N=2K ,令
a i , j = ( i + j ) % N + 1 + i % 2 × N a_{i,j}=(i+j)\%N+1+i\%2\times N ai,j=(i+j)%N+1+i%2×N

不难发现这是一组可行解,并且将所有 i + N i+N i+N 变为 i i i 后,依然是一个可行解。

由此,我们可以处理 K K K 不是 4 4 4 的倍数的情况。

时间复杂度 O ( K 2 ) O(K^2) O(K2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, k;
int main() {
	read(k), n = (k + 1) / 2;
	if (k == 1) {
		puts("1");
		puts("1");
		return 0;
	}
	if (n & 1) n += 1;
	cout << n << endl;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++)
			if (i & 1) printf("%d ", (i + j) % n + 1);
			else if ((i + j) % n + 1 + n > k) printf("%d ", (i + j) % n + 1);
			else printf("%d ", (i + j) % n + 1 + n);
		printf("\n");
	}
	return 0;
}

Problem D. Inversion Sum

考虑倒序 DP ,记 d p i , j , k dp_{i,j,k} dpi,j,k 表示考虑最后 i i i 次交换,当前 a a a j j j 处, b b b k k k 处,使得最终 a a a 排在 b b b 之后的操作序列个数。分开考虑每一对数的贡献,则由 d p Q , ∗ , ∗ dp_{Q,*,*} dpQ,, ,可以直接计算出答案。

直接进行 DP 是 O ( Q N 2 ) O(QN^2) O(QN2) 的,但注意到一次交换操作时,除了 O ( N ) O(N) O(N) 个位置,剩余位置的 DP​ 值均为原有的 DP 值 × 2 \times 2 ×2 ,从而可以维护全局乘标记来优化转移。

时间复杂度 O ( N 2 + N Q ) O(N^2+NQ) O(N2+NQ)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3005;
const int P = 1e9 + 7;
const int inv2 = (P + 1) / 2;
typedef long long ll;
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int n, q, a[MAXN], x[MAXN], y[MAXN];
int mul, inv, dp[MAXN][MAXN];
int main() {
	read(n), read(q);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	for (int i = 1; i <= q; i++)
		read(x[i]), read(y[i]);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		dp[i][j] = i > j;
	mul = inv = 1;
	for (int i = q; i >= 1; i--) {
		int tmp = 1ll * inv2 * inv % P;
		for (int j = 1; j <= n; j++)
			if (j != x[i] && j != y[i]) {
				dp[x[i]][j] = dp[y[i]][j] = 1ll * mul * (dp[x[i]][j] + dp[y[i]][j]) % P * tmp % P;
				dp[j][x[i]] = dp[j][y[i]] = 1ll * mul * (dp[j][x[i]] + dp[j][y[i]]) % P * tmp % P;
			}
		dp[x[i]][y[i]] = dp[y[i]][x[i]] = 1ll * mul * (dp[x[i]][y[i]] + dp[y[i]][x[i]]) % P * tmp % P;
		mul = 2ll * mul % P, inv = tmp;
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		dp[i][j] = 1ll * dp[i][j] * mul % P;
	int ans = 0;
	for (int i = 1; i <= n; i++)
	for (int j = i + 1; j <= n; j++)
		if (a[i] < a[j]) ans = (ans + dp[i][j]) % P;
		else if (a[i] > a[j]) ans = (ans + dp[j][i]) % P;
	cout << ans << endl;
	return 0;
}

Problem E. Less than 3

考虑用每一段连续的颜色的长度以及开头颜色来描述数组。

对于一次不在开头处的合法操作,相当于交换了连续的一对 1 , 2 1,2 1,2

对于一次在开头或结尾处的操作,相当于一次 1 , 1 1,1 1,1 2 2 2 和互换,同时会改变所操作端的颜色。

考虑不对两头进行操作的情况,则答案显然为对应 2 2 2 出现位置差的和。

那么,枚举在开头处的操作次数,首先进行有关开头的操作,可以类似地计算对应代价。

这样的做法需要将字符串反向再做一次,这是因为最优策略可能是先操作头,也可能是先操作尾。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e3 + 5;
const int INF  = 1e9;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n;
vector <int> a, b;
char s[MAXN], t[MAXN];
bool fail(vector <int> &a, int cnt, int &ans) {
	vector <int> tmp = a;
	if (cnt == 0) return false;
	if (cnt >= 0) {
		int now = 0;
		while (cnt > 0) {
			if (a.empty()) return true;
			if (a.back() == 1) now++;
			else {
				cnt--;
				ans += now + 1;
				now += 2;
			}
			a.pop_back();
		}
		while (now--) a.push_back(1);
	} else {
		cnt = -cnt;
		int now = 0, state = 0;
		while (cnt > 0) {
			if (a.empty()) return true;
			if (a.back() == 2) now++;
			else if (state == 0) state = 1, ans += now;
			else {
				ans += now + 1;
				cnt--, now++;
				state = 0;
			}
			a.pop_back();
		}
		while (now--) a.push_back(2);
	}
	return false;
}
void debug(vector <int> &a) {
	for (auto x : a)
		cerr << x << ' ';
	cerr << endl;
}
int func(int x) {
	int y = b.size() - x - a.size();
	vector <int> c = a; int ans = 0;
	reverse(c.begin(), c.end());
	if (fail(c, x, ans)) return INF;
	reverse(c.begin(), c.end());
	if (fail(c, y, ans)) return INF;
	vector <int> e, d;
	for (unsigned i = 0; i < b.size(); i++) {
		if (b[i] == 2) d.push_back(i);
		if (c[i] == 2) e.push_back(i);
	}
	assert(d.size() == e.size());
	for (unsigned i = 0; i < d.size(); i++)
		ans += abs(d[i] - e[i]);
	return ans;
}
int main() {
	read(n);
	scanf("\n%s", s + 1);
	scanf("\n%s", t + 1);
	if (n == 1) {
		if (s[1] != t[1]) cout << 1 << endl;
		else cout << 0 << endl;
		return 0;
	}
	for (int i = 1; i <= n; i++) {
		if (s[i] == s[i - 1]) {
			a.pop_back();
			a.push_back(2);
		} else a.push_back(1);
		if (t[i] == t[i - 1]) {
			b.pop_back();
			b.push_back(2);
		} else b.push_back(1);
	}
	int ans = INF;
	for (int i = -n; i <= n; i++)
		if ((s[1] == t[1]) ^ (i % 2 != 0)) chkmin(ans, func(i));
	reverse(a.begin(), a.end());
	reverse(b.begin(), b.end());
	for (int i = -n; i <= n; i++)
		if ((s[n] == t[n]) ^ (i % 2 != 0)) chkmin(ans, func(i));
	cout << ans << endl;
	return 0;
}

Problem F. Permutation and Minimum

首先,我们可以将已经确定 B i B_i Bi 对应的 A i A_i Ai 从数组中删去,此后,一个 B i B_i Bi 只可能对应一对 − 1 -1 1 ,或者一个 − 1 -1 1 ,一个数值。

C C C 表示对应一对 − 1 -1 1 B i B_i Bi 的总数,我们可以令这些 B i B_i Bi 是无序的,再将最终答案 × C ! \times C! ×C!

不难发现此时我们只关心某个数值是否在 A i A_i Ai 中出现过,记为 v i s i vis_i visi

考虑从小到大向 A A A 中填入数字,进行动态规划。

将数字 x x x 填入一个尚未填入的组表示使其成为 B i B_i Bi ,我们需要决策与 x x x 成为一组的数值的 v i s vis vis v i s vis vis 同为 t r u e true true 的两个数字不能出现在同一组。

将数字 x x x 填入一个已经填入的组不会使其成为 B i B_i Bi ,但若 v i s x = t r u e vis_x=true visx=true ,则需要决策与 x x x 配对的数字。

d p i , j , k dp_{i,j,k} dpi,j,k 表示已经填入 1 ∼ i 1\sim i 1i ,存在 j + k j+k j+k 个已经填入一个数字的组,且有 j j j 组组内的另一个数字的 v i s vis vis t r u e true true k k k 组组内的另一个数字的 v i s vis vis f a l s e false false

那么,对于 v i s = t r u e vis=true vis=true 的位置,有
d p i , j , k ⇒ d p i + 1 , j , k + 1 j × d p i , j , k ⇒ d p i + 1 , j − 1 , k dp_{i,j,k}\Rightarrow dp_{i+1,j,k+1}\\j\times dp_{i,j,k}\Rightarrow dp_{i+1,j-1,k} dpi,j,kdpi+1,j,k+1j×dpi,j,kdpi+1,j1,k

对于 v i s = f a l s e vis=false vis=false 的位置,有
d p i , j , k ⇒ d p i + 1 , j , k + 1 d p i , j , k ⇒ d p i + 1 , j + 1 , k d p i , j , k ⇒ d p i + 1 , j , k − 1 dp_{i,j,k}\Rightarrow dp_{i+1,j,k+1}\\dp_{i,j,k}\Rightarrow dp_{i+1,j+1,k}\\dp_{i,j,k}\Rightarrow dp_{i+1,j,k-1} dpi,j,kdpi+1,j,k+1dpi,j,kdpi+1,j+1,kdpi,j,kdpi+1,j,k1

时间复杂度 O ( N 3 ) O(N^3) O(N3)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
bool vis[MAXN * 2];
int n, a[MAXN * 2], dp[MAXN * 2][MAXN][MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n);
	for (int i = 1; i <= n * 2; i++) {
		read(a[i]);
		if (a[i] == -1) a[i] = 0;
	}
	for (int i = 1; i <= n * 2; i += 2) {
		if (a[i] != 0 && a[i + 1] != 0) {
			int x = a[i], y = a[i + 1];
			for (int j = 1; j <= n * 2; j++)
				a[j] -= (a[j] >= x) + (a[j] >= y);
			for (int j = i; j <= n * 2; j++)
				a[j] = a[j + 2];
			n--, i -= 2;
		}
	}
	int now = 0, ans = 1;
	for (int i = 1; i <= n * 2; i += 2) {
		vis[a[i]] = vis[a[i + 1]] = true;
		if (a[i] == 0 && a[i + 1] == 0) ans = 1ll * ans * ++now % P;
	}
	dp[1][0][0] = 1;
	for (int i = 1; i <= n * 2; i++) {
		for (int j = 0; j <= n; j++)
		for (int k = 0; j + k <= n; k++) {
			int tmp = dp[i][j][k];
			if (tmp == 0) continue;
			if (vis[i]) {
				update(dp[i + 1][j][k + 1], tmp);
				if (j) update(dp[i + 1][j - 1][k], 1ll * j * tmp % P);
			} else {
				update(dp[i + 1][j][k + 1], tmp);
				update(dp[i + 1][j + 1][k], tmp);
				if (k) update(dp[i + 1][j][k - 1], tmp);
			}
		}
	}
	cout << 1ll * ans * dp[n * 2 + 1][0][0] % P << endl;
	return 0;
}

Atcoder Grand Contest 31

Problem D. A Sequence of Permutations

记置换 i → p i i\rightarrow p_i ipi 为置换 p p p ,定义 p − 1 p^{-1} p1 表示 p i → i p_i\rightarrow i pii p × q p\times q p×q 表示 i → q p i i\rightarrow q_{p_i} iqpi

那么
a 1 = p a 2 = q a 3 = p − 1 q a 4 = q − 1 p − 1 q a 5 = q − 1 p q − 1 p − 1 q a 6 = q − 1 p 2 q − 1 p − 1 q a 7 = q − 1 p q p q − 1 p − 1 q a 8 = q − 1 p q p − 1 q p q − 1 p − 1 q a_1=p\\a_2=q\\a_3=p^{-1}q\\a_4=q^{-1}p^{-1}q\\a_5=q^{-1}pq^{-1}p^{-1}q\\a_6=q^{-1}p^2q^{-1}p^{-1}q\\a_7=q^{-1}pqpq^{-1}p^{-1}q\\a_8=q^{-1}pqp^{-1}qpq^{-1}p^{-1}q a1=pa2=qa3=p1qa4=q1p1qa5=q1pq1p1qa6=q1p2q1p1qa7=q1pqpq1p1qa8=q1pqp1qpq1p1q

注意到令 r = q − 1 p q r=q^{-1}pq r=q1pq ,有
a 7 = r p r − 1 , a 8 = r p − 1 q p r − 1 a_7=rpr^{-1},a_8=rp^{-1}qpr^{-1} a7=rpr1,a8=rp1qpr1

并且
f ( r p r − 1 , r q r − 1 ) = r f ( p , q ) r − 1 f(rpr^{-1},rqr^{-1})=rf(p,q)r^{-1} f(rpr1,rqr1)=rf(p,q)r1

可将 6 6 6 个操作为一周期考虑,令 K = 6 m + 1 K=6m+1 K=6m+1 ,则答案为 r p r − 1 rpr^{-1} rpr1 ,其中
r = ( q − 1 p q p − 1 ) m p m + 1 r=(q^{-1}pqp^{-1})^{m}p^{m+1} r=(q1pqp1)mpm+1

时间复杂度 O ( N L o g K ) O(NLogK) O(NLogK)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
struct info {int a[MAXN]; };
int n; ll m;
info inv(info a) {
	info res;
	for (int i = 1; i <= n; i++)
		res.a[a.a[i]] = i;
	return res;
}
info unit() {
	info res;
	for (int i = 1; i <= n; i++)
		res.a[i] = i;
	return res;
}
info operator * (info a, info b) {
	info res;
	for (int i = 1; i <= n; i++)
		res.a[i] = b.a[a.a[i]];
	return res;
}
info p, q, r;
info power(info a, ll x) {
	if (x == 0) return unit();
	if (x < 0) return power(inv(a), -x);
	info tmp = power(a, x / 2);
	if (x % 2 == 0) return tmp * tmp;
	else return tmp * tmp * a;
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		read(p.a[i]);
	for (int i = 1; i <= n; i++)
		read(q.a[i]);
	while (m % 6 != 1) {
		info r = inv(p) * q;
		p = q, q = r, m--;
	}
	r = power(inv(q) * p * q * inv(p), m / 6) * power(p, m / 6 + 1);
	info res = r * p * inv(r);
	for (int i = 1; i <= n; i++)
		printf("%d ", res.a[i]);
	return 0;
}

Problem E. Snuke the Phantom Thief

若只存在 L 和 U 两种限制,则问题可以通过最大费用流解决。

在源汇之间建立 2 N 2N 2N 个点,两两连边,表示是否选取各个珠宝,由于两种限制的各个集合存在包含关系,显然可以建立辅助点限流。在源点与中间点之间对于 L 限制限流,汇点与中间点之间对于 U 限制限流,可以在 O ( M i n C o s t F l o w ( N + V , N + V ) ) O(MinCostFlow(N+V,N+V)) O(MinCostFlow(N+V,N+V)) 内解决问题。

对于原本的问题,我们需要在中间点的一侧对两种限制同时限流。

问题的关键在于枚举总共偷走的珠宝数,一旦枚举了总共偷走的珠宝数 K K K ,对于一个 R 型限制,我们可以将其看做一个 L 型的下界限制,从而用有上下界的费用流解题。

时间复杂度 O ( N × M i n C o s t F l o w ( N + V , N + V ) ) O(N\times MinCostFlow(N+V,N+V)) O(N×MinCostFlow(N+V,N+V))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
const int MAXV = 100;
const int INF  = 1e9;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
namespace MincostFlow {
	const int MAXP = 12005;
	const int MAXQ = 2e5 + 5;
	const long long INF = 1e18;
	struct edge {int dest, flow, pos; ll cost; };
	vector <edge> a[MAXP];
	int n, m, s, t, tot, flow;
	ll dist[MAXP], cost; int path[MAXP], home[MAXP];
	void FlowPath() {
		int p = t, ans = 1e9;
		while (p != s) {
			ans = min(ans, a[path[p]][home[p]].flow);
			p = path[p];
		}
		flow += ans;
		cost += ans * dist[t];
		p = t;
		while (p != s) {
			a[path[p]][home[p]].flow -= ans;
			a[p][a[path[p]][home[p]].pos].flow += ans;
			p = path[p];
		}
	}
	bool spfa() {
		static int q[MAXQ];
		static bool inq[MAXP];
		static int l = 0, r = 0;
		for (int i = 0; i <= r; i++)
			dist[q[i]] = -INF;
		q[l = r = 0] = s, dist[s] = 0, inq[s] = true;
		while (l <= r) {
			int tmp = q[l];
			for (unsigned i = 0; i < a[tmp].size(); i++)
				if (a[tmp][i].flow != 0 && dist[tmp] + a[tmp][i].cost > dist[a[tmp][i].dest]) {
					dist[a[tmp][i].dest] = dist[tmp] + a[tmp][i].cost;
					path[a[tmp][i].dest] = tmp;
					home[a[tmp][i].dest] = i;
					if (!inq[a[tmp][i].dest]) {
						q[++r] = a[tmp][i].dest;
						inq[q[r]] = true;
					}
				}
			l++, inq[tmp] = false;
		}
		return dist[t] != -INF;
	}
	void addedge(int x, int y, int z, ll c) {
		a[x].push_back((edge){y, z, a[y].size(), c});
		a[y].push_back((edge){x, 0, a[x].size() - 1, -c});
	}
	ll work(int n, int ns, int nt, int intend) {
		flow = cost = 0;
		for (int i = 1; i <= n; i++)
			dist[i] = -INF;
		while (spfa()) FlowPath();
		if (flow < intend) return 0;
		s = ns, t = nt;
		while (spfa() && dist[t] > 0) FlowPath();
		return cost;
	}
	void init() {
		for (int i = 1; i <= tot; i++)
			a[i].clear();
		s = 1; t = tot = 2;
	}
}
int l[MAXN], r[MAXN], u[MAXN], d[MAXN];
int s, t, n, m, tot, x[MAXN], y[MAXN]; ll v[MAXN];
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(x[i]), read(y[i]), read(v[i]);
	read(m);
	for (int i = 0; i <= MAXV + 1; i++)
		l[i] = r[i] = u[i] = d[i] = n;
	l[0] = d[0] = 0, r[MAXV + 1] = u[MAXV + 1] = 0;
	for (int i = 1; i <= m; i++) {
		char c; int x, y;
		scanf("\n%c%d%d", &c, &x, &y);
		if (c == 'L') l[x] = y;
		if (c == 'R') r[x] = y;
		if (c == 'U') u[x] = y;
		if (c == 'D') d[x] = y;
	}
	for (int i = 1, j = MAXV; i <= MAXV; i++, j--) {
		chkmin(r[i], r[i - 1]), chkmin(u[i], u[i - 1]);
		chkmin(l[j], l[j + 1]), chkmin(d[j], d[j + 1]);
	}
	ll ans = 0;
	for (int k = 1; k <= n; k++) {
		MincostFlow :: init();
		s = ++MincostFlow :: tot;
		t = ++MincostFlow :: tot;
		bool fail = false; int intend = 0;
		static int xp[MAXN], yp[MAXN], inp[MAXN], outp[MAXN];
		for (int i = 1; i <= n; i++) {
			inp[i] = ++MincostFlow :: tot;
			outp[i] = ++MincostFlow :: tot;
		}
		for (int i = 1; i <= MAXV; i++) {
			xp[i] = ++MincostFlow :: tot;
			yp[i] = ++MincostFlow :: tot;
		}
		MincostFlow :: addedge(t, s, INF, -1e15);
		for (int i = 1; i <= n; i++) {
			MincostFlow :: addedge(inp[i], outp[i], 1, v[i]);
			MincostFlow :: addedge(xp[x[i]], inp[i], 1, 0);
			MincostFlow :: addedge(outp[i], yp[y[i]], 1, 0);
		}
		xp[MAXV + 1] = s, yp[MAXV + 1] = t;
		for (int i = 1; i <= MAXV; i++) {
			int Min, Max;
			Max = min(l[i], k), Min = max(k - r[i + 1], 0), intend += Min;
			if (Min > Max) {fail = true; break; }
			MincostFlow :: addedge(xp[i + 1], xp[i], Max - Min, 0);
			MincostFlow :: addedge(MincostFlow :: s, xp[i], Min, 0);
			MincostFlow :: addedge(xp[i + 1], MincostFlow :: t, Min, 0);
			
			Max = min(d[i], k), Min = max(k - u[i + 1], 0), intend += Min;
			if (Min > Max) {fail = true; break; }
			MincostFlow :: addedge(yp[i], yp[i + 1], Max - Min, 0);
			MincostFlow :: addedge(MincostFlow :: s, yp[i + 1], Min, 0);
			MincostFlow :: addedge(yp[i], MincostFlow :: t, Min, 0);
		}
		if (fail) continue;
		else chkmax(ans, MincostFlow :: work(MincostFlow :: tot, s, t, intend));
	}
	cout << ans << endl;
	return 0;
}

Problem F. Walk on Graph

首先考虑从 T T T 走到 S S S ,每经过一条边 c c c 就令 x = 2 x + c x=2x+c x=2x+c

[ v , x ] [v,x] [v,x] 表示一个在 v v v 处数值为 x x x 的状态,考虑问题的以下性质:

( 1 ) (1) (1) 、考虑在边 ( a , b , c ) (a,b,c) (a,b,c) 上往返,则状态变化形如 [ a , x ] → [ b , 2 x + c ] → [ a , 4 x + 3 c ] [a,x]\rightarrow [b,2x+c]\rightarrow [a,4x+3c] [a,x][b,2x+c][a,4x+3c] ,可以发现其一定能够回到 [ a , x ] [a,x] [a,x] ,因此可以认为到达关系是双向的,能够相互到达的状态相互等价,因此,我们只需要判断始末状态是否等价。

( 2 ) (2) (2) 、考虑在两条交于点 v v v ,长度为 a , b a,b a,b 的边上往返,则有 [ v , x ] = [ v , 4 x + 3 a ] = [ v , 4 x + 3 b ] [v,x]=[v,4x+3a]=[v,4x+3b] [v,x]=[v,4x+3a]=[v,4x+3b] ,又因为 4 4 4 存在逆元,状态 [ v , x ] [v,x] [v,x] 一定存在,因此,我们可以在 v v v 处对 x x x 任意增加 3 ( a − b ) 3(a-b) 3(ab)

( 3 ) (3) (3) 、综上,令 g g g 是最大的使得存在 z z z 使得所有边长 c ≡ z   ( m o d   g ) c\equiv z\ (mod\ g) cz (mod g) M o d Mod Mod 的因数,则可以令 M o d = g c d ( 3 g , M o d ) Mod=gcd(3g,Mod) Mod=gcd(3g,Mod) ,而不对结果产生影响。

若将所有边长 − z -z z ,则在新图中 z z z 可以到达 z + r z+r z+r 等价于在原图中 0 0 0 可以到达 r r r

在新图中,各条边长均为 g g g 的倍数,从某处权值 r r r 出发到达的权值可以表示为 p z + q s pz+qs pz+qs 的形式,其中 q ∈ { 0 , 1 , 2 } , p = 2 i q\in\{0,1,2\},p=2^i q{0,1,2},p=2i 。又由于 [ v , x ] → [ v , 4 x + 3 g ] = [ v , 4 x ] [v,x]\rightarrow[v,4x+3g]=[v,4x] [v,x][v,4x+3g]=[v,4x] ,可以认为 [ v , x ] = [ v , 4 x ] [v,x]=[v,4x] [v,x]=[v,4x] ,从而 p ∈ { 1 , 2 } p\in\{1,2\} p{1,2} ,状态总数减少至了 O ( N ) O(N) O(N) ,可以直接通过并查集维护连通性。

最后,回答询问时,需要判断 [ t , z ] [t,z] [t,z] 能否到达 [ s , z + r ] [s,z+r] [s,z+r] ,枚举 p , q p,q p,q ,判断是否 [ t , z ] = [ s , p z + q s ] [t,z]=[s,pz+qs] [t,z]=[s,pz+qs] ,并且存在 i i i 使得 x = 4 i , p x + q s = z + r x=4^i,px+qs=z+r x=4i,px+qs=z+r 即可。

时间复杂度 O ( M o d + ( N + M + Q ) α ( N ) ) O(Mod+(N+M+Q)\alpha(N)) O(Mod+(N+M+Q)α(N))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
const int MAXP = 1e6 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, m, q, P, f[MAXN];
int tot, point[MAXN][2][3];
int x[MAXN], y[MAXN], w[MAXN];
int find(int x) {
	if (f[x] == x) return x;
	else return f[x] = find(f[x]);
}
void merge(int x, int y) {
	f[find(x)] = find(y);
}
bool same(int x, int y) {
	return find(x) == find(y);
}
bool vis[2][MAXP];
int main() {
	read(n), read(m), read(q), read(P); int g = P;
	for (int i = 1; i <= m; i++) {
		read(x[i]), read(y[i]), read(w[i]);
		g = __gcd(g, abs(w[i] - w[1]));
	}
	P = __gcd(3 * g, P);
	for (int i = 1; i <= n; i++)
	for (int p = 0; p <= 1; p++)
	for (int q = 0; q <= 2; q++) {
		point[i][p][q] = ++tot;
		f[tot] = tot;
	}
	int z = w[1] % g;
	for (int x = z, i = 0; i <= P; i++, x = 2 * x % P)
		vis[i & 1][x] = true;
	for (int i = 1; i <= m; i++) {
		int tx = x[i], ty = y[i];
		int tw = (w[i] - z) % P / g;
		for (int p = 0; p <= 1; p++)
		for (int q = 0; q <= 2; q++)
			merge(point[tx][p][q], point[ty][p ^ 1][(2 * q + tw) % 3]);
	}
	for (int i = 1; i <= q; i++) {
		bool res = false;
		int s, t, r; read(s), read(t), read(r);
		for (int p = 0; p <= 1; p++)
		for (int q = 0; q <= 2; q++)
			res |= same(point[t][0][0], point[s][p][q]) && vis[p][(r + z + (3 - q) * g) % P];
		if (res) puts("YES");
		else puts("NO");
	}
	return 0;
}

Atcoder Grand Contest 32

Problem C. Three Circuits

首先,若这张图不是一张欧拉图,答案为 N o No No ,下令各点度数为偶数。

若存在一个点度数 ≥ 6 \geq 6 6 ,该点将在欧拉回路上出现至少 3 3 3 次,我们可以将该欧拉回路分成 3 3 3 部分,答案为 Y e s Yes Yes ,下令各点度数 ∈ { 2 , 4 } \in \{2,4\} {2,4} ,记度为 4 4 4 的点数为 c n t 4 cnt_4 cnt4

c n t 4 ≥ 3 cnt_4\geq3 cnt43 ,取三个度为 4 4 4 的点 A , B , C A,B,C A,B,C ,由于 A A A 点将在欧拉回路上出现 2 2 2 次,我们可以将该欧拉回路分成 2 2 2 部分, B , C B,C B,C 都会出现在某一条上 2 2 2 ( 1 ) (1) (1) ,或在两条上各出现 1 1 1 ( 2 ) (2) (2) 。对于情况 ( 1 ) (1) (1) ,直接将对应部分的欧拉回路断开即可将原图的欧拉回路分成 3 3 3 部分;对于情况 ( 2 ) (2) (2) ( A , B ) , ( B , C ) , ( A , C ) (A,B),(B,C),(A,C) (A,B),(B,C),(A,C) 间的边可分别组成 3 3 3 条欧拉回路,因此答案为 Y e s Yes Yes ,下令 c n t 4 ≤ 2 cnt_4\leq 2 cnt42

由于形成 3 3 3 条欧拉回路至少需要 M ≥ N + 2 M\geq N+2 MN+2 ,因此 M ≤ N + 1 M\leq N+1 MN+1 时答案为 N o No No ,剩余的情况即 c n t 4 = 2 cnt_4=2 cnt4=2 ,令这两个度为 4 4 4 的点为 X , Y X,Y X,Y

X , Y X,Y X,Y 之间有 4 4 4 条路径,答案为 N o No No ,否则,即 X , Y X,Y X,Y 之间有 2 2 2 条路径,答案为 Y e s Yes Yes

时间复杂度 O ( N + M ) O(N+M) O(N+M)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, cnt, x, y, d[MAXN];
vector <int> a[MAXN];
void dfs(int pos, int from) {
	if (pos == x && from) return;
	if (pos == y) {
		cnt++;
		return;
	}
	for (auto x : a[pos])
		if (x != from) dfs(x, pos);
}
int main() {
	read(n), read(m);
	if (m < n + 2) {
		puts("No");
		return 0;
	}
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		d[x]++, d[y]++;
		a[x].push_back(y);
		a[y].push_back(x);
	}
	for (int i = 1; i <= n; i++)
		if (d[i] % 2) {
			puts("No");
			return 0;
		}
	x = 0, y = 0;
	for (int i = 1; i <= n; i++)
		if (d[i] > 2) {
			if (d[i] > 4) {
				puts("Yes");
				return 0;
			}
			if (x == 0) x = i;
			else if (y == 0) y = i;
			else {
				puts("Yes");
				return 0;
			}
		}
	dfs(x, 0);
	if (cnt == 4) puts("No");
	else {
		assert(cnt == 2);
		puts("Yes");
	}
	return 0;
}

Problem D. Rotation Sort

考虑旋转操作的本质,即选择一个数,花费一定代价将其插入至左侧任一位置,或花费一定代价将其插入至右侧任一位置。

考虑对于最终没有移动的数进行动态规划,记 d p i , j dp_{i,j} dpi,j 表示考虑前 i i i 个数,最后一个没有移动的数为 j j j 的情况下最小的代价,分 a i > j a_i>j ai>j a i < j a_i<j ai<j 转移即可。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const long long INF = 1e18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
ll dp[MAXN][MAXN];
int n, f, b, a[MAXN];
int main() {
	read(n), read(f), read(b);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	for (int i = 0; i <= n; i++)
	for (int j = 0; j <= n; j++)
		dp[i][j] = INF;
	dp[0][0] = 0;
	for (int i = 1; i <= n; i++)
	for (int j = 0; j <= n; j++) {
		ll tmp = dp[i - 1][j];
		if (tmp == INF) continue;
		if (a[i] > j) {
			chkmin(dp[i][j], tmp + f);
			chkmin(dp[i][a[i]], tmp);
		} else chkmin(dp[i][j], tmp + b);
	}
	ll ans = INF;
	for (int i = 0; i <= n; i++)
		chkmin(ans, dp[n][i]);
	writeln(ans);
	return 0;
}

Problem E. Modulo Pairing

首先,排序整个数列。

可以证明,所有解都可以调整至如下形式:存在一个分界点 M i d Mid Mid [ 1 , M i d ] [1,Mid] [1,Mid] 中的元素按照 1 − M i d , 2 − ( M i d − 1 ) , . . . 1-Mid,2-(Mid-1),... 1Mid,2(Mid1),... 的形式匹配,且各组的和小于 M M M [ M i d + 1 , 2 ∗ N ] [Mid+1,2*N] [Mid+1,2N] 中的元素按照 ( M i d + 1 ) − ( 2 ∗ N ) , ( M i d + 2 ) − ( 2 ∗ N − 1 ) , . . . (Mid+1)-(2*N),(Mid+2)-(2*N-1),... (Mid+1)(2N),(Mid+2)(2N1),... 的形式匹配,且各组的和不小于 M M M

若用蓝线表示一组和小于 M M M 的匹配,红线表示一组和不小于 M M M 的匹配,按照下图的方式调整可以保证方案不会变劣。

在这里插入图片描述

同时,当分界点 M i d Mid Mid 向左移动的时候,两部分的最大和都会减少,因此,我们之需要找到使得右侧的最小和不小于 M M M 的最小的 M i d Mid Mid ,构造答案。二分即可。

时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 2e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, a[MAXN];
bool check(int pos) {
	for (int i = 2 * pos + 1, j = 2 * n; i <= j; i++, j--)
		if (a[i] + a[j] < m) return false;
	return true;
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= 2 * n; i++)
		read(a[i]);
	sort(a + 1, a + 2 * n + 1);
	int l = 0, r = n;
	while (l < r) {
		int mid = (l + r) / 2;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	int ans = 0;
	for (int i = 1, j = 2 * l; i <= j; i++, j--)
		chkmax(ans, a[i] + a[j]);
	for (int i = 2 * l + 1, j = 2 * n; i <= j; i++, j--)
		chkmax(ans, a[i] + a[j] - m);
	writeln(ans);
	return 0;
}

Problem F. One Third

考虑将一次在 x x x 处的划分描述为三条线,红线 x x x ,蓝线 x + 120 ∘ x+120{}^{\circ} x+120 ,绿线 x − 120 ∘ x-120{}^{\circ} x120

最接近 1 3 \frac{1}{3} 31 的值与 1 3 \frac{1}{3} 31 的差值即为不同颜色的两条线形成的最小非负夹角。

令第一次划分有 x = 0 x=0 x=0 ,在数轴上标红 0 0 0 ,标蓝 1 3 \frac{1}{3} 31 ,剩余的每一次划分在 [ 0 , 1 3 ) [0,\frac{1}{3}) [0,31) 内都存在恰好一个随机分布,随机颜色的点,距离最近的两个异色点的距离的期望即为答案。

N − 1 N-1 N1 个点将 [ 0 , 1 3 ) [0,\frac{1}{3}) [0,31) 分成了 N N N 份,考虑最小的一部分的期望长度 E 1 E_1 E1 ,有
E 1 = 1 3 ∫ 0 1 N P ( L e n ≥ x ) d x = 1 3 ∫ 0 1 N ( 1 − n x ) n − 1 d x = 1 3 N 2 E_1=\frac{1}{3}\int_{0}^{\frac{1}{N}}P(Len\geq x)dx=\frac{1}{3}\int_{0}^{\frac{1}{N}}(1-nx)^{n-1}dx=\frac{1}{3N^2} E1=310N1P(Lenx)dx=310N1(1nx)n1dx=3N21

考虑第 i i i 小的一部分的期望长度与第 i − 1 i-1 i1 小的一部分的期望长度的差 E i − E i − 1 E_i-E_{i-1} EiEi1 ,考虑将所有长度第 j   ( j > i − 1 ) j\ (j>i-1) j (j>i1) 小的部分的长度减去第 i − 1 i-1 i1 小的长度,计算剩余线段的期望最短长度,有
E i − E i − 1 = 1 3 × 1 − ∑ j = 1 i − 1 ( N − j + 1 ) ( E j − E j − 1 ) ( N − i + 1 ) 2 = 1 3 N ( N − i + 1 ) E_i-E_{i-1}=\frac{1}{3}\times \frac{1-\sum_{j=1}^{i-1}(N-j+1)(E_j-E_{j-1})}{(N-i+1)^2}=\frac{1}{3N(N-i+1)} EiEi1=31×(Ni+1)21j=1i1(Nj+1)(EjEj1)=3N(Ni+1)1

每一部分都有 1 3 \frac{1}{3} 31 的概率两端同色,因此答案 E a n s Eans Eans
E a n s = ∑ i = 1 N 1 3 i − 1 × ( E i − E i − 1 ) = ∑ i = 1 N 1 3 i N ( N − i + 1 ) Eans=\sum_{i=1}^{N}\frac{1}{3^{i-1}}\times(E_i-E_{i-1})=\sum_{i=1}^{N}\frac{1}{3^{i}N(N-i+1)} Eans=i=1N3i11×(EiEi1)=i=1N3iN(Ni+1)1

时间复杂度 O ( N ) O(N) O(N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 2e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, a[MAXN];
bool check(int pos) {
	for (int i = 2 * pos + 1, j = 2 * n; i <= j; i++, j--)
		if (a[i] + a[j] < m) return false;
	return true;
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= 2 * n; i++)
		read(a[i]);
	sort(a + 1, a + 2 * n + 1);
	int l = 0, r = n;
	while (l < r) {
		int mid = (l + r) / 2;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	int ans = 0;
	for (int i = 1, j = 2 * l; i <= j; i++, j--)
		chkmax(ans, a[i] + a[j]);
	for (int i = 2 * l + 1, j = 2 * n; i <= j; i++, j--)
		chkmax(ans, a[i] + a[j] - m);
	writeln(ans);
	return 0;
}

Atcoder Grand Contest 33

Problem D. Complexity

朴素的 DP 是 O ( H 2 W 2 ) O(H^2W^2) O(H2W2) 的,但注意到 Complexity 的范围应当在 O ( L o g N ) O(LogN) O(LogN) 级别,可以考虑将一维 DP 数组的下标变为 Complexity ,再进行 DP 。

时间复杂度 O ( H W ( H + W ) L o g ( H + W ) ) O(HW(H+W)Log(H+W)) O(HW(H+W)Log(H+W))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 190;
const int MAXM = 18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
char s[MAXN][MAXN]; int n, m;
int dp[MAXN][MAXN][MAXN][MAXM];
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		scanf("\n%s", s[i] + 1);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		bool same = true; char col = s[i][j];
		for (int k = j; k <= m; k++) {
			if (s[i][k] != col) same = false;
			if (same) {
				if (s[i - 1][j] == s[i][j]) dp[i][j][k][0] = dp[i - 1][j][k][0];
				else dp[i][j][k][0] = i;
			} else dp[i][j][k][0] = i + 1;
		}
	}
	for (int i = 1; i <= n; i++)
	for (int len = 1; len <= m; len++) {
		for (int j = 1, k = len; k <= m; j++, k++) {
			for (int v = 1; v < MAXM; v++) {
				int tmp = dp[i][j][k][v - 1]; 
				if (tmp == 1) dp[i][j][k][v] = 1;
				else dp[i][j][k][v] = dp[tmp - 1][j][k][v - 1];
				int l = j, r = k - 1;
				while (l <= r) {
					int mid = (l + r) / 2;
					chkmin(dp[i][j][k][v], max(dp[i][j][mid][v - 1], dp[i][mid + 1][k][v - 1]));
					if (dp[i][j][mid][v - 1] > dp[i][mid + 1][k][v - 1]) r = mid - 1;
					else l = mid + 1;
				}
			}
		}
	}
	int ans = 0;
	while (dp[n][1][m][ans] != 1) ans++;
	printf("%d\n", ans);
	return 0;
}

Problem E. Go around a Circle

细节比较多的 DP 题,首先特判 S i S_i Si 都相同的情况,此时要求每一个位置都与 S 1 S_1 S1 对应的颜色相邻。

否则,不妨设 S 1 = R S_1=R S1=R ,则要求如下:
( 1 ) (1) (1) 、存在蓝色的边,但连续段长度至多为 1 1 1
( 2 ) (2) (2) 、每一段红色的边连续段长度均为奇数,且在某个数 M i n Min Min 以内

可以用前缀和优化朴素的 DP 直接计数。

时间复杂度 O ( N + M ) O(N+M) O(N+M)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m;
char s[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(m);
	scanf("%s", s + 1);
	bool same = true;
	for (int i = 1; i <= m; i++)
		if (s[i] != s[1]) same = false;
	if (same) {
		static int dp[MAXN], sum[MAXN];
		dp[1] = sum[1] = 1;
		for (int i = 2; i <= n; i++) {
			dp[i] = sum[i - 2];
			sum[i] = (sum[i - 1] + dp[i]) % P;
		}
		int ans = (sum[n - 1] + 1) % P;
		for (int i = 2, j = n - 1; i <= n; i++, j--)
			update(ans, sum[j]);
		printf("%d\n", ans);
		return 0;
	}
	if (n & 1) {
		puts("0");
		return 0;
	}
	int Min = n - (n - 1) % 2;
	int last = 0;
	for (int i = 1; i <= m; i++)
		if (s[i] != s[1]) {
			int len = i - last - 1;
			if (last == 0) chkmin(Min, len + 1);
			if (len % 2 == 1) chkmin(Min, len);
			last = i;
		}
	static int dp[MAXN], sum[MAXN];
	dp[1] = sum[1] = 1;
	for (int i = 2; i <= n; i++) {
		if (i % 2 == 1) {
			dp[i] = sum[i - 2];
			update(dp[i], P - sum[max(i - Min - 3, 0)]);
		}
		sum[i] = (sum[i - 1] + dp[i]) % P;
	}
	int ans = (sum[n - 1] - sum[max(n - Min - 2, 0)] + P) % P;
	for (int i = 2, j = n - 1; i <= n && j >= n - Min - 2; i++, j--)
		update(ans, (sum[j] - sum[max(n - Min - 2, 0)] + P) % P);
	printf("%d\n", ans);
	return 0;
}

Problem F. Adding Edges

直接用队列模拟加边的过程,即依次考虑每一条已有的边,找到所有尚未加入队列,且与其存在一个公共点的,可以被加入的边加入队列。

朴素的实现时 O ( N 3 ) O(N^3) O(N3) 的,但可以简单地用 bitset 进行优化。

时间复杂度 O ( N 3 w + M ) O(\frac{N^3}{w}+M) O(wN3+M)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
typedef long long ll;
typedef bitset <MAXN> bits;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
bits all, sub[MAXN], fat[MAXN], path[MAXN], g[MAXN];
int n, m, lca[MAXN][MAXN], step[MAXN][MAXN], father[MAXN];
vector <int> a[MAXN], tmp;
void efs(int pos, int fa, int from, int val) {
	step[from][pos] = val;
	tmp.push_back(pos);
	for (auto x : a[pos])
		if (x != fa) efs(x, pos, from, val);
}
void dfs(int pos, int fa) {
	father[pos] = fa;
	path[pos] = path[fa];
	path[pos].set(pos);
	sub[pos].set(pos);
	for (auto x : a[pos])
		if (x != fa) {
			dfs(x, pos);
			tmp.clear();
			efs(x, pos, pos, x);
			for (auto y : tmp)
			for (int i = sub[pos]._Find_first(); i <= n; i = sub[pos]._Find_next(i))
				lca[y][i] = lca[i][y] = pos;
			sub[pos] |= sub[x];
		}
	fat[pos] = all ^ sub[pos];
	fat[pos].set(pos);
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		all.set(i);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(1, 0);
	int l = 1, r = 0;
	static pair <int, int> q[MAXN * MAXN];
	for (int i = 1; i <= n; i++)
		g[i].set(i);
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		g[x].set(y), g[y].set(x);
		q[++r] = make_pair(x, y);
	}
	while (l <= r) {
		int x = q[l].first, y = q[l++].second;
		if (sub[y][x]) swap(x, y);
		bits ex, ey;
		if (sub[x][y]) ex = ey = sub[y] | path[y] | fat[step[x][y]];
		else {
			ex = ey = sub[y] | (path[y] ^ path[x]) | sub[x];
			ex.set(lca[x][y]), ey.set(lca[x][y]);
		}
		ex &= ~g[x] & g[y], ey &= ~g[y] & g[x];
		for (int i = ex._Find_first(); i <= n; i = ex._Find_next(i)) {
			g[i].set(x), g[x].set(i);
			q[++r] = make_pair(i, x);
		}
		for (int i = ey._Find_first(); i <= n; i = ey._Find_next(i)) {
			g[i].set(y), g[y].set(i);
			q[++r] = make_pair(i, y);
		}
	}
	cout << r << endl;
	return 0;
}

Atcoder Grand Contest 34

Problem D. Manhattan Max Matching

由于我们需要最大化答案,因此将绝对值去掉不会影响答案。

枚举两维坐标的符号,利用中间点优化建图,直接用费用流解题即可。

若将 s p f a spfa spfa 的时间复杂度看做 O ( N ) O(N) O(N) ,时间复杂度为 O ( N S ) O(NS) O(NS)

#include<bits/stdc++.h>
using namespace std;
const int MAXQ = 1e7 + 5;
const int MAXN = 1e3 + 5;
const int MAXP = 5e3 + 5;
const int INF = 1e9;
const long long INFLL = 1e18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct edge {int dest, flow, pos, cost; };
vector <edge> a[MAXP];
int s, t, tot, flow; ll cost;
ll dist[MAXP]; int path[MAXP], home[MAXP];
void FlowPath() {
	int p = t, ans = INF;
	while (p != s) {
		ans = min(ans, a[path[p]][home[p]].flow);
		p = path[p];
	}
	flow += ans;
	cost += ans * dist[t];
	p = t;
	while (p != s) {
		a[path[p]][home[p]].flow -= ans;
		a[p][a[path[p]][home[p]].pos].flow += ans;
		p = path[p];
	}
}
bool spfa() {
	static int q[MAXQ];
	static bool inq[MAXP];
	static int l = 0, r = 0;
	for (int i = 0; i <= r; i++)
		dist[q[i]] = -INFLL;
	q[l = r = 0] = s, dist[s] = 0, inq[s] = true;
	while (l <= r) {
		int tmp = q[l];
		for (unsigned i = 0; i < a[tmp].size(); i++)
			if (a[tmp][i].flow != 0 && dist[tmp] + a[tmp][i].cost > dist[a[tmp][i].dest]) {
				dist[a[tmp][i].dest] = dist[tmp] + a[tmp][i].cost;
				path[a[tmp][i].dest] = tmp;
				home[a[tmp][i].dest] = i;
				if (!inq[a[tmp][i].dest]) {
					q[++r] = a[tmp][i].dest;
					inq[q[r]] = true;
				}
			}
		l++, inq[tmp] = false;
	}
	return dist[t] != -INFLL;
}
void addedge(int x, int y, int z, int c) {
	a[x].push_back((edge){y, z, a[y].size(), c});
	a[y].push_back((edge){x, 0, a[x].size() - 1, -c});
}
int n, mid[4], cntx[MAXN], cnty[MAXN];
pair <int, int> posx[MAXN], posy[MAXN];
int main() {
	read(n), tot = 2 * n;
	s = ++tot, t = ++tot;
	for (int i = 0; i <= 3; i++)
		mid[i] = ++tot;
	for (int i = 1; i <= n; i++) {
		read(posx[i].first), read(posx[i].second), read(cntx[i]);
		addedge(s, i, cntx[i], 0);
		addedge(i, mid[0], INF, -posx[i].first - posx[i].second);
		addedge(i, mid[1], INF, posx[i].first - posx[i].second);
		addedge(i, mid[2], INF, -posx[i].first + posx[i].second);
		addedge(i, mid[3], INF, posx[i].first + posx[i].second);
	}
	for (int i = 1; i <= n; i++) {
		read(posy[i].first), read(posy[i].second), read(cnty[i]);
		addedge(n + i, t, cnty[i], 0);
		addedge(mid[0], n + i, INF, posy[i].first + posy[i].second);
		addedge(mid[1], n + i, INF, -posy[i].first + posy[i].second);
		addedge(mid[2], n + i, INF, posy[i].first - posy[i].second);
		addedge(mid[3], n + i, INF, -posy[i].first - posy[i].second);
	}
	for (int i = 1; i <= tot; i++)
		dist[i] = -INFLL;
	while (spfa()) FlowPath();
	writeln(cost);
	return 0;
}

Problem E. Complete Compress

考虑判断一个位置 r o o t root root 是否能够成为终点。

**引理 1 1 1 :**最优方案可以避免对存在祖孙关系的两点进行操作。

**证明:**找到最后一次这样的操作,我们用以下方式调整,使得最终方案中不存在这样的操作。令该操作设计两个棋子 A , B A,B A,B,所在节点为 x , y x,y x,y,其中 x x x y y y 的祖先。
( 1 ) (1) (1) 、若下一次操作不含 A , B A,B A,B,那么交换这两次操作。
( 2 ) (2) (2) 、若下一次操作为 ( A , C ) (A,C) (A,C),将 ( A , B ) , ( A , C ) (A,B),(A,C) (A,B),(A,C) 直接替换为 ( B , C ) (B,C) (B,C)
( 3 ) (3) (3) 、若下一次操作为 ( B , C ) (B,C) (B,C),且 x , y x,y x,y 间距离大于 2 2 2,那么那么交换这两次操作;否则 x , y x,y x,y 间距离为 2 2 2,即将 ( A , B ) , ( B , C ) (A,B),(B,C) (A,B),(B,C) 直接替换为 ( B , C ) (B,C) (B,C) 不会使得棋子位置的可重集发生变化。

**引理 2 2 2 :**令 x i x_i xi 为第 i i i 次操作的两个位置的 L c a Lca Lca ,最优方案可以避免存在 i , j   ( i < j ) i,j\ (i<j) i,j (i<j) ,满足 x i x_i xi y i y_i yi 的祖先。

**证明:**若 x i x_i xi x i + 1 x_{i+1} xi+1 的祖先,交换两次操作,操作序列依然合法。

由此,我们可以用树形 d p dp dp 解决该问题,记 s i z e i size_i sizei 表示子树中的棋子数, M a x i Max_i Maxi 表示子树内所有棋子的深度和, M i n i Min_i Mini 表示满足引理 1 1 1 的情况下,在子树内进行若干操作,可以使得子树内所有棋子的深度和达到的最小值。

r o o t root root 能够成为终点当且仅当 M i n r o o t = 0 Min_{root}=0 Minroot=0

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e3 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct info {
	int size, Max, Min;
} dp[MAXN];
int ans, now;
char s[MAXN];
vector <int> a[MAXN];
info operator + (info a, int one) {
	a.Max += a.size;
	a.Min += a.size;
	return a;
}
info operator + (info a, info b) {
	info ans;
	ans.size = a.size + b.size;
	ans.Max = a.Max + b.Max;
	if (a.Min > b.Min) swap(a, b);
	if (a.Max > b.Min) ans.Min = ans.Max & 1;
	else ans.Min = b.Min - a.Max;
	return ans;
}
void work(int pos, int fa, int depth) {
	dp[pos] = (info) {0, 0, 0};
	for (auto x : a[pos])
		if (x != fa) {
			work(x, pos, depth + 1);
			dp[pos] = dp[pos] + (dp[x] + 1);
		}
	if (s[pos] == '1') {
		dp[pos].size++;
		now += depth;
	}
}
int main() {
	int n; read(n), ans = 1e9;
	scanf("%s", s + 1);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	for (int i = 1; i <= n; i++) {
		now = 0, work(i, 0, 0);
		if (dp[i].Min == 0) chkmin(ans, now / 2);
	}
	if (ans == 1e9) puts("-1");
	else writeln(ans);
	return 0;
}

Problem F. RNG and XOR

由题意列出方程,有
x 0 = 0 x i = ∑ j p j x i ⊕ j + 1   ( i ≥ 1 ) x_0=0\\ x_i=\sum_{j}p_jx^{i\oplus j}+1\ (i\geq1) x0=0xi=jpjxij+1 (i1)

其中,第二种方程可以写作异或卷积的形式
( x 0 , x 1 , … , x 2 N − 1 ) ⊕ ( p 0 , p 1 , … , p 2 N − 1 ) = ( ? , x 1 − 1 , … , x 2 N − 1 − 1 ) (x_0,x_1,\dots,x_{2^N-1})\oplus(p_0,p_1,\dots,p_{2^N-1})=(?,x_1-1,\dots,x_{2^N-1}-1) (x0,x1,,x2N1)(p0,p1,,p2N1)=(?,x11,,x2N11)

注意到该卷积若成立, ( ∑ x i ) ( ∑ p i ) = ? + ∑ ( x i − 1 ) (\sum x_i)(\sum p_i)=?+\sum(x_i-1) (xi)(pi)=?+(xi1) ,因此 ? = x 0 + 2 N − 1 ?=x_0+2^N-1 ?=x0+2N1

因此
( x 0 , x 1 , … , x 2 N − 1 ) ⊕ ( p 0 , p 1 , … , p 2 N − 1 ) = ( x 0 + 2 N − 1 , x 1 − 1 , … , x 2 N − 1 − 1 ) (x_0,x_1,\dots,x_{2^N-1})\oplus(p_0,p_1,\dots,p_{2^N-1})=(x_0+2^N-1,x_1-1,\dots,x_{2^N-1}-1) (x0,x1,,x2N1)(p0,p1,,p2N1)=(x0+2N1,x11,,x2N11)
( x 0 , x 1 , … , x 2 N − 1 ) ⊕ ( p 0 − 1 , p 1 , … , p 2 N − 1 ) = ( 2 N − 1 , − 1 , … , − 1 ) (x_0,x_1,\dots,x_{2^N-1})\oplus(p_0-1,p_1,\dots,p_{2^N-1})=(2^N-1,-1,\dots,-1) (x0,x1,,x2N1)(p01,p1,,p2N1)=(2N1,1,,1)

使用异或卷积除法即可,注意 ( 2 N − 1 , − 1 , … , − 1 ) (2^N-1,-1,\dots,-1) (2N1,1,,1) F W T FWT FWT 后有且只有第 0 0 0 项为 0 0 0 ,剩余位置的值都可以通过直接除法得到,第 0 0 0 项处的值可取任意,这可能导致 x i x_i xi 被整体加上一个值,最后利用 x 0 = 0 x_0=0 x0=0 还原即可。

时间复杂度 O ( N × 2 N ) O(N\times2^N) O(N×2N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 18;
const int P = 998244353;
const int inv2 = (P + 1) / 2;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
void FWT(int *a, int N) {
	for (int len = 2; len <= N; len <<= 1)
	for (int i = 0; i < N; i += len)
	for (int j = i, k = i + len / 2; k < i + len; j++, k++) {
		int tmp = (a[j] + a[k]) % P, tnp = (a[j] - a[k] + P) % P;
		a[j] = tmp, a[k] = tnp;
	}
}
void UFWT(int *a, int N) {
	for (int len = 2; len <= N; len <<= 1)
	for (int i = 0; i < N; i += len)
	for (int j = i, k = i + len / 2; k < i + len; j++, k++) {
		int tmp = (a[j] + a[k]) % P, tnp = (a[j] - a[k] + P) % P;
		a[j] = 1ll * tmp * inv2 % P, a[k] = 1ll * tnp * inv2 % P;
	}
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int n, goal, p[MAXN], q[MAXN], res[MAXN];
int main() {
	read(n), goal = 1 << n;
	int sum = 0;
	for (int i = 0; i < goal; i++)
		read(p[i]), sum += p[i];
	sum = power(sum, P - 2);
	for (int i = 0; i < goal; i++) {
		p[i] = 1ll * p[i] * sum % P;
		q[i] = P - 1;
	}
	p[0] -= 1;
	q[0] = goal - 1;
	FWT(p, goal);
	FWT(q, goal);
	for (int i = 1; i < goal; i++)
		res[i] = 1ll * q[i] * power(p[i], P - 2) % P;
	UFWT(res, goal);
	for (int i = 0; i < goal; i++)
		writeln((res[i] - res[0] + P) % P);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值