牛客挑战赛32

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=2an1+an22an3an4
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} anan1an2an3===2×an1+1×an22×an31×an41×an1+0×an2+0×an3+0×an40×an1+1×an2+0×an3+0×an40×an1+0×an2+1×an3+0×an42100101020011000×an1an2an3an42100101020011000n3×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(i1)×fi1,j+1(ij)×fi1,j+j×fi1,j1i = 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值