22.2 雜題

1623D [4]

首先找到循環節,假設長度爲 \(L\),然後設答案爲 \(E\),中間有 \(k\) 個位置可以搞到。有 \(E=(1-(1-p)^k)(E+L)+\sum (i-1)p(1-p)^{q-1}\)。後面那一坨就代表在中間停下的期望。然後解方程模擬即可。

/*
Time : 2022/01/02 11:09 
Author : Gemini7X
Problem : https://codeforces.com/contest/1623/problem/D
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp make_pair
#define F first
#define S second
using namespace std;
const int maxn = 200005, mod = 1e9 + 7;
int add(int a, int b) { return a + b >= mod ? a + b - mod : a + b; }
int dec(int a, int b) { return a - b < 0 ? a - b + mod : a - b; }
int mul(int a, int b) { return 1ll * a * b % mod; }
int ksm(int a, int b = mod - 2) { int ret = 1; for (; b; b >>= 1, a = mul(a, a)) if (b & 1) ret = mul(ret, a); return ret; }
int n, m, rb, cb, rd, cd, p;
pii stk[maxn << 2];
int top;
void run(int &x, int &y, int dx, int dy) {
	x += dx; y += dy;
}
void work(int x, int y, int &dx, int &dy) {
	if (x == 1 && dx == -1) dx = -dx;
	if (x == n && dx == 1) dx = -dx;
	if (y == 1 && dy == -1) dy = -dy;
	if (y == m && dy == 1) dy = -dy;
}
void solve() {
	cin >> n >> m >> rb >> cb >> rd >> cd >> p;
	p = mul(p, ksm(100));
	int x = rb, y = cb, dx = 1, dy = 1; top = 0;
	work(x, y, dx, dy);
	int ux = dx, uy = dy;
	while (1) {
		stk[++top] = mp(x, y);
		run(x, y, dx, dy);
		work(x, y, dx, dy);
		if (x == rb && y == cb && dx == ux && dy == uy) break;
	}
	int k = 0, ans = 0;
	for (int i = 1; i <= top; i++) {
		x = stk[i].F, y = stk[i].S;
		if (x == rd || y == cd) {
			ans = add(ans, mul(mul(p, i - 1), ksm(dec(1, p), k)));
			k++;
		}
	}
	ans = add(ans, mul(top, ksm(dec(1, p), k)));
	printf("%d\n", mul(ans, ksm(dec(1, ksm(dec(1, p), k)))));
}
int main() {
	int T; cin >> T; while (T--) solve();
	return 0;
}

1623E [5]

一開始有個誤區就是要按a,b,c,...的順序擴,其實不用。先處理出double了之後會更優的節點,這個可以通過處理 in-order 來解決。然後考慮一個貪心,還是按照 in-order 來 dfs,在每個節點記錄一個 cost 代表 double 了之後有一個沒有 double 的祖先要被處理。如果 cost 大於 k 了也就不需要管了,否則先遍歷左兒子,假設左兒子被 double 了,那麼這個點也一定要被 double。否則假設這個點是 good 的,那麼就可以 double 它。然後是一步很關鍵的點,只有當這個點被 double 了纔可以取遍歷其右兒子,否則如果右子樹內有點被 double,那麼這個點也要被 double,而這個點之前沒有被 double 一定是因爲它不優,那麼這樣最後的答案也就不優了。所以在遍歷右兒子的時候已經可以把 cost 設成 1,從新開始遍歷一棵樹了。

/*
Time : 2022/01/02 12:50
Author : Gemini7X
Problem : https://codeforces.com/contest/1623/problem/E
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200005;
int n, k;
char s[maxn];
int ls[maxn], rs[maxn];
int stk[maxn], top;
bool good[maxn], mark[maxn];
void dfs(int u) {
	if (ls[u]) dfs(ls[u]);
	stk[++top] = u;
	if (rs[u]) dfs(rs[u]); 
}
void get_ans(int u, int cost) {
	if (cost > k) return;
	if (ls[u]) get_ans(ls[u], cost + 1);
	if (mark[ls[u]]) mark[u] = 1;
	else if (good[u]) mark[u] = 1, k -= cost;
	if (rs[u] && mark[u]) get_ans(rs[u], 1);
}
int main() {
	scanf("%d%d", &n, &k);
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++) scanf("%d%d", ls + i, rs + i);
	dfs(1);
	int lst = s[stk[n]];
	for (int i = n - 1; i >= 1; i--) {
		if (s[stk[i]] != lst) {
			if (s[stk[i]] < lst) good[stk[i]] = 1;
			lst = s[stk[i]];
		} else good[stk[i]] = good[stk[i + 1]];
	}
	get_ans(1, 1);
	for (int i = 1; i <= n; i++) {
		putchar(s[stk[i]]);
		if (mark[stk[i]]) putchar(s[stk[i]]);
	}
	return 0;
}

1622E [3]

也就是一個比較騷的操作,絕對值看起來不好處理,拆掉!然後 \(2^n\) 枚舉正負,然後就是個排序不等式。

#include <bits/stdc++.h>
using namespace std;
template <typename T>
void read(T &num) {
	T flg = 1;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') flg = -1;
	for (num = 0; isdigit(ch); ch = getchar()) num = num * 10 + ch - '0';
	num *= flg;
}
int n, m, x[15], a[10005], ans;
int p[10005], rk[10005];
char s[15][10005];
bool cmp(int i, int j) {
	return a[i] < a[j];
}
void solve() {
	read(n); read(m);
	for (int i = 1; i <= n; i++) read(x[i]);
	for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1);
	ans = -1; 
	for (int msk = 0; msk < (1 << n); msk++) {
		int now = 0; 
		for (int j = 1; j <= m; j++) a[j] = 0, rk[j] = j;
		for (int j = 1; j <= n; j++) {
			if ((msk >> (j - 1)) & 1) {
				now += x[j];
				for (int k = 1; k <= m; k++) if (s[j][k] == '1') a[k]--;
			} else {
				now -= x[j];
				for (int k = 1; k <= m; k++) if (s[j][k] == '1') a[k]++;
			}
		}
		sort(rk + 1, rk + 1 + m, cmp);
		for (int j = 1; j <= m; j++) now += j * a[rk[j]];
		if (now > ans) {
			ans = now;
			for (int j = 1; j <= m; j++) p[rk[j]] = j;
		}
	}
	for (int i = 1; i <= m; i++) printf("%d ", p[i]);
	puts("");
}
int main() {
	int T; read(T); while (T--) solve();
	return 0;
}

1622F [5]

實屬詐騙了。手玩或推導發現答案至少是 \(n-3\)。考慮把相鄰的配對即可。接下來考慮用 xor 來哈希,給每個質數隨機一個 \([0,2^64]\) 範圍內的值。然後將每個數替換成這些質數隨機的值的 xor。顯然對於一個完全平方數這樣異或出來的值是 \(0\)。於是問題就變成了能否找到一個位置等於全部的異或和,或兩個位置異或起來等於全部的異或和,這個隨便 map 搞一個下就好了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1000005;
mt19937 rnd(20060413);
template <typename T>
void read(T &num) {
	T flg = 1;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') flg = -1;
	for (num = 0; isdigit(ch); ch = getchar()) num = num * 10 + ch - '0';
	num *= flg;
}
int n;
int prime[maxn], cnt;
bool mark[maxn];
ull H[maxn], HH[maxn], val[maxn];
int mn[maxn];
map<ull, int> mp;
void solve() {
	read(n);
	for (int i = 2; i <= n; i++) {
		if (!mark[i]) {
			prime[++cnt] = i;
			mn[i] = i;
		}
		for (int j = 1; j <= cnt && prime[j] * i <= n; j++) {
			mark[i * prime[j]] = 1;
			mn[i * prime[j]] = prime[j];
			if (i % prime[j] == 0) break;
		}
	}
	for (int i = 1; i <= cnt; i++) {
		val[i] = ((ull)rnd() << 32) | rnd();
		for (ll now = prime[i]; now <= n; now *= prime[i]) {
			for (ll j = now; j <= n; j += now) {
				H[j] ^= val[i];
			}
		}
	}
	for (int i = 2; i <= n; i++) HH[i] = HH[i - 1] ^ H[i];
	ull s = 0;
	for (int i = 2; i <= n; i++) s ^= HH[i];
	if (s == 0) {
		printf("%d\n", n);
		for (int i = 1; i <= n; i++) printf("%d ", i);
		puts("");
	} else {
		for (int i = 2; i <= n; i++) {
			if ((HH[i] ^ s) == 0) {
				printf("%d\n", n - 1);
				for (int j = 1; j <= n; j++) {
					if (j == i) continue;
					printf("%d ", j);
				} puts("");
				return;
			}
		}
		for (int i = 2; i <= n; i++) {
			if (mp.find(HH[i]) != mp.end()) {
				printf("%d\n", n - 2);
				for (int j = 1; j <= n; j++) {
					if (j == i || j == mp[HH[i]]) continue;
					printf("%d ", j);
				} puts("");
				return;
			}
			mp[HH[i] ^ s] = i;
		}
		int k = n / 2;
		printf("%d\n", n - 3);
		for (int i = 1; i <= n; i++) {
			if (i == 2 || i == k || i == n) continue;
			printf("%d ", i);
		} puts("");
		return;
	}
}
int main() {
	int T = 1; while (T--) solve();
	return 0;
}

CF 1634F

一個非常妙的轉換。考慮 \(C_i=A_i-B_i\),然後只用在 \(C\) 上做操作。然後這個區間加考慮差分,這裏差分的姿勢奇怪一點 \(D_1=C_1,D_i=C_i-C_{i-1}-C_{i-2}\)。這樣發現區間加 \(F\) 即變爲單點加,於是就做完了。

code

ABC217H

被稱作經典題。。。

先考慮一個dp,設 \(f_{i,p}\) 代表在時刻 \(T_i\) 時處在 \(p\) 位置的最小傷害,以 \(d=0\) 爲例,\(d=1\) 反過來即可。記 \(m_i=T_i-T{i-1}\)。有轉移 \(f_{i,p}=\min_{p-m_i\le q\le <p+m_i}f_{i-1,q}+\max(x_i-p,0)\)。如果 \(x_i\)\(T_i\) 還大可以考慮將 \(x_i\) 變成 \(T_i\),於是可以只考慮到 \(T_i\) 即可。然後我們發現這個dp是一個下凸殼,於是可以用一個大根堆(維護斜率小於 0),一個 ans(維護斜率等於 0)以及一個小根堆(維護斜率大於 0)的部分。

轉移的第一步相當於把斜率小於 \(0\) 的部分像左平移 \(m_i\) 個單位,大於 \(0\) 部分同理向右平移。然後再加上一段斜率 \(-1\) 的射線,到 \(x_i\) 爲止。那麼這個時候分類討論一下,是否會撞到右半邊第一個點,不會的話就直接放進左邊增加一段。會的話,右邊一部分就會變成平的,而平的部分會變成左邊的斜率小於 \(0\) 的部分。

關於偏移量,其實當前 \(T_i\) 就是偏移量。注意我們這些操作其實都有可能有中間插入,所以得用堆。

code

loj3563 BalticOI2021

\(f[u,0/1,0/1]\) 代表以 \(u\) 爲根的子樹,\(u\) 是否翻了,以及 \(u\)\(0\) 還是 \(1\)。然後使得子樹內所有點是 \(0\) 的最小次數。

轉移就做卷積即可。此題關鍵在於想到 \(f[u,0/1]\) 的 dp 後發現需要再加一維狀態。

code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章