1028考试总结
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;
}