A 选拔赛
题目大意 : 给a数组中的每一个数在b数组中选一个与其他数选的不同的数加上,问有多少种选法可以使加之前是前k大的数,加之后还是前k大
-
将a数组前k大的称为A组,剩下的称为B组。
-
设 (dp_{i,j}) 表示A组都大于等于i,B组都小于等于j的方案数,
-
则 (dp_{i,i}-dp-{i+1,i}) 就是A组最小的是i,B组的都小于等于i的方案数
-
然后枚举最小的和i就可以求出答案。
-
然后考虑如何求一个 (dp_{L,R})
-
a和b都按从大到小排序
-
对于b的第i个数,x[i] 表示A组内有 x[i] 个数加上 b[i] 大于等于 L,因为b[i]是单调变小的,所以x[i]是单调变小的
-
设将 b[u[1]],b[u[2]],...,b[u[k]] 给了A组,由于 x[i] 单调变小,就从后往前分配,u[k] 有 x[u[k]] 种放法,u[k-1] 有 x[u[k-1]] - 1 种放法
-
所以第i个分配的数贡献是 x[u[i]] - (k - i)
-
对于b的第i个数,y[i] 表示B组内有 y[i] 个数加上 b[i] 小于等于 R,因为b[i]是单调变小的,所以y[i]是单调变大的
-
设将 b[v[1]],b[v[2]],...,b[u[n-k]] 给了b组,由于 y[i] 单调变小,就从前往后分配,v[1] 有 y[u[1]] 种放法,v[2] 有 y[u[2]] - 1 种放法
-
所以第i个分配的数贡献是 y[v[i]] - (i - 1)
-
然后就可以dp了。 设 f[i][j] 前i个有j个b给了前k大的a的方案数, 转移的时候枚举是第j个放在A组还是第i-j个放在了B组,然后根据上边的式子求系数就行了
Code
Show Code
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 105, M = 1e9 + 7;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
int n, k, a[N], b[N], s[N*N], cnt, x[N], y[N], f[N][N], ans;
//f[i][j]:前i个有j个b给了前k大的a的方案数
bool Cmp(const int &x, const int &y) {
return x > y;
}
int Cal(int l, int r) {
for (int i = 1; i <= n; ++i)
for (x[i] = x[i-1]; b[i] + a[x[i]] < l; --x[i]);
for (int i = 1; i <= n; ++i)
for (y[i] = y[i-1]; b[i] + a[n-y[i]] <= r && y[i] + 1 <= n - k; ++y[i]);
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= i && j <= k; ++j) {
f[i][j] = 0;
if (j && x[i] > k - j) f[i][j] = (f[i][j] + 1ll * f[i-1][j-1] * (x[i] - k + j)) % M;
if (y[i] > i - j - 1) f[i][j] = (f[i][j] + 1ll * f[i-1][j] * (y[i] - i + j + 1)) % M;
}
}
return f[n][k];
}
int main() {
freopen("select.in", "r", stdin);
freopen("select.out", "w", stdout);
n = read(); x[0] = k = read(); f[0][0] = 1;
for (int i = 1; i <= n; ++i) a[i] = read();
for (int i = 1; i <= n; ++i) b[i] = read();
sort(a + 1, a + n + 1, Cmp);
sort(b + 1, b + n + 1, Cmp);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
s[++cnt] = a[i] + b[j];
sort(s + 1, s + cnt + 1);
for (int i = 1; i <= cnt; ++i)
if (s[i] != s[i-1]) ans = (1ll * ans + Cal(s[i], s[i]) - Cal(s[i] + 1, s[i]) + M) % M;
printf("%d
", ans);
return 0;
}
B 跳跃
题目大意 : 再第i个柱子可以往左或往右跳a[i]个柱子(可以小于a[i]),求最大的两点间最小步数
-
l[i][j][x],r[i][j][x]分别表示从x到x+2j-1这些点开始跳2i步最左最右可以跳到哪里,利用ST表的思想很容易在nlogn2的时间里预处理出来
-
然后二分答案,nlogn判断,总复杂度 (O(nlog^2 n))
Code
Show Code
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2e5 + 5;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
int n, L[18][18][N], R[18][18][N], lg[N], al, ar, l[N], r[N], b[N], s[N];
void Ask(int l, int r, int i) {
int k = lg[r-l+1]; r = r - (1 << k) + 1;
al = min(L[i][k][l], L[i][k][r]);
ar = max(R[i][k][l], R[i][k][r]);
}
bool Judge(int mid) {
for (int x = 1; x <= n; ++x) {
al = ar = x;
for (int i = lg[mid]; i >= 0; --i)
if (mid >> i & 1) Ask(al, ar, i);
l[x] = al; r[x] = ar;
}
for (int i = 1; i <= n; ++i)
s[i] = min(s[i-1], r[i]);
for (int i = n; i >= 1; --i)
b[i] = max(b[i+1], l[i]);
for (int i = 1; i <= n; ++i)
if (s[l[i]-1] < i || b[r[i]+1] > i) return 0;
return 1;
}
int main() {
freopen("jump.in", "r", stdin);
freopen("jump.out", "w", stdout);
n = read();
for (int i = 2; i <= n; ++i)
lg[i] = lg[i>>1] + 1;
for (int x = 1; x <= n; ++x) {
int l = read();
L[0][0][x] = max(x - l, 1);
R[0][0][x] = min(x + l, n);
}
for (int j = 0; j < lg[n]; ++j) {
for (int p = 1 << j, x = n - (1 << j + 1) + 1; x >= 1; --x) {
L[0][j+1][x] = min(L[0][j][x], L[0][j][x+p]);
R[0][j+1][x] = max(R[0][j][x], R[0][j][x+p]);
}
}
for (int i = 1; i <= lg[n]; ++i) {
for (int x = 1; x <= n; ++x) {
Ask(L[i-1][0][x], R[i-1][0][x], i-1);
L[i][0][x] = al, R[i][0][x] = ar;
}
for (int j = 0; j < lg[n]; ++j) {
for (int p = 1 << j, x = n - (1 << j + 1) + 1; x >= 1; --x) {
L[i][j+1][x] = min(L[i][j][x], L[i][j][x+p]);
R[i][j+1][x] = max(R[i][j][x], R[i][j][x+p]);
}
}
}
int l = 0, r = n; s[0] = 1e9;
while (l < r) {
int mid = l + r >> 1;
if (Judge(mid)) r = mid;
else l = mid + 1;
}
printf("%d
", l);
return 0;
}
C 切蛋糕 (Unaccepted)
题目大意 : 求一个半平面交和圆的交的周长
- 大力平面几何
Code
Show Code