【2021夏紀中游記】2021.8.10模擬賽
2021.8.10模擬賽
比賽概括:
\(\mathrm{sum}=100+100+100+20\)
今天題質量下降了。
另外精度誤差真的煩。
T1 單峰:
題目大意:
思路:
由於是排列,所以峰值必為 \(n\),其它數可以依次倒序選擇在 \(n\) 左邊或右邊,所以答案為 \(2^{n-1}\)。
程式碼:
const int N = 0; const ll mod = 1e9 + 7; inline ll Read() { ll x = 0, f = 1; char c = getchar(); while (c != '-' && (c < '0' || c > '9')) c = getchar(); if (c == '-') f = -f, c = getchar(); while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * f; } ll qpow(ll a, ll b) { ll ans = 1ll; for (; b; b >>= 1, a = a * a % mod) if (b & 1) ans = ans * a % mod; return ans; } int main() { printf ("%lld\n", qpow(2, Read() - 1)); return 0; }
T2 祖孫詢問:
題目大意:
已知一棵 \(n\) 個節點的有根樹。有 \(m\) 個詢問。每個詢問給出了一對節點的編號 \(x\) 和 \(y\),詢問 \(x\) 與 \(y\) 的祖孫關係。
思路:
這不是 LCA 板題嗎。
程式碼:
const int N = 4e4 + 10; inline ll Read() { ll x = 0, f = 1; char c = getchar(); while (c != '-' && (c < '0' || c > '9')) c = getchar(); if (c == '-') f = -f, c = getchar(); while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * f; } int m, root, n, logN; struct edge { int to, nxt; }e[N << 1]; int head[N], tot; void add(int u, int v) { e[++tot] = (edge) {v, head[u]}, head[u] = tot; } bool vis[N]; int dep[N], f[N][30]; queue <int> q; void bfs(int root) { while(!q.empty()) q.pop(); q.push(root); vis[root] = 1; dep[root] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (vis[v]) continue; vis[v] = 1; dep[v] = dep[u] + 1; f[v][0] = u; for (int j = 1; j <= logN; j++) f[v][j] = f[f[v][j-1]][j - 1]; q.push(v); } } } int LCA(int u, int v) { if (dep[u] > dep[v]) u ^= v ^= u ^= v; for (int j = logN; ~j; j--) if (dep[f[v][j]] >= dep[u]) v = f[v][j]; if (u == v) return u; for (int j = logN; ~j; j--) if (f[u][j] != f[v][j]) u = f[u][j], v = f[v][j]; u = f[u][0]; return u; } int main() { n = Read(); logN = 20; for (int i = 1; i <= n; i++) { int u = Read(), v = Read(); if (v == -1) root = u; else add (u, v), add (v, u); } bfs(root); for (m = Read(); m--; ) { int u = Read(), v = Read(); int lca = LCA(u, v); if (lca == u) puts("1"); else if (lca == v) puts("2"); else puts("0"); } return 0; }
T3 比賽:
題目大意:
\(A\) 隊和 \(B\) 隊的每個人和其它隊隊員等概率匹配在一起,比較權值,大者為隊內貢獻 \((a_i-b_j)^2\) 分。求兩隊分數期望差。
思路:
這裡等概率是 \(\frac{1}{n}\) 而不是 \(\frac{1}{n!}\),差點損失 \(100\mathrm{pts}\)。
然後列舉 \(B\) 隊在 \(A\) 中匹配,考慮暴力:
for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (a[i] > b[j]) sum += (a[i] - b[j]) * (a[i] - b[j]); else sum -= (a[i] - b[j]) * (a[i] - b[j]);
sum
即為 \(A,B\) 隊權值差。
考慮優化它,可以先對 \(A\) 隊按權值升序排序,然後二分查詢第最後一個小於 \(b\) 的位置 \(\mathrm{pos}\),即 \([1,\mathrm{pos}]\) 為 \(A\) 隊貢獻,\([\mathrm{pos} + 1, n]\) 反之。
展開 \((x-y)^2=x^2+2xy+y^2\),代入題目:
\[\begin{aligned} \mathrm{sum}&\leftarrow\mathrm{sum}-\sum_{i=1}^\mathrm{pos}(a_i^2+2a_ib+b^2)+\sum_{\mathrm{pos}+1}^n(a_i^2+2a_ib+b^2)\\ &\leftarrow\mathrm{sum}-\left(b^2\mathrm{pos}+\sum_{i=1}^\mathrm{pos}a_i^2+2b\sum_{i=1}^\mathrm{pos}a_i)\right)+(n-\mathrm{pos})b^2+\sum_{\mathrm{pos}+1}^na_i^2+2b\sum_{\mathrm{pos}+1}^na_i \end{aligned} \]再維護 \(a_i\) 的字首和與 \({a_i}^2\) 的字首和即可。
程式碼:
小心精度誤差,多用 long double
。
const int N = 50010;
inline ll Read()
{
ll x = 0, f = 1;
char c = getchar();
while (c != '-' && (c < '0' || c > '9')) c = getchar();
if (c == '-') f = -f, c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x * f;
}
ll n;
ll sum1[N], sum2[N];
double sum;
ll a[N];
int main()
{
n = Read();
for (int i = 1; i <= n; i++) a[i] = Read();
sort (a + 1, a + 1 + n);
for (int i = 1; i <= n; i++)
sum1[i] = sum1[i - 1] + a[i],
sum2[i] = sum2[i - 1] + a[i] * a[i];
for (int i = 1; i <= n; i++)
{
ll x = Read();
if (x <= a[1]) {sum += n * x * x + sum2[n] - 2ll * x * sum1[n]; continue;}
if (x >= a[n]) {sum -= n * x * x + sum2[n] - 2ll * x * sum1[n]; continue;}
ll pos = lower_bound(a + 1, a + 1 + n, x) - a - 1;
sum += -(pos * x * x + sum2[pos] - 2ll * x * sum1[pos]) +
(n - pos) * x * x + (sum2[n] - sum2[pos]) - 2ll * x * (sum1[n] - sum1[pos]);
}
// for (int i = 1; i <= n; i++) brute♂force
// for (int j = 1; j <= n; j++)
// if (a[i] > b[j]) sum += (a[i] - b[j]) * (a[i] - b[j]);
// else sum -= (a[i] - b[j]) * (a[i] - b[j]);
printf ("%lf", sum * 1.0 / n);
return 0;
}
T4 數字:
題目大意:
找到一些數滿足:
- 它有 \(2n\) 個數位,\(n\)是正整數(允許有前導 \(0\))。
- 構成它的每個數字都在給定的數字集合 \(S\) 中。
- 它前n位之和與後n位之和相等或者它奇數位之和與偶數位之和相等。
已知n,求合法的數的個數,答案很大,對 \(999983\) 取模忍一下。
思路:
考慮簡單容斥思想,即求出前 \(n\) 位之和與後 \(n\) 位之和相等的方案數 + 奇數位之和與偶數位之和相等的方案數 - 前 \(n\) 位之和與後 \(n\) 位之和相等且奇數位之和與偶數位之和相等的方案數。
設 \(f_{i,j}\) 表示前 \(i\) 位和為 \(j\) 的方案數。
可以把奇偶和相等的看作是前 \(n\) 位後 \(n\) 位和相等(即奇數位看作前 \(n\) 位,偶數位看作後 \(n\) 位)。並且可以不用計算到 \(2n\) 這麼冗長,平方即可。那麼前 \(n\) 位之和與後 \(n\) 位之和相等的方案數 + 奇數位之和與偶數位之和相等的方案數就是:
\[\sum_{j}2f_{n,j}^2 \]前 \(n\) 位之和與後 \(n\) 位之和相等、奇數位之和與偶數位之和相等的方案數是有交集的,所以計算很麻煩,考慮轉化題意:
因為前 \(n\) 位之和等於後\(n\)位之和、奇數位之和等於偶數位之和,所以前 \(n\) 位中奇數位之和等於後n位中偶數位之和且前 \(n\) 位中偶數位之和等於後 \(n\) 位中奇數位之和。
這樣就無交集了。
程式碼:
const int N = 1010, M = 9010, mod = 999983;
inline ll Read()
{
ll x = 0, f = 1;
char c = getchar();
while (c != '-' && (c < '0' || c > '9')) c = getchar();
if (c == '-') f = -f, c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x * f;
}
int n, m, K;
char s[15];
int a[N];
int f[N][M];
bool num[15];
int main()
{
n = Read(); f[0][0] = 1;
scanf ("%s", s + 1);
m = strlen (s + 1);
for (int i = 1; i <= m; i++)
K = max(K, s[i] - '0'), f[1][s[i] - '0'] = 1, num[s[i] - '0'] = 1;
for (int i = 2; i <= n; i++)
for (int j = 0; j <= n * K; j++)
for (int k = 0; k < 10; k++)
if (num[k])
(f[i][j] += f[i - 1][j - k]) %= mod;
int ans = 0;
for (int i = 0; i <= n * K; i++)
(ans += 2ll * f[n][i] * f[n][i] % mod) %= mod;
int Even = n >> 1, Odd = n - Even, A = 0, B = 0;
for (int i = 0; i <= Odd * K; i++)
(A += 1ll * f[Odd][i] * f[Odd][i] % mod) %= mod;
for (int i = 0; i <= Even * K; i++)
(B += 1ll * f[Even][i] * f[Even][i] % mod) %= mod;
printf ("%d", (ans - 1ll * A * B % mod + mod) % mod);
return 0;
}