CSP模拟2

A. F

考场没想出来,寄。

既然 \(x\) 需要满足所有的 \(a\)\(b\) 成立,所以我们把 \(a_1\)\(b\) 数组异或一遍就可以直接得到所有 \(x\) 的预备役。

可能符合条件的 \(x\) 最多也只有 \(n\) 个,那就可以直接把剩下的 \(a\)\(x\) 进行异或,看 \(b\) 中是否存在。

判存在这个用哈希、\(\text{map}\) 都可以,\(n\) 范围到 \(2000\)\(O(n^2 \log n)\) 可过。
Code:

// csp-2 A
#include <algorithm>
#include <iostream>
#include <climits>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <cstdlib>
#include <map>

using namespace std;

const int N = 2050;

int n;

int a[N],b[N],x[N];

int ans[N],cnt;

map<int,bool> t; 

int main() {
    freopen("f.in","r",stdin);
    freopen("f.out","w",stdout);

    scanf("%d",&n);
    for(int i = 1;i <= n; i++)
        scanf("%d",&a[i]);

    for(int i = 1;i <= n; i++) {
        scanf("%d",&b[i]);
        t[b[i]] = 1;
    }

    for(int i = 1;i <= n; i++)
        x[i] = a[1] ^ b[i];  

    bool flag = 0;
    for(int i = 1;i <= n; i++) {
        flag = 0;
        for(int j = 2;j <= n; j++) {
            if(!t[a[j] ^ x[i]]) {
                flag = 1;
                break;
            }
        }
        if(!flag)
            ans[++cnt] = x[i];
    }
    
    sort(ans + 1,ans + cnt + 1);
    printf("%d\n",cnt);

    for(int i = 1;i <= cnt; i++)
        printf("%d\n",ans[i]);

    fclose(stdin);
    fclose(stdout);
    return 0;
}

B.S

咕了 \(\dots\)


C.Y

DP 做法。

\(x_i\) 为第 \(i\) 个人向下一个人传的球数,\(x_i\)\(b_i\) 间存在一个映射关系。

尽管每个人给别人的球的数量不同,但最终的序列 \(b\) 可能相同,例如每个人给别人 \(1\) 个球和 \(2\) 个球之后,每个人的球的数量是一样的,这就有了重复的情况。

所以由序列 \(x\) 可以得到唯一的序列 \(b\),而反之不成立。

可以发现,每个 \(x_i\) 都是正数所以我们把所有 \(x_i\) 都减去 \(1\) 后对应的 \(b_i\) 相同,因此,至少有一个人不传球的方案是与 \(b_i\) 存在双射的,即二者可以互相唯一得到。

思考 \(\prod b_i\) 的组合意义,在每个最终状态中,从每个人手中选出一个球的方案数。

最终状态下每个人的球有两种来源,一是自己原来就有的,二是上一个人传给她的。

\(dp_{i,0}\) 表示第 \(i\) 个人选她自己本来有的球的情况下,前 \(i - 1\) 个人选球的方案数;\(dp_{i,1}\) 表示第 \(i\) 个人选上一个人传给她的球的情况下,前 \(i\) 个人选球的方案数。

为什么 \(dp_{i,0}\) 要表示前 \(i - 1\) 个人的选球方案数呢?因为第 \(i\) 个人选自己本来有的情况下,会与下一个人产生关联。

如何转移?

\[dp_{i + 1,0} = dp_{i,0} \cdot \sum^{a_i}_{k = 1}k + dp_{i,1} \cdot (a_i + 1) \]

\(i+1\) 个人如果选她自己本来有的球,其前 \(i - 1\) 个人方案数分两种情况求和计算。

若第 \(i\) 个人选自己本来有的,她可能选 \(1,2,3,\dots,a_i\) 个,根据乘法原理和加法原理,方案数为 \(dp_{i,0} \cdot \sum^{a_i}_{k = 1}k\)

若第 \(i\) 个人选择上一个人传给她的球,她可以不选球,也可以选 \(1,2,3,\dots,a_i\) 个,所以方案数为 \(dp_{i,1} \cdot (a_i + 1)\)

\[dp_{i + 1,1} = dp_{i,0} \cdot \sum_{k = 1}^{a_i}k(a_i - k) +dp_{i,1} \cdot \sum_{k = 1}^{a_i}k \]

\(i\) 个人如果选择她自己本来有的球,她需要考虑给第 \(i\) 个人多少球,有 \(a_i + 1\) 种选择,给完球之后,第 \(i + 1\) 给人要从中选一个,第 \(i\) 个人从自己剩下的球里选一个。

但这个 \(\sum_{k = 1}^{a_i}k(a_i - k)\) 还可以再转化一下。

\[\sum_{k = 1}^{a_i}k(a_i - k) = \sum^{a_i}_{k = 1}ka_i - \sum^{a_i}_{k = 1}k^2 = \dfrac{n(n + 1)}2 - \dfrac{n(n + 1)(2n + 1)}6 \]

\(i\) 个人如果选择上一个人传给她的球,她需要考虑给第 \(i + 1\) 个人多少球,第 \(i + 1\) 个人还要在这些球中选一个。

最后再进行简单容斥,处理之前要求的符合双射的情况。
Code:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#define int long long

using namespace std;

const int N = 1919810;
const int MOD = MOD;

int n,a[N];

int Pow(int x) {
	int y = MOD - 2;
	int ans = 1;
	
	while(y) {
		if(y & 1)
			ans = 1ll * ans * x % MOD;
		x = 1ll * x * x % MOD;
		y >>= 1;
	}
	return ans;
}

int dp[N][5];

int Inv_2 = Pow(2),Inv_6 = Pow(6);

int Sum(int x) {
	return x * (x + 1) % MOD * Inv_2 % MOD;
}

int Get(int x) {
	return x * (x + 1) % MOD * (2 * x + 1) % MOD * Inv_6 % MOD;
}

int DP(int x,int y) {
	memset(dp,0,sizeof(dp));
	dp[1][0] = x ^ 1;
	dp[1][1] = x;
	for(int i = 2;i <= n; i++) {
		dp[i][0] = (dp[i - 1][0] * Sum(a[i - 1] - y) + dp[i - 1][1] * (a[i - 1] - y + 1)) % MOD;
		dp[i][1] = (dp[i - 1][0] * (a[i - 1] * Sum(a[i - 1]) % MOD - Get(a[i - 1])) % MOD + dp[i - 1][1] * Sum(a[i - 1])) % MOD;
	}
	int ans;
	if(x == 0)
		ans = (dp[n][1] * (a[n] - y + 1) + dp[n][0] * Sum(a[n] - y)) % MOD;
	else
		ans = (dp[n][1] * Sum(a[n]) + dp[n][0] * (a[n] * Sum(a[n]) % MOD - Get(a[n]))) % MOD;
	return ans;
}

signed main() {
#ifndef LOCAL
	freopen("y.in","r",stdin);
	freopen("y.out","w",stdout);
#endif
	cin >> n;
	for(int i = 1;i <= n; i++) 
		cin >> a[i];
	cout << ((DP(1,0) + DP(0,0) - DP(1,1) - DP(0,1)) % MOD + MOD) % MOD;
	return 0;
}

D.O

先咕一咕 \(\dots\)
考场思路粘一下。

/*
实质上每次风吹的时候的更改 
 
就是把该位置以及前面的最大值移动到该位置 

 m 次操作后 

a[i] = max[x - m,x] 初始位置值 

所以要维护一个静态区间最大值 

这样就可以 

O(1) 查询 m 次更改后的 a[i] 的值 

然后问题就是求和 

目前为止其实就是 l == r 的思路 

因为 l == r 相当于单点查询 

直接 n log n 预处理最大值之后 

就可以 O(1) 查询  
 
每个点只能被前面的点更新 

也就是在一定次数更新后 

一些点的值就不会再更改了  

这个时候可以计算无法更改部分的前缀和 

似乎没有太大的优化 

对于还没有完全更新完的点 

它在某一次更新后的值相当于它在那一次扩展区间的最大值 

增加了最大值 - 当前值 

可以 O(1) 计算增加值 

就可以 O(1) 计算单点对前缀和的影响值 

每次更新 r 以前的前缀和 …… 

复杂度仍然高 …… 

想想线段树 

需要支持区间推平和区间求和 

复杂度都是 O(log n)

不能直接推 

直接推每一次复杂度近似 O(n log n)

悲……

怎么做?!!!!

放弃

回去调 T3
*/
posted @ 2023-07-21 18:43  -白简-  阅读(16)  评论(1编辑  收藏  举报