CrCPC 2024 做题笔记 / 代码集

CrCPC 2024 做题笔记 / 代码集

[CrCPC 2024] A - AI 的末日

随便维护一下转移即可。

瓶颈离散化,复杂度 \(O(n \log n)\)

ケロシの代码
const int N = 1e6 + 5;
int n, k, a[N];
int b[N], len;
int f[N], mn, cnt;
void solve() {
	cin >> n >> k;
	FOR(i, 1, n) cin >> a[i];
	FOR(i, 1, n) b[++ len] = a[i];
	sort(b + 1, b + len + 1);
	len = unique(b + 1, b + len + 1) - b - 1;
	FOR(i, 1, n) a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
	if(len < k) {
		cout << 0 << endl;
		return;
	}
	cnt = k;
	FOR(i, 1, n) {
		if(f[a[i]] == mn) {
			cnt --;
			if(! cnt) mn ++, cnt = k - 1, f[a[i]] = mn + 1;
			else f[a[i]] = mn + 1;
		}
	}
	cout << mn << endl;
}

[CrCPC 2024] B - 平凡的数论题

考虑因为 \(B > \max a_i,b_i,c_i\),所以 \(B\) 可以当成进制考虑,右边的式子永远不会进位,左边的式子会随着 \(B\) 增大而进位减少,所以这个式子是关于 \(B\) 单调的,直接二分即可。

时间复杂度 \(O(n^2 \log V)\)

ケロシの代码
const int N = 2e3 + 15;
const ll LNF = 2e18;
int n, m, k, mx;
int a[N], b[N], c[N];
ll f[N];
void solve() {
	cin >> n; ROF(i, n - 1, 0) cin >> a[i];
	cin >> m; ROF(i, m - 1, 0) cin >> b[i];
	cin >> k; ROF(i, k - 1, 0) cin >> c[i];
	int mx = 0, len = max(n + m + 1, k);
	REP(i, n) chmax(mx, a[i]);
	REP(i, m) chmax(mx, b[i]);
	REP(i, k) chmax(mx, c[i]);
	ll L = mx + 1, R = LNF;
	while(L <= R) {
		ll mid = L + R >> 1;
		FOR(i, 0, len) f[i] = 0;
		REP(i, n) REP(j, m) {
			f[i + j] += 1ll * a[i] * b[j];
			f[i + j + 1] += f[i + j] / mid;
			f[i + j] %= mid;
		}
		bool ok = 1;
		ROF(i, len, 0) {
			if(f[i] > c[i]) { L = mid + 1; ok = 0; break; }
			if(f[i] < c[i]) { R = mid - 1; ok = 0; break; }
		}
		if(ok) {
			cout << mid << endl;
			return;
		}
	}
	cout << "impossible" << endl;
}

[CrCPC 2024] C - 修路

时间复杂度 \(O(n^2 \log n)\)

ケロシの代码
const int N = 1.5e3 + 5;
const ll LNF = 1e18;
struct Point {
	ll x, y;
	Point(ll x = 0, ll y = 0): x(x), y(y) {}
};
struct Vector {
	ll x, y;
	Vector(ll x = 0, ll y = 0): x(x), y(y) {}
};
Vector operator - (Point A, Point B) {
	return Vector(A.x - B.x, A.y - B.y);
}
ll Dot(Vector A, Vector B) {
	return A.x * B.x + A.y * B.y;
}
ll Cross(Vector A, Vector B) {
	return A.x * B.y - A.y * B.x;
}
double Length(Vector A) {
	return sqrt(Dot(A, A));
}
double Angle(Vector A, Vector B) {
	return acos(Dot(A, B) / Length(A) / Length(B));
}
int n; double K;
Point a[N];
double F[N][2], A[N], B[N];
int p[N], len;
struct fenwick {
	int c[N];
	void clear() {
		FOR(i, 1, n) c[i] = 0;
	}
	int lowbit(int x) {
		return - x & x;
	}
	void modify(int u, int x) {
		for(int i = u; i <= n; i += lowbit(i)) 
			c[i] += x;
	}
	void modify(int l, int r, int x) {
		modify(l, x);
		modify(r + 1, - x);
	}
	int query(int u) {
		int res = 0;
		for(int i = u; i; i -= lowbit(i))
			res += c[i];
		return res;
	}
} t;
void solve() {
	cin >> n >> K;
	FOR(i, 1, n) cin >> a[i].x >> a[i].y;
	FOR(i, 2, n) F[i][0] = F[i][1] = LNF;
	FOR(i, 1, n - 1) {
		chmin(F[i][0], F[i][1] + K);
		chmin(F[i][1], F[i][0] + K);
		chmin(F[i + 1][0], F[i][0] + Length(a[i + 1] - a[i]));
		chmin(F[i + 1][1], F[i][1] + Length(a[i + 1] - a[i]));
		FOR(j, i + 1, n) A[j] = Angle(Vector(- 1, 0), a[j] - a[i]);
		len = 0;
		FOR(j, i + 1, n) B[++ len] = A[j];
		sort(B + 1, B + len + 1);
		FOR(j, i + 1, n) p[j] = lower_bound(B + 1, B + len + 1, A[j]) - B;
		t.clear();
		FOR(j, i + 2, n) {
			int u = p[j] > p[i + 1];
			int v = Cross(a[i] - a[j], a[j - 1] - a[j]) < 0;
			chmin(F[j][v], F[i][u] + Length(a[j] - a[i]) + K * t.query(p[j]));
			int l = p[j - 1], r = p[j];
			if(l > r) swap(l, r);
			t.modify(l + 1, r - 1, 1);
		}
	}
	cout << fixed << setprecision(10);
	cout << min(F[n][0], F[n][1]) << endl;
}

[CrCPC 2024] D - 排序

考虑每次变换有 \(\binom{n+3}{3}\) 种情况,直接对于所有的排列做 bfs 复杂度会爆炸。

注意到答案小于等于 \(6\),所以可以双向 bfs,每次最多搜三步。

那么时间复杂度 \(O(\binom{n+3}{3}^3n\log n)\)

ケロシの代码
const int N = 12;
const int M = 4e6 + 5;
const int INF = 1e9 + 7;
int n, fac[N];
int F[M], G[M];
struct fenwick {
	int c[N];
	void clear() {
		FOR(i, 1, n) c[i] = 0;
	}
	inline int lowbit(int x) {
		return - x & x;
	}
	void modify(int u) {
		for(int i = u + 1; i <= n; i += lowbit(i)) 
			c[i] ++;
	}
	int query(int u) {
		int res = 0;
		for(int i = u + 1; i; i -= lowbit(i))
			res += c[i];
		return res;
	}
} t;
int inc(vector<int> a) {
	t.clear();
	int res = 0;
	REP(i, n) {
		res += (a[i] - t.query(a[i] - 1)) * fac[n - i - 1];
		t.modify(a[i]);
	}
	return res;
}
vector<int> dec(int res) {
	vector<bool> f(n);
	vector<int> a(n);
	REP(i, n) {
		int x = res / fac[n - i - 1];
		res %= fac[n - i - 1];
		int c = 0;
		REP(j, n) if(! f[j]) {
			if(c == x) {
				a[i] = j;
				f[j] = 1;
				break;
			}
			c ++;
		}
	}
	return a;
}
void solve() {
	cin >> n;
	vector<int> a(n);
	REP(i, n) cin >> a[i], a[i] --;
	fac[0] = 1;
	FOR(i, 1, n) fac[i] = fac[i - 1] * i;
	REP(i, fac[n]) F[i] = G[i] = INF;
	queue<int> q;
	F[inc(a)] = 0; q.push(inc(a));
	while(! q.empty()) {
		int u = q.front();
		q.pop();
		vector<int> a = dec(u);
		FOR(i, - 1, n - 1)	FOR(j, i, n - 1) FOR(k, j, n - 1) {
			vector<int> b;
			FOR(p, j + 1, k) b.push_back(a[p]);
			FOR(p, 0, i) b.push_back(a[p]);
			FOR(p, k + 1, n - 1) b.push_back(a[p]);
			FOR(p, i + 1, j) b.push_back(a[p]);
			int v = inc(b);
			if(F[v] == INF) {
				F[v] = F[u] + 1;
				if(F[v] <= 2) q.push(v);
			}
		}
	}
	REP(i, n) a[i] = i;
	G[inc(a)] = 0; q.push(inc(a));
	while(! q.empty()) {
		int u = q.front();
		q.pop();
		vector<int> a = dec(u);
		FOR(i, - 1, n - 1)	FOR(j, i, n - 1) FOR(k, j, n - 1) {
			vector<int> b;
			FOR(p, i + 1, j) b.push_back(a[p]);
			FOR(p, k + 1, n - 1) b.push_back(a[p]);
			FOR(p, 0, i) b.push_back(a[p]);
			FOR(p, j + 1, k) b.push_back(a[p]);
			int v = inc(b);
			if(G[v] == INF) {
				G[v] = G[u] + 1;
				if(G[v] <= 2) q.push(v);
			}
		}
	}
	int ans = INF;
	REP(i, fac[n]) chmin(ans, F[i] + G[i]);
	cout << ans << endl;
}

[CrCPC 2024] E - 萌萌交互题

由于要猜的序列随机,所以直接从 \(1\) 开始一个一个试即可,那么代价就是序列的最大值。

枚举每一个最大值有多少种方案,不难想到转化成最大值小于等于一个数的方案,然后做差分。

那么推公式后就变成了一个 \(k\) 次幂和问题。

忽略快速幂和逆元,时间复杂度 \(O(n)\)

ケロシの代码
const int N = 1e6 + 5;
const int P = 1e9 + 7;
typedef Modint<P> mint;
mint fac[N], f[N][2], s[N];
int fp(int x, int y) {
	int res = 1;
	for(; y; y >>= 1) {
		if(y & 1) res = (1ll * res * x) % P;
		x = (1ll * x * x) % P;
	}
	return res;
}
mint calc(mint n, int k) {
	fac[0] = f[0][0] = f[k + 3][1] = 1;
	FOR(i, 1, k + 2) fac[i] = fac[i - 1] * i;
	FOR(i, 1, k + 2) f[i][0] = f[i - 1][0] * (n - i);
	ROF(i, k + 2, 1) f[i][1] = f[i + 1][1] * (n - i);
	FOR(i, 1, k + 2) s[i] = s[i - 1] + fp(i, k);
	mint ans = 0;
	FOR(i, 1, k + 2) 
		ans += s[i] * f[i - 1][0] * f[i + 1][1] / (fac[k + 2 - i] * fac[i - 1] * ((k - i) % 2 ? P - 1 : 1));
	return ans;
}
void solve() {
	int n, k;
	cin >> n >> k;
	mint ans = mint(k) * fp(k, n);
	ans -= calc(k - 1, n);
	ans /= fp(k, n);
	cout << ans << endl;
}

[CrCPC 2024] F - 取名

直接模拟,时间复杂度 \(O(n)\)

ケロシの代码
const int N = 1e6 + 5;
int n, a[N], vis[N];
void solve() {
	cin >> n;
	FOR(i, 1, n) cin >> a[i];
	int c0 = 0, c1 = 0;
	FOR(i, 1, n) {
		vis[a[i]] = 1;
		if(a[i] % 2) c1 ++;
		else c0 ++;
	}
	int pos = 2 - (c1 > c0);
	while(vis[pos]) pos += 2;
	cout << pos << endl;
}

[CrCPC 2024] G - 传传爆

首先注意到进入任意一个白点都是一样的,所以策略一定是要么直接走到终点,要么走到最近的白点,每次决策是继续走白点还是直接走到终点。

首先把白点需要一步或两步再次走到白点,通过一步还是两步分为两类。

不难发现对于一类白点,存在一个阈值,使得到终点的距离小于这个阈值就直接走向终点,否则继续走白点。

根据数学直觉,不难发现两类的期望步数都是关于阈值单峰的,所以直接两次三分即可。

时间复杂度 \(O(nm)\)

ケロシの代码
const int N = 1e3 + 5;
const int M = 1e6 + 5;
const int INF = 1e9 + 7;
ll gcd(ll x, ll y) {
	if(! y) return x;
	return gcd(y, x % y);
}
struct Fint {
	ll x, y;
	Fint(ll _x = 0, ll _y = 1) {
		x = _x, y = _y;
		ll g = gcd(x, y);
		x /= g, y /= g;
	}
	friend Fint operator * (Fint A, Fint B) {
		return Fint(A.x * B.x, A.y * B.y);
	}
	friend Fint operator / (Fint A, Fint B) {
		return Fint(A.x * B.y, A.y * B.x);
	}
	friend Fint operator + (Fint A, Fint B) {
		return Fint(A.x * B.y + A.y * B.x, A.y * B.y);
	}
	friend Fint operator - (Fint A, Fint B) {
		return Fint(A.x * B.y - A.y * B.x, A.y * B.y);
	}
	friend ostream & operator << (ostream & os, Fint A) {
		return os << A.x << '/' << A.y;
	}
	friend bool operator > (Fint A, Fint B) {
		return A.x * B.y > A.y * B.x;
	}
};
int n, m, dis;
int d[N][N];
string s[N];
int dx[] = {0, 0, 1, - 1};
int dy[] = {1, - 1, 0, 0};
bool b[N][N];
ll s1[M], s2[M];
int t1, t2, l1, l2;
Fint get(int i, int j) {
	if(! i && ! j) return Fint(INF);
	return Fint(s1[i] + s2[j] + t1 - i + (t2 - j) * 2, i + j) + dis;
}
Fint query(int i) {
	Fint res(INF);
	int L = 0, R = l2;
	while(R - L >= 3) {
		int mid = L + R >> 1;
		if(get(i, mid) > get(i, mid + 1)) {
			L = mid;
		}
		else {
			R = mid;
		}
	}
	FOR(j, L, R) chmin(res, get(i, j));
	return res;
}
void solve() {
	cin >> n >> m;
	FOR(i, 1, n) {
		cin >> s[i];
		s[i] = ' ' + s[i];
	}
	FOR(i, 1, n) FOR(j, 1, m) d[i][j] = INF;
	FOR(i, 1, n) FOR(j, 1, m) b[i][j] = 0;
	FOR(x, 1, n) FOR(y, 1, m) if(s[x][y] == 'B') {
		REP(i, 4) {
			int px = x + dx[i];
			int py = y + dy[i];
			if(px < 1 || px > n || py < 1 || py > m) continue;
			if(s[px][py] == 'B') b[x][y] = 1;
		}
	}
	queue<PII> q;
	d[n][m] = 0;
	q.push({n, m});
	t1 = t2 = l1 = l2 = 0;
	while(! q.empty()) {
		auto h = q.front();
		q.pop();
		int x, y; tie(x, y) = h;
		if(s[x][y] == 'B') {
			if(b[x][y]) s1[++ l1] = d[x][y];
			else s2[++ l2] = d[x][y];
			continue;
		}
		REP(i, 4) {
			int px = x + dx[i];
			int py = y + dy[i];
			if(px < 1 || px > n || py < 1 || py > m) continue;
			if(d[px][py] == INF) {
				d[px][py] = d[x][y] + 1;
				q.push({px, py});
			}
		}
	}
	FOR(i, 1, n) FOR(j, 1, m) if(s[i][j] == 'B') {
		(b[i][j] ? t1 : t2) ++;
	}
	FOR(i, 1, l1) s1[i] += s1[i - 1];
	FOR(i, 1, l2) s2[i] += s2[i - 1];
	dis = INF;
	FOR(i, 1, n) FOR(j, 1, m) if(s[i][j] == 'B')
		chmin(dis, i + j - 2);
	Fint ans(d[1][1]);
	int L = 0, R = l1;
	while(R - L >= 3) {
		int mid = L + R >> 1;
		if(query(mid) > query(mid + 1)) {
			L = mid;
		}
		else {
			R = mid;
		}
	}
	FOR(i, L, R) chmin(ans, query(i));
	cout << ans << endl;
}

[CrCPC 2024] H - 信步山中

按照题意模拟建图,然后跑 SPFA 即可。

时间复杂度 \(O(nm)\)

ケロシの代码
const int N = 2e3 + 5;
const int M = 2e5 + 5;
const ll LNF = 1e18;
int n, st, ed;
int m1, fu1[M], fv1[M], fw1[M];
int m2, fu2[M], fv2[M], fw2[M];
vector<PII> e[N];
ll d1[N], d2[N]; bool vis[N];
ll d[N]; int c[N];
void clear() {
	FOR(i, 1, n * 2) e[i].clear();
}
void add(int u, int v, int w) {
	e[u].push_back({v, w});
}
void solve() {
	cin >> n >> st >> ed;
	cin >> m1;
	FOR(i, 1, m1) cin >> fu1[i] >> fv1[i] >> fw1[i];
	cin >> m2;
	FOR(i, 1, m2) cin >> fu2[i] >> fv2[i] >> fw2[i];
	FOR(i, 1, m1) {
		add(fu1[i], fv1[i], fw1[i]);
		add(fv1[i], fu1[i], fw1[i]);
	}
	priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> pq;
	FOR(i, 1, n) d1[i] = LNF;
	d1[ed] = 0;
	pq.push({0, ed});
	while(! pq.empty()) {
		int u = SE(pq.top());
		pq.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(auto h : e[u]) {
			int v, w; tie(v, w) = h;
			if(d1[v] > d1[u] + w) {
				d1[v] = d1[u] + w;
				pq.push({d1[v], v});
			}
		}
	}
	clear();
	FOR(i, 1, m2) {
		add(fu2[i], fv2[i], fw2[i]);
		add(fv2[i], fu2[i], fw2[i]);
	}
	FOR(i, 1, n) d2[i] = LNF;
	FOR(i, 1, n) vis[i] = 0;
	d2[ed] = 0;
	pq.push({0, ed});
	while(! pq.empty()) {
		int u = SE(pq.top());
		pq.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(auto h : e[u]) {
			int v, w; tie(v, w) = h;
			if(d2[v] > d2[u] + w) {
				d2[v] = d2[u] + w;
				pq.push({d2[v], v});
			}
		}
	}
	clear();
	FOR(i, 1, m1) {
		if(d1[fu1[i]] > d1[fv1[i]]) add(fu1[i], fv1[i] + n, fw1[i]);
		if(d1[fu1[i]] < d1[fv1[i]]) add(fv1[i], fu1[i] + n, fw1[i]);
	}
	FOR(i, 1, m2) {
		if(d2[fu2[i]] > d2[fv2[i]]) add(fu2[i] + n, fv2[i], fw2[i]);
		if(d2[fu2[i]] < d2[fv2[i]]) add(fv2[i] + n, fu2[i], fw2[i]);
	}
	queue<int> q;
	FOR(i, 1, n * 2) d[i] = - LNF;
	FOR(i, 1, n * 2) vis[i] = 0;
	d[st] = 0;
	q.push(st);
	while(! q.empty()) {
		int u = q.front();
		q.pop(); vis[u] = 0;
		for(auto h : e[u]) {
			int v, w; tie(v, w) = h;
			if(d[v] < d[u] + w) {
				d[v] = d[u] + w;
				if(! vis[v]) {
					c[v] ++;
					if(c[v] > n * 2) {
						cout << - 1 << endl;
						return;
					}
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
	ll ans = max(d[ed], d[ed + n]);
	cout << (ans == - LNF ? - 1 : ans) << endl;
}

[CrCPC 2024] I - 别样的滚榜大战

模拟,时间复杂度 \(O(n)\)

ケロシの代码
const int N = 1e3 + 5;
int n, m;
ll b[N], a[N];
string name[N];
void solve() {
	cin >> n >> m;
	FOR(i, 1, n + 1) {
		cin >> name[i];
		REP(_, m) {
			string s; cin >> s;
			if(s[0] == '-') continue;
			b[i] ++;
			ll x = s[1] - '0';
			ll H = (s[3] - '0') * 10 + (s[4] - '0');
			ll M = (s[6] - '0') * 10 + (s[7] - '0');
			ll S = (s[9] - '0') * 10 + (s[10] - '0');
			ll val = S + (M + (x - 1) * 20) * 60 + H * 60 * 60;
			a[i] += val;
		}
	}
	int rk = 1;
	FOR(i, 1, n) if(name[i] != name[n + 1]) {
		if(b[i] > b[n + 1]) rk ++;
		if(b[i] == b[n + 1]) {
			if(a[i] < a[n + 1]) rk ++;
			if(a[i] == a[n + 1]) {
				if(name[i] < name[n + 1]) rk ++;
			}
		}
	}
	cout << rk << endl;
}

[CrCPC 2024] J - 搬东西

考虑答案会变成什么样子,设所有段的最左处是 \(L\),最右处是 \(R\)

那么对于答案,首先有一个主方向,比如向右,那么存在点 \(x,y\) 满足 \(x<y\),那么主路线即为 \(x \to L \to R \to y\),那么所有向右的线段都能被经过。接下来还剩下一些没被经过的 \(r \to l\) 的向左的段,直接花费 \(2(r-l)\) 的代价即可。

直接枚举后贪心就这个东西即可。

时间复杂度 \(O(n \log n)\)

ケロシの代码
const int N = 1e5 + 5;
const int INF = 1e9 + 7;
const ll LNF = 1e18;
int Len, n;
int a[N], b[N];
void solve() {
	cin >> Len >> n;
	FOR(i, 1, n) cin >> a[i] >> b[i];
	ll ans = LNF;
	REP(_, 2) {
		int L = INF, R = - INF;
		FOR(i, 1, n) chmin(L, a[i]), chmin(L, b[i]);
		FOR(i, 1, n) chmax(R, a[i]), chmax(R, b[i]);
		vector<PII> e, f;
		FOR(i, 1, n) if(a[i] > b[i]) {
			e.push_back({b[i], 1});
			e.push_back({a[i], - 1});
		}
		sort(begin(e), end(e));
		int val = 0, pl = 0;
		f.push_back({L, L});
		for(auto h : e) {
			if(val == 0) pl = FI(h);
			val += SE(h);
			if(val == 0) f.push_back({pl, FI(h)});
		}
		f.push_back({R, R});
		ll res = L;
		for(auto h : f) {
			chmin(ans, 2ll * (R - L) + res - FI(h));
			res = min(res + 2ll * (SE(h) - FI(h)), (ll) SE(h));
		}
		FOR(i, 1, n) a[i] = - a[i], b[i] = - b[i];
	}
	cout << ans << endl;
}

[CrCPC 2024] K - 牙牙学语

考虑建出字典树,考虑有两个点从根开始走,一直走相同的字符,如果遇到了结束位置就可以回到根,若两个点同时对应了两个终点,则不合法。所以直接在字典树上 dfs 即可,同时记录一下走过的位置防止重复经过。

时间复杂度 \(O(t^2)\)

ケロシの代码
const int N = 1e6 + 5;
int tr[N][26], ed[N], idx;
bool f[N][26], ok;
void clear() {
	FOR(i, 0, idx) {
		REP(j, 26) f[i][j] = tr[i][j] = 0;
		ed[i] = 0;
	}
	idx = 0;
}
void dfs(int u, int v) {
	if(ok) return;
	if(u == v && ed[u] >= 2) {
		ok = 1;
		return;
	}
	if(u != v && ed[u] && ed[v]) {
		ok = 1;
		return;
	}
	REP(i, 26) {
		if(u == tr[0][i] && f[v][i]) return;
		if(v == tr[0][i] && f[u][i]) return;
	}
	REP(i, 26) {
		if(u == tr[0][i]) f[v][i] = 1;
		if(v == tr[0][i]) f[u][i] = 1;
	}
	REP(i, 26) if(tr[u][i] && tr[v][i]) dfs(tr[u][i], tr[v][i]);
	REP(i, 26) if(ed[u] && tr[0][i] && tr[v][i]) dfs(tr[0][i], tr[v][i]);
	REP(i, 26) if(tr[u][i] && ed[v] && tr[0][i]) dfs(tr[u][i], tr[0][i]);
}
void solve() {
	clear();
	REP(i, 26) {
		string s; cin >> s;
		int u = 0;
		for(char c : s) {
			if(! tr[u][c - 'a']) tr[u][c - 'a'] = ++ idx;
			u = tr[u][c - 'a'];
		}
		ed[u] ++;
	}
	ok = 0;
	dfs(0, 0);
	cout << (ok ? "Ruzan" : "Krasan") << endl;
}
posted @ 2025-03-24 14:18  KevinLikesCoding  阅读(65)  评论(0)    收藏  举报