1. 程式人生 > 實用技巧 >「NOIP」 聯賽模擬測試44

「NOIP」 聯賽模擬測試44

「NOIP」 聯賽模擬測試44

long long ago,I remember someone set a flag...

什麼都能做,如果你非得樹剖加一個 \(log\),應該也能過。

不過倍增顯然更好處理。

看題目有一個特別好的一個點來:每次詢問的 \(v\) 保證是 \(u\) 的祖先。

所以預處理一個倍增陣列和一個 \(val\) 陣列,\(val\) 用來存這個點走到根節點需要”努力學習“的次數。

每次詢問查詢 \(u_i\) 之上第一個比 \(c_i\) 大的點 \(x\)

如果 \(deep[x] < deep[v]\),顯然是 \(0\)

否則求出 \(u\)

\(v\) 之間的最大值 \(tmp\),查詢 \(v_i\) 之上第一個比 \(tmp\) 大的點 \(y\),最後的答案是 \(val[x] - val[y]\)

自我感覺寫麻煩了

程式碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int n, q;
int w[maxn], val[maxn];

struct Edge {
	int to, next;
} e[maxn << 1];

int tot, head[maxn];

inline void Add (register int u, register int v) {
	e[++ tot].to = v;
	e[tot].next = head[u];
	head[u] = tot;
}

int deep[maxn];
int f[maxn][21], maxx[maxn][21];

inline void DFS0 (register int u, register int fa) {
	deep[u] = deep[fa] + 1, maxx[u][0] = w[u];
	for (register int i = 1; (1 << i) <= deep[u]; i ++) {
		f[u][i] = f[f[u][i - 1]][i - 1];
		maxx[u][i] = max (maxx[u][i - 1], maxx[f[u][i - 1]][i - 1]);
	}
	for (register int i = head[u]; i; i = e[i].next) {
		register int v = e[i].to;
		if (v == fa) continue;
		f[v][0] = u, DFS0 (v, u);
	}
}

inline int Find (register int u, register int c) {
	for (register int i = 20; i >= 0; i --) {
		if (maxx[u][i] <= c) u = f[u][i];
	}
	return u;
}

inline int Getmax (register int u, register int v) {
	register int maxval = 0;
	for (register int i = 20; i >= 0; i --) {
		if (deep[f[u][i]] >= deep[v]) maxval = max (maxval, maxx[u][i]), u = f[u][i];
	}
	return max (maxval, w[v]);
}

inline void DFS1 (register int u, register int fa) {
	val[u] = val[Find (u, w[u])] + 1;
	for (register int i = head[u]; i; i = e[i].next) {
		register int v = e[i].to;
		if (v == fa) continue;
		DFS1 (v, u);
	}
}

int main () {
	freopen ("tree.in", "r", stdin);
	freopen ("tree.out", "w", stdout);
	n = read(), q = read(), memset (maxx, 0x3f, sizeof maxx);
	for (register int i = 1; i <= n; i ++) w[i] = read();
	for (register int i = 1; i <= n - 1; i ++) {
		register int u = read(), v = read();
		Add (u, v), Add (v, u);
	}
	DFS0 (1, 0), DFS1 (1, 0);
	while (q --) {
		register int u = read(), v = read(), c = read();
		register int x = Find (u, c);
		if (deep[x] < deep[v]) {
			puts ("0");
		} else {
			register int tmp = Getmax (u, v);
			register int y = Find (v, tmp);
			printf ("%d\n", val[x] - val[y]);
		}
	}
	return 0;
}

環 circle

顯然我們要求的是最小環的數量,而且因為這些環在一個完全圖中,所有的非三元環都會被分解成三元環(畫圖易證),所以這些環只能是三元環。

問題轉化成了求三元環的期望數。

如果沒有限制,顯然答案是從 \(n\) 個點中選 \(3\) 個點:

\[\binom{n}{3}=\frac{n\times (n - 1)\times (n - 2)}{6} \]

用容斥減去不合法的情況,對於一個三元圖有 \(8\) 種情況:

容易發現,對於一個不合法的三元環,總是有一個點的出度為 \(2\)

可以推得:對於一個三元組 \((a,b,c)\),如果 \(b\in p_a\;且\;c\in p_a\;且\;b\neq c\)

\(p_a\) 表示 \(a\) 出邊到達的點的集合),則這個三元組不是三元環。

考慮對每個點 \(i\) 減去上面的貢獻,設 \(a_i\) 表示 \(i\) 確定出邊的數量,\(b_i\) 表示 \(i\) 除去確定連的邊還要連的邊,則每次減去的期望是:

\[\frac{a_i\times(a_i - 1)}{2}+a_i\times \frac{b_i}{2}+\frac{b_i\times(b_i - 1)}{2}\times \frac{1}{4} \]

程式碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

typedef long long ll;

using namespace std;

const int maxn = 1e5 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int n, m, deg[maxn], a[maxn], b[maxn];
ll ans;

inline ll qpow (register ll a, register ll b) {
	register ll ans = 1;
	while (b) {
		if (b & 1) ans = ans * a % mod;
		a = a * a % mod, b >>= 1;
	}
	return ans;
}

int main () {	
	freopen ("circle.in", "r", stdin);
	freopen ("circle.out", "w", stdout);
	n = read(), m = read(), ans = 1ll * n * (n - 1) % mod * (n - 2) % mod * qpow (6, mod - 2) % mod;
	for (register int i = 1; i <= m; i ++) {
		register int u = read(), v = read(); deg[u] ++, deg[v] ++, a[u] ++;
	}
	for (register int i = 1; i <= n; i ++) {
		b[i] = n - 1 - deg[i];
		ans = ((ans - 1ll * a[i] * (a[i] - 1) % mod * qpow (2, mod - 2) % mod - 1ll * a[i] * b[i] % mod * qpow (2, mod - 2) % mod - 1ll * b[i] * (b[i] - 1) % mod * qpow (2, mod - 2) % mod * qpow (4, mod - 2) % mod) % mod + mod) % mod;
	}
	printf ("%lld\n", ans);
	return 0;
}

禮物 gift

一個轉化式:

\[\binom{a_i+a_j+b_i+b_j}{a_i+a_j}=\sum^{a_i+a_j}_{t=0}\binom{a_i+b_i}{t}\times \binom{a_j+b_j}{a_i+a_j-t}=\sum^{a_j}_{t=-a_i}\binom{a_i+b_i}{a_i+t}\times \binom{a_j+b_j}{a_j-t} \]

原式可以理解為從 \((-a_i,-b_i)\) 走到 \((a_j,b_j)\) 的方案數。

然後,按 \(y=-x\) 將兩邊分開,可以轉化為從 \((-a_i,-b_i)\) 橫著走 \(t\) 步到 \(y=-x\) 上,再橫著走 \(a_i+a_j-t\) 步到 \((a_j,b_j)\)

根據式子發現,\(t\) 只用列舉 \(-a_i\)\(b_i\)

考慮實現:

我們可以將每個 \(i\),用某個 \(t\) 求出前半部分的值,再乘上對應的 \(t\)\(1\sim i-1\) 的後半部分的值的總和,最後的答案乘上 \(2\) 即可。

程式碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7, base = 2e7;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int n, maxx, ans;
int a[maxn], b[maxn];
int fac[20000005], facinv[20000005], val[40000005];

inline int qpow (register int a, register int b) {
	register int ans = 1;
	while (b) {
		if (b & 1) ans = 1ll * ans * a % mod;
		a = 1ll * a * a % mod, b >>= 1;
	}
	return ans;
}

inline void Init () {
	fac[0] = 1;
	for (register int i = 1; i <= maxx; i ++) 
		fac[i] = 1ll * fac[i - 1] * i % mod;
	facinv[maxx] = qpow (fac[maxx], mod - 2);
	for (register int i = maxx; i >= 1; i --) 
		facinv[i - 1] = 1ll * facinv[i] * i % mod;
}

inline int C (register int a, register int b) {
	if (a == 0 || b == 0 || a == b) return 1;
	if (a < 0 || b < 0 || a < b) return 0;
	return 1ll * fac[a] * facinv[b] % mod * facinv[a - b] % mod;
}

int main () {
	freopen ("gift.in", "r", stdin);
	freopen ("gift.out", "w", stdout);
	n = read();
	for (register int i = 1; i <= n; i ++) 
		a[i] = read(), b[i] = read(), maxx = max (maxx, a[i] + b[i]);
	Init ();
	for (register int i = 1; i <= n; i ++) {
		for (register int t = -a[i]; t <= b[i]; t ++) 
			ans = (ans + 1ll * C (a[i] + b[i], a[i] + t) * val[t + base] % mod) % mod;
		for (register int t = -b[i]; t <= a[i]; t ++) 
			val[t + base] = (val[t + base] + C (a[i] + b[i], a[i] - t)) % mod;
	}
	printf ("%lld\n", 2ll * ans % mod);
	return 0;
}

最優排名

考慮貪心取,我們取 \(v\) 比當前的 \(v_1\) 大的所有元素中 \(w_i-v_i\) 最小的,每次 \(v_1\) 減小的時候,用單調指標將後面 \(v_i\) 比它大的元素放到堆裡。

會發現,我們每次貪心只會造成區域性最優,並不能保證全域性最優,所以每次操作的時候取個 \(min\) 即可。

程式碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

typedef long long ll;

using namespace std;

const int maxn = 3e5 + 50, INF = 0x3f3f3f3f;

inline ll read () {
	register ll x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int n, r = 1, ans, num;
ll s;

struct Node {
	ll v, w;
	inline bool operator < (const Node &x) const { return v > x.v; }
} a[maxn];

priority_queue <ll, vector <ll>, greater <ll> > q;

int main () {
	freopen ("rank.in", "r", stdin);
	freopen ("rank.out", "w", stdout);
	n = read() - 1, s = read(), read();
	for (register int i = 1; i <= n; i ++) {
		a[i].v = read(), a[i].w = read();
		if (a[i].v <= s) ans ++;
	}
	sort (a + 1, a + n + 1);
	for (register int i = 1; i <= n; i ++, r = i) {
		if (a[i].v <= s) break;
		q.push (a[i].w - a[i].v + 1);
	}
	while (! q.empty ()) {
		register ll u = q.top ();
		if (s < u) break;
		q.pop (), s -= u, num ++;
		while (a[r].v > s && r <= n) q.push (a[r].w - a[r].v + 1), r ++;
		ans = max (ans, num + n - r + 1);
	}
	printf ("%d\n", n + 1 - ans);
	return 0;
}