为什么还要考试啊,我的天啊!!!!
T1
二分 + 贪心。
可以发现,一行的数是连续的肯定会让这一行最大值减去最小值最小。
对数组进行排序,进行二分答案,用连续的数铺每一行,看能否铺满。
来个好看的代码。
int k, n, m, ans, a[maxn];
bool check(int mid)
{
int js = 0;
for(int i = 1; i + m - 1 <= k; i++)
if(a[i + m - 1] - a[i] <= mid) js++, i += m - 1;
if(js >= n) return 1;
return 0;
}
int main()
{
k = read(), n = read(), m = read();
if(m == 1) {puts("0"); return 0;}
for(int i = 1; i <= k; i++) a[i] = read();
sort(a + 1, a + k + 1);
int l = 0, r = a[k];
while(l <= r)
{
int mid = (l + r) >> 1;
if(check(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
printf("%d", ans);
return 0;
}
T2
60 pts
\(n ^3\) 的DP。
用 \(f[i][j]\) 表示前 \(i\) 个数分成 \(j\) 段所得到的最大价值。
\(f[i][j] = max(f[k][j - 1] + sum[k + 1][i])\)。
n = read(), K = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= n; i++)
for(int j = i; j <= n; j++)
sum[i][j] = (sum[i][j - 1] | a[j]);
for(int i = 1; i <= n; i++) f[i][1] = sum[1][i];
for(int i = 1; i <= n; i++)
{
for(int j = 1; j < i; j++)
{
for(int k = 1; k <= Min(i, K); k++)
{
f[i][k] = Max(f[i][k], f[j][k - 1] + sum[j + 1][i]);
}
}
}
printf("%d", f[n][K]);
100 pts
可以发现,\(sum\) 数组有一段一段的数是相同的。
\(f\) 数组肯定是单调递增的,对于每个固定的 \(i\) 点,\(sum[k+1][j]\) 的取值最多只有 \(32\) 种。
预处理出转移点。
int n, K, a[maxn], sum[maxn][maxn], f[maxn][1000]; // 把前 i个数分成 j块
vector<int> e[maxn];
int main()
{
//freopen("b.in", "r", stdin);
//freopen("b.out", "w", stdout);
n = read(), K = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= n; i++)
for(int j = i; j <= n; j++)
sum[i][j] = (sum[i][j - 1] | a[j]);
for(int i = 1; i <= n; i++)
{
e[i].push_back(i);
for(int j = i - 1; j >= 1; j--)
if(sum[j][i] != sum[j + 1][i]) e[i].push_back(j);
}
for(int i = 1; i <= n; i++)
{
for(int k = 1; k <= Min(i, K); k++)
{
for(int j = 0; j < e[i].size(); j++)
{
int v = e[i][j];
f[i][k] = Max(f[i][k], f[v - 1][k - 1] + sum[v][i]);
}
}
}
printf("%d", f[n][K]);
fclose(stdin);
fclose(stdout);
return 0;
}
T3
30pts
暴力枚举全排列。
60pts
对于这棵二叉树设最下边一层为第 \(0\) 层,设编号 \(i\) 与编号 \(j\) 在第 \(k\) 层相遇,设 \(i < j\)。
对于第 \(k\) 层,每个点对应的叶子节点为 \(2 ^ k\)。
对于\(i\) 的子树,现在已知最大的一个编号为 \(i\), 还剩下 \(2^k - 1\) 个节点,那么这棵子树中所有的情况就是 \(C(2^k - 1, i - 1)\) 。
对于 \(j\) 的子树,以为已知 \(i < j\) ,所以 \(i\) 子树内所有的编号一定小于 \(j\) 的子树内的,所以 \(j\) 的子树内编号为 \(j - 1 - 2 ^ k\),所有的情况就是 \(C(2 ^k - 1, j- 1 - 2^k )\)。
上边讨论的是编号的情况,又因为编号是随机排列的,所以子树内的编号的排列顺序也是不固定的,还要乘以两个\(2 ^k\) !。
对于一整颗二叉树,除了刚刚讨论的 \(i, j\) 子树中的点, 还有 \(n - 2 ^{k + 1}\) 个点, 这些点的排列也是随机的,所以再再乘以一个 \(n - 2 ^{k + 1}\) !。
刚刚讨论的是一个点对,要算出这一层中有多少个点对,就是 \(2 ^{K- k - 1}\)个。
因为这是 \(i < j\) 的情况, 还有 \(i > j\) 的情况,所以还要乘以 \(2\)。
最后再乘上 \(i, j\) 的权值。
枚举所有的 \(i, j\) ,它们的总贡献就是
100 pts
考虑前面这个东西做一个前缀和。
时间复杂度为 \(O(k 2 ^k)\)。
隔壁 zxs AC代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 3e5 + 5;
const int MOD = 1e9 + 7;
int read() {
int x = 0, f = 1;char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int K, Val[MAXN], N, Fac[MAXN], ret, inv[MAXN], sum[MAXN];
int Qpow(int x, int y) {
int ret = 1;
while(y) {
if(y & 1) ret = (ret * x) % MOD;
y >>= 1;
x = (x * x) % MOD;
}
return ret;
}
int C(int n, int m) {
if (m > n) return 0;
return Fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
signed main() {
//freopen("c4.in", "r", stdin);
//freopen("c.out", "w", stdout);
K = read();
N = (1 << K), Fac[0] = 1, inv[0] = 1;
for (int i = 1; i <= N; i++) Fac[i] = Fac[i - 1] * i % MOD;
for (int i = 1; i <= N; i++) inv[i] = Qpow(Fac[i], MOD - 2);
for (int i = 1; i <= N; i++) Val[i] = read();
for (int k = 0, tmp; k < K; k++) {
tmp = (1 << (K - k)) * Fac[N - (1 << k + 1)] % MOD;
tmp = tmp * Fac[(1 << k)] % MOD * Fac[(1 << k)] % MOD;
int all = 0;
for (int j = N; j >= 1; j--) {
sum[j] = (sum[j + 1] + C(j - 1 - (1 << k), (1 << k) - 1) * Val[j] % MOD) % MOD;
}
for (int i = 1; i <= N; i++)
all = (all + C(i - 1, (1 << k) - 1) % MOD * Val[i] % MOD * sum[i + 1] % MOD) % MOD;
tmp = tmp * all % MOD;
ret = (ret + tmp) % MOD;
}
int tot = Fac[N];
cout<<ret * Qpow(tot, MOD - 2) % MOD;
return 0;
}