1014下午考试
1014下午考试
T1
题目大意:
有一个\(n*m\)的矩阵,矩阵的每个位置上可以放置一个数。对于第i行,第i行的差异定义为该行的最大数和最小数的差。一个矩阵的差异,定义为矩阵中每一行差异的最大值。现在给定k个数v[1..k],问:从这k个数中选\(n*m\)个数放入矩阵,能够得到的矩阵的差异最小值是多少。
n * m <= k <= 100000, n, m <= 1000,0<= v[i] <= 10^9
二分。
就是二分的板子题,先对每个数排序,然后二分矩阵差异最小值,贪心的判断是否可以选出n个长度为m的连续子段。
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5, inf = 1e9;
int k, n, m, ans;
int a[N], vis[N];
int check(int s) {
int res = 0;
for(int i = 1;i <= k; i++) {
int tag = 0, tmp = 1;
for(int j = i + 1;j <= k; j++) {
if(a[j] - a[i] > s) { tag = 1; break; }
else { tmp ++; }
if(tmp == m) { i = j; break; }
}
if(tag == 0 && tmp == m) res ++;
}
return res >= n;
}
int main() {
k = read(); n = read(); m = read();
for(int i = 1;i <= k; i++) a[i] = read();
sort(a + 1, a + k + 1);
int l = 0, r = inf;
while(l <= r) {
if(check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d", ans);
fclose(stdin); fclose(stdout);
return 0;
}
T2
题目大意:
给定一个长度为n的序列v[1..n],现在要将这个序列分成k段(每段都不能为空),定义每一段的权值为该段上的所有数的或和。定义整个序列的权值为每段权值的和。问:这个序列的最大权值为多少。
k <= n <= 2000,1<= v[i] <= 5 *10^5
线性DP。
考场上想出来60分的做法:
\(f[i][j]\)表示前\(i\)个数分成\(j\)段的最大权值,那么转移方程就是:\(f[i][j] = max(f[i][j], f[l][j - 1] + or[l + 1][i])\)。
复杂度\(O(n ^ 3)\)。
考虑优化一下,或和从0变到10^6大概需要20次,如果说没变,我们只需要取没变的这一段\(f\)值最大的一个就好了。因为\(or\)随着长度变小单调不升,\(f\)随着长度变大单调不降,所以这一段最后一个点就是断点。所以我们可以与处理出断点在哪,第三层循环直接枚举这些断点就好了。
复杂度\(O(n^2logn)\)。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 2005;
int n, k;
long long a[N], aor[N][N], f[N][N];
vector <long long> v[N];
int main() {
n = read(); k = read();
for(int i = 1;i <= n; i++) a[i] = read();
for(int l = 1;l <= n; l++)
for(int r = l;r <= n; r++)
aor[l][r] = aor[l][r - 1] | a[r];
for(int r = 1;r <= n; r++)
for(int l = 1;l <= r; l++)
if(aor[l][r] != aor[l + 1][r]) v[r].push_back(l);
for(int i = 1;i <= n; i++) f[i][1] = aor[i][1];
for(int i = 2;i <= n; i++)
for(int j = 1;j <= min(i, k); j++)
for(int l = 0;l < (int)v[i].size(); l++)
f[i][j] = max(f[i][j], f[v[i][l] - 1][j - 1] + aor[v[i][l]][i]);
printf("%lld", f[n][k]);
fclose(stdin); fclose(stdout);
return 0;
}
T3
题目大意:
有一棵k+1层的满二叉树,那么该树有2^k 个叶子节点。给定n个机器人(n=2^k),编号从1—n,编号为i的机器人的权值为v[i]。我们现在要将这n个机器人分别放置在这n个叶子节点上,每个叶子节点放且只能放一个机器人。叶子节点的权值为该叶子节点上的机器人的权值。非叶子节点的权值定义为该树中编号最大的机器人的权值。每个非叶子节点除了权值以外,还有一个魔法值,该点的魔法值为其左右儿子节点的权值的乘积。整棵树的魔法值定义为非叶子节点的魔法值的和。问:将这n个机器人随机地放在这n个叶子节点上,树的魔法值的期望为多少。
假设答案为一个不可约分数P/Q,则输出在模1e9+7意义下的P* (Q^-1)模1e9+7的值。k <= 18。
组合数 + 推式子
我们假设当先要合并的点高度为d,左子树权值为x,右子树权值为y,x > y;
那么贡献是这个:\(C(y - 1, 2^d - 1) * C(x - 2 ^ d - 1, 2^d - 1) * v[x] * v[y] * (n - 2^{d + 1})! * 2 * 2^{k - d} * 2^d! * 2^d!\)。
解释一下:
高度为d的二叉树节点的子树中有\(2 ^ d\)个节点,要从比\(y\)小的数里面选出\(2^d - 1\)个数,顺序不同方案也不同,所以是:\(C(y - 1, 2 ^ d - 1) * 2 ^ d!\)。
除去\(y\)的子树中的节点后,要从比\(x\)小的数里面选出\(2^d - 1\)个数,顺序不同方案也不同,所以是:\(C(x - 2^d - 1, 2 ^ d - 1) * 2 ^ d!\)。
除去\(x, y\)的所有节点后,剩下的数随便排,所以是:\((n - 2 ^ d - 2^d)! = (n - 2^{d + 1})!\)。
\(x, y\)的顺序可以交换,所以再乘2。
当前高度为d,那么层数就是\(k - d\)层,二叉树这一层会有\(2 ^ {(k - d)}\)个节点,都可以在这上面合并,所以再乘\(2 ^ {k - d}\)。
最后再乘上\(x, y\)的权值\(v[x], v[y]\)。
复杂度\(O(kn ^ 2)\)。
可以后缀和优化一下,首先原式子可以写成:
\(\displaystyle \sum_{d} \sum_{x} \sum_{y} * A(y - 1, 2^d - 1) * A(x - 2^d - 2, 2^d - 1) * (n - 2^{d + 1})! * 2^{k - d + 1}\)。
还可以写成:
\(\displaystyle \sum_{d} (n - 2^{d + 1})! * 2^{k - d + 1} \sum_{y} A(y - 1, 2^d - 1) \sum_{x} A(x - 2^d - 2, 2^d - 1)\)
预处理出后面那两坨sigema的后缀和就好了,复杂度\(O(kn)\)。
#include <bits/stdc++.h>
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 3e5 + 5, mod = 1e9 + 7;
int k, n;
int ans, a[N];
long long fac[N], inv[N], bit[N], sum[N];
void make_pre() {
fac[0] = fac[1] = inv[0] = inv[1] = bit[0] = 1;
for(int i = 2;i < N; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
for(int i = 2;i < N; i++) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
for(int i = 2;i < N; i++) inv[i] = 1ll * inv[i - 1] * inv[i] % mod;
for(int i = 1;i < N; i++) bit[i] = bit[i - 1] * 2;
}
int C(int x, int y) {
if(x < y) return 0;
return 1ll * fac[x] * inv[y] % mod * inv[x - y] % mod;
}
int main() {
make_pre();
k = read(); n = bit[k];
for(int i = 1;i <= n; i++) a[i] = read();
for(int d = 1;d <= k; d++) {
for(int x = n;x >= bit[d]; x--)
sum[x] = (sum[x + 1] + 1ll * C(x - bit[d - 1] - 1, bit[d - 1] - 1) * a[x] % mod) % mod;
long long tmp = 0;
for(int y = n;y >= bit[d - 1]; y--)
tmp = (tmp + 1ll * C(y - 1, bit[d - 1] - 1) * a[y] % mod * sum[max(bit[d], (long long) y + 1)] % mod) % mod;
ans = (ans + 1ll * tmp * fac[bit[d - 1]] % mod * fac[bit[d - 1]] % mod * fac[n - bit[d]] % mod * 2 % mod * bit[k - d] % mod) % mod;
}
printf("%d", 1ll * ans * inv[n] % mod);
fclose(stdin); fclose(stdout);
return 0;
}