1. 程式人生 > 其它 >【2021夏紀中游記】2021.8.10模擬賽

【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 數字:

題目大意:

找到一些數滿足:

  1. 它有 \(2n\) 個數位,\(n\)是正整數(允許有前導 \(0\))。
  2. 構成它的每個數字都在給定的數字集合 \(S\) 中。
  3. 它前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;
}