C 斐波那契数列卷积
题解:
1、
a
n
=
2
a
n
−
1
+
a
n
−
2
−
2
a
n
−
3
−
a
n
−
4
a_n = 2a_{n-1} + a_{n-2} - 2a_{n-3} -a_{n-4}
an=2an−1+an−2−2an−3−an−4
2、使用矩阵乘法算出答案。
[
a
n
a
n
−
1
a
n
−
2
a
n
−
3
]
=
[
2
×
a
n
−
1
+
1
×
a
n
−
2
−
2
×
a
n
−
3
−
1
×
a
n
−
4
1
×
a
n
−
1
+
0
×
a
n
−
2
+
0
×
a
n
−
3
+
0
×
a
n
−
4
0
×
a
n
−
1
+
1
×
a
n
−
2
+
0
×
a
n
−
3
+
0
×
a
n
−
4
0
×
a
n
−
1
+
0
×
a
n
−
2
+
1
×
a
n
−
3
+
0
×
a
n
−
4
]
=
[
2
1
−
2
−
1
1
0
0
0
0
1
0
0
0
0
1
0
]
×
[
a
n
−
1
a
n
−
2
a
n
−
3
a
n
−
4
]
=
[
2
1
−
2
−
1
1
0
0
0
0
1
0
0
0
0
1
0
]
n
−
3
×
[
a
3
a
2
a
1
a
0
]
\begin{gathered} \begin{bmatrix} a_n \\ a_{n-1} \\ a_{n-2} \\ a_{n-3} \\ \end{bmatrix} & = & \begin{bmatrix} 2×a_{n-1} + 1 × a_{n-2} -2 × a_{n-3} - 1 × a_{n-4} \\ 1×a_{n-1} + 0 × a_{n-2} + 0 × a_{n-3} + 0 × a_{n-4} \\ 0×a_{n-1} + 1 × a_{n-2} + 0 × a_{n-3} + 0 × a_{n-4} \\ 0×a_{n-1} + 0 × a_{n-2} + 1 × a_{n-3} + 0 × a_{n-4} \\ \end{bmatrix} \\ & = & \begin{bmatrix} 2 & 1 & -2 & -1 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix} ×\begin{bmatrix} a_{n-1} \\ a_{n-2} \\ a_{n-3} \\ a_{n-4} \end{bmatrix} \\ & = & \begin{bmatrix} 2 & 1 & -2 & -1 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix}^{n-3} ×\begin{bmatrix} a_3 \\ a_2 \\ a_1 \\ a_0 \end{bmatrix} \end{gathered}
⎣⎢⎢⎡anan−1an−2an−3⎦⎥⎥⎤===⎣⎢⎢⎡2×an−1+1×an−2−2×an−3−1×an−41×an−1+0×an−2+0×an−3+0×an−40×an−1+1×an−2+0×an−3+0×an−40×an−1+0×an−2+1×an−3+0×an−4⎦⎥⎥⎤⎣⎢⎢⎡21001010−2001−1000⎦⎥⎥⎤×⎣⎢⎢⎡an−1an−2an−3an−4⎦⎥⎥⎤⎣⎢⎢⎡21001010−2001−1000⎦⎥⎥⎤n−3×⎣⎢⎢⎡a3a2a1a0⎦⎥⎥⎤
3、算出来的结果可能是负数,需要处理。
代码:
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
ll n;
struct Matrix {
ll m[5][5];
} m;
Matrix mul (Matrix a, Matrix b) {
Matrix c;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
c.m[i][j] = 0;
}
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
for (int k = 0; k < 4; k++) {
c.m[i][j] = (c.m[i][j] % mod + ((a.m[i][k] % mod) * (b.m[k][j] % mod)) % mod) % mod;
}
}
}
return c;
}
Matrix pow2 (Matrix a) {
Matrix ans;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
ans.m[i][j] = 0;
}
}
for (int i = 0; i < 4; i++) {
ans.m[i][i] = 1;
}
while (n != 0) {
if ((n&1) != 0) {
ans = mul(ans, a);
}
a = mul(a, a);
n = n >> 1;
}
return ans;
}
int main () {
scanf("%lld", &n);
if (n <= 3) {
if (n == 1) printf("0\n");
else if (n == 2) printf("1\n");
else if (n == 3) printf("2\n");
return 0;
} else {
n = n - 3;
m.m[0][0] = 2;
m.m[0][1] = 1;
m.m[0][2] = -2;
m.m[0][3] = -1;
m.m[1][0] = m.m[2][1] = m.m[3][2] = 1;
Matrix ans = pow2(m);
printf("%lld\n", ((2 * ans.m[0][0] + ans.m[0][1]) % mod + mod) % mod);
}
return 0;
}
D 放物品
题解:
1、f[i, j] 表示在i张牌中,j张牌对应的p位置已经被这i张牌以外的牌占据的方案数。
f ( x ) = { 1 i = 0, j = 0 ( i − 1 ) × f i − 1 , j + 1 j = 0 ( i − j ) × f i − 1 , j + j × f i − 1 , j − 1 j != 0 f(x)= \begin{cases} 1 &\text{i = 0, j = 0}\\ (i-1) × f_{i-1, j+1} & \text{j = 0}\\ (i - j) × f_{i-1, j} + j × f_{i-1, j-1} & \text{j != 0} \end{cases} f(x)=⎩⎪⎨⎪⎧1(i−1)×fi−1,j+1(i−j)×fi−1,j+j×fi−1,j−1i = 0, j = 0j = 0j != 0
2、枚举i和j,分情况讨论i和j对key值产生的贡献。有四种情况:
1)i和j分别放在p[j]和p[i]
2)i放在p[j],j没有放在p[i]
3)j放在p[i],i没有放在p[j]
4)i不放在p[j],j不放在p[i]
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
const int mod = 998244353;
ll t, n, p[N];
ll dr[N], dl[N], dt[N];
ll f[N][3];
void dp () {
f[0][0] = 1;
for (int i = 1; i <= 1000; i++) {
f[i][0] = (f[i-1][1] * (i-1)) % mod;
f[i][1] = ((i - 1) * (f[i-1][1]) + f[i-1][0]) % mod;
f[i][2] = ((i - 2) * (f[i-1][2]) + 2 * (f[i-1][1])) % mod;
}
}
int main () {
dp();
scanf("%lld", &t);
while (t--) {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &p[i]);
}
ll dis = 0;
for (int i = 1; i <= n; i++) {
dl[i] = dr[i] = dt[i] = 0;
for (int j = 1; j < i; j++) {
dl[i] += i - j;
dis += i - j;
}
for (int j = i+1; j <= n; j++) {
dr[i] += j - i;
}
dt[i] = dl[i] + dr[i];
}
ll ans = 0, val1, val2, val3, val4;
// 枚举i和j。j的编号比i大
for (int i = 1; i < n; i++) {
for (int j = i + 1; j <= n; j++) {
if (p[i] < p[j]) {
// j放在p[i], i放在p[j]
val1 = ((p[j] - p[i]) * f[n-2][0]) % mod;
// j放在p[i]上,i不放在p[j]上
val2 = ((dr[p[i]] - (p[j] - p[i])) * f[n - 2][1]) % mod;
// j不放在p[i]上,i放在p[j]上
val3 = ((dl[p[j]] - (p[j] - p[i])) * f[n - 2][1]) % mod;
// i和j都不放在p[i],p[j]上
val4 = ((dis - dt[p[i]] - dt[p[j]] + p[j] - p[i]) * f[n-2][2]) % mod;
ans = (ans + (j - i) * (val1 + val2 + val3 + val4)) % mod;
} else {
// j放在p[i]上,i不放在p[j]上
val2 = (dr[p[i]] * f[n - 2][1]) % mod;
// i放在p[j]上,j不放在p[i]上
val3 = (dl[p[j]] * f[n - 2][1]) % mod;
// i和j都不放在p[i],p[j]上
val4 = ((dis - dt[p[i]] - dt[p[j]] + p[i] - p[j]) * f[n - 2][2]) % mod;
ans = (ans + (j - i) * (val2 + val3 + val4)) % mod;
}
}
}
printf("%lld\n", ans);
}
return 0;
}
E 树上逆序对
题解:
1、树上逆序对只受大的元素的影响,我们只需要统计每个节点的子节点小于该节点的个数val1和父节点小于该节点的个数val2。对于该节点而言,它能创造的逆序对个数要么为val1(变为负数)要么为val2(仍然为正数)。
2、使用树剖来计算每个节点小于它的节点个数。将节点按照关键值从小到大进行排序。然后使用线段树来记录每个区间有几个节点存在。
3、使用背包来判断树上是否可能存在k对逆序对,需要使用bitset加速。
代码:
#include <cstdio>
#include <algorithm>
#include <bitset>
using namespace std;
typedef long long ll;
const int N = 100005;
int n, q;
int head[N], Next[N * 2], edge[N * 2], tot;
int fa[N], dep[N], num[N], son[N];
int id[N], b[N], top[N], cnt;
ll total = 0;
bool vis[N];
struct A {
int a, i;
} a[N];
struct Tree {
int l, r, num;
} tree[N * 4];
bitset<30001> set;
bool cmp (A o1, A o2) {
return o1.a < o2.a;
}
void addEdge (int a, int b) {
tot++;
Next[tot] = head[a];
head[a] = tot;
edge[tot] = b;
}
void input () {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
a[i].i = i;
}
for (int i = 1, a, b; i < n; i++) {
scanf("%d%d", &a, &b);
addEdge(a, b);
addEdge(b, a);
}
}
void dfs1 (int x) {
vis[x] = true;
num[x] = 1;
for (int i = head[x]; i; i = Next[i]) {
int y = edge[i];
if (vis[y]) continue;
dfs1(y);
fa[y] = x;
num[x] += num[y];
if (num[y] > num[son[x]]) {
son[x] = y;
}
}
}
void dfs2 (int x, int root) {
id[x] = ++cnt;
top[x] = root;
b[cnt] = a[x].a;
if (son[x] == 0) return ;
dfs2(son[x], root);
for (int i = head[x]; i; i = Next[i]) {
int y = edge[i];
if (y == son[x] || y == fa[x]) continue;
dfs2(y, y);
}
}
void build (int i, int l, int r) {
tree[i].l = l;
tree[i].r = r;
tree[i].num = 0;
if (tree[i].l != tree[i].r) {
int mid = (l + r) / 2;
build(i * 2, l, mid);
build(i * 2 + 1, mid + 1, r);
}
}
void add (int i, int x) {
if (tree[i].l == tree[i].r) {
tree[i].num = 1;
} else {
if (tree[i*2].r >= x) add(i * 2, x);
else add(i * 2 + 1, x);
tree[i].num = tree[i*2].num + tree[i*2+1].num;
}
}
int search (int i, int l, int r) {
if (tree[i].l == l && tree[i].r == r) return tree[i].num;
if (tree[i*2].r >= r) return search(i*2, l, r);
else if (tree[i*2+1].l <= l) return search(i*2+1, l, r);
else return search(i*2, l, tree[i*2].r) + search(i*2+1, tree[i*2+1].l, r);
}
void cal (int i, int &val1, int &val2) {
val1 = 0;
int x = a[i].i;
while (x != 0) {
val1 += search(1, id[top[x]], id[x]);
x = fa[top[x]];
}
x = a[i].i;
val2 = search(1, id[x], id[x] + num[a[i].i] - 1);
}
int main () {
input();
dfs1(1);
dfs2(1, 1);
sort(a + 1, a + n + 1, cmp);
build (1, 1, n);
set[0] = 1;
for (int i = 1; i <= n; i++) {
int val1, val2;
cal(i, val1, val2);
set = (set << val1) | (set << val2);
add(1, id[a[i].i]);
}
scanf("%d", &q);
for (int i = 0, x; i < q; i++) {
scanf("%d", &x);
if (set[x]) printf("Orz\n");
else printf("QAQ\n");
}
return 0;
}