1028考试总结
T1
题目大意:
给定一个排列,为这个排列字典序的下一个排列是啥?(n <= 1e5).(不能用next_permutation.)
从后往前找第一个(x_i < x_{i + 1})的数,这个数(i)之后的数是单调递减的.也就是说这个数后面的已经是极大的了,不能通过改变顺序是后面这几个数更大,所以我们只能增大(i)位置上的这个数,在后面的数中找一个比它大的最小的数,让他俩交换位置,剩下没选过的数就从小到大排一遍序就好了.
#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 = 1e5 + 5;
int n, p[N], vis[N];
int main() {
n = read();
for(int i = 1;i <= n; i++) p[i] = read();
int pos;
for(int i = n;i >= 1; i--) {
vis[p[i]] = 1;
if(p[i - 1] < p[i]) { pos = i - 1; break; }
}
if(pos == 0) for(int i = 1;i <= n; i++) printf("%d ", i);
else {
vis[p[pos]] = 1; int tag = 0;
for(int i = 1;i <= n; i++) if(vis[i] && i > p[pos]) {
vis[i] = 0; p[pos] = i; tag = 1; break;
}
if(!tag) {
vis[p[pos]] = 1; vis[++ p[pos]] = 0;
}
for(int i = 1;i <= pos; i++) printf("%d ", p[i]);
for(int i = 1;i <= n; i++) if(vis[i]) printf("%d ", i);
}
fclose(stdin); fclose(stdout);
return 0;
}
T2
题目大意:给定一棵树,再给出所有的叶子结点经过的顺序,然后从根节点1开始走,每条边最多走两次,最后回到根节点,问是否可以走出一条路径满足给定的经过叶子结点的顺序.不能的话输出-1,否则输出这条路径.(n <= 1e5).
(LCA) 树上问题.
我们发现直接按题意模拟是可以通过时间复杂度的,因为每条边最多走两次,所以最多走(2n - 2)条边.所以我们按给定的叶子结点的顺序求出相邻两个叶子结点的(LCA).然后暴力条父亲就好了,记得记录一下边的经过次数.
#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 = 1e5 + 5;
int n, cnt, num;
int id[N], dep[N], fa[N][21], head[N], tmp_ans[N * 10], ans[N * 10], in_edge[N];
struct edge { int to, nxt; } e[N << 1];
void add(int x, int y) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; in_edge[y] ++;
}
void get_tree(int x, int Fa) {
dep[x] = dep[Fa] + 1; fa[x][0] = Fa;
int tag = 0;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; if(y == Fa) continue;
get_tree(y, x); tag = 1;
}
if(!tag) num ++;
}
void make_fa() {
for(int i = 1;i <= 20; i++)
for(int j = 1;j <= n; j++)
fa[j][i] = fa[fa[j][i - 1]][i - 1];
}
int LCA(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
for(int i = 20;i >= 0; i--) if(dep[x] - dep[y] >= (1 << i)) x = fa[x][i];
if(x == y) return x;
for(int i = 20;i >= 0; i--) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main() {
n = read(); int tot = 0;
for(int i = 1, x, y;i <= n - 1; i++) {
x = read(); y = read(); add(x, y); add(y, x);
}
get_tree(1, 0); make_fa();
int last = 1; tot = 0;
for(int i = 1, x, lca;i <= num + 1; i++) {
if(i == num + 1) x = 1; else x = read();
lca = LCA(last, x);
int tmp = last; cnt = 0;
while(tmp != lca) { tmp_ans[++ cnt] = tmp; id[tmp] ++; tmp = fa[tmp][0]; if(id[tmp] > 2) { printf("-1"); return 0; } }
for(int i = 2;i <= cnt; i++) ans[++ tot] = tmp_ans[i];
ans[++ tot] = lca;
tmp = x; cnt = 0;
while(tmp != lca) { tmp_ans[++ cnt] = tmp; id[tmp] ++; tmp = fa[tmp][0]; if(id[tmp] > 2) { printf("-1"); return 0; } }
for(int i = cnt;i >= 1; i--) ans[++ tot] = tmp_ans[i];
last = x;
}
for(int i = 1;i <= tot; i++) printf("%d ", ans[i]);
fclose(stdin); fclose(stdout);
return 0;
}
T3
题目链接
题目大意:给定(n)数(a)和(n)个数(b),求(a > b)的对数 - (b > a)的对数恰好等于(k)的方案数.(n <= 2000),答案对(1e9 + 9)取模.保证每个(a, b)都不相等.
首先我们可以得到(a > b)的对数是(frac {n + k}{2}).然后我们对(a, b)从小到大排序.
(f[i][j])表示前(i)个(a),钦定有(j)对(a > b)的方案数,那么DP转移方程就是:(f[i][j] = f[i - 1][j] + f[i - 1][j - 1] * (g[i] - (j - 1))).
为什么是钦定不说恰好呢?假设当前所有(a_i)都大于(b),那么是无法恰好选出(j)对的,因为所有的(a_i)都可以与其他的(b)形成(a > b),钦定的意思是强制选取(j)对,其他的不管,这样之后求(h[j])的时候会乘上((n - j)!),求出来的(h[j])才是正确的.
(g[i])表示有多少个(b)小于(a_i).这个转移方程的含义就是"第(i)个数(a_i)没有形成(a > b) + 第(i)个数(a_i)形成了(a > b)并且这个(b)前(j - 1)对(a > b)中没有出现过 ".
(h[i])表示至少有(i)对(a > b)的方案数(这里也可以说是钦定(i)对(a > b),其他的不管的方案数),那么可以得到:(h[i] = f[n][i] * (n - i)!).剩下(n - i)个数随便排列,可能形成(1)到(n - i)对新的(a > b).(这样算出的(h[i])是有重复方案的对吧)
(ans[i])表示恰好有(i)对(a > b)的方案数,我们可以发现它和(h[i])的关系:(h[i] = displaystyle sum_{j = i}^{n} C_{j}^{i} ans[j]).重新看(h[i])的定义:至少有(i)对(a > b)的方案数.那么我们就要把所有(j >= i)的(ans[j])统计到(h[i])里面,并且要算上重复的部分,也就是在乘上(C_{j}^{i})(因为钦定了(i)对嘛)..
然后我们把(ans[i])移到一边:(ans[i] = h[i] - displaystyle sum_{j = i + 1}^{n} C_{j}^{i} ans[j]).然后倒推就可以了.时间复杂度(O(n ^ 2)).
#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, mod = 1e9 + 9;
int n, k, num;
int a[N], b[N], g[N], h[N], fac[N], ans[N], inv[N], f[N][N];
void make_pre() {
fac[0] = inv[0] = inv[1] = 1;
for(int i = 1;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;
}
int C(int n, int m) {
if(n < m) return 0;
return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
n = read(); k = read(); make_pre();
if(((n & 1) + (k & 1)) & 1) { printf("0"); return 0; }
num = (n + k) / 2;
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); sort(b + 1, b + n + 1);
for(int i = 1;i <= n; i++)
for(int j = 1;j <= n; j++)
if(a[i] > b[j]) g[i] ++;
else break;
for(int i = 0;i <= n; i++) f[i][0] = 1;
for(int i = 1;i <= n; i++) {
for(int j = 1;j <= n; j++)
f[i][j] = (f[i - 1][j] + 1ll * f[i - 1][j - 1] * (max(0, g[i] - j + 1)) % mod) % mod;
}
for(int i = 0;i <= n; i++)
h[i] = 1ll * f[n][i] * fac[n - i] % mod;
for(int i = n;i >= num; i--) {
int tmp = 0;
for(int j = i + 1;j <= n; j++) tmp = (tmp + 1ll * C(j, i) * ans[j] % mod) % mod;
ans[i] = (h[i] - tmp + mod) % mod;
}
printf("%d", ans[num]);
return 0;
}