Loading

The 2021 Shanghai Collegiate Programming Contest 部分题解

The 2021 Shanghai Collegiate Programming Contest 部分题解

这场比赛是我自己VP打的,题不难但是有很多不必要的WA,需要以此为戒

A.

题意

给定两个三维向量\((x_1,y_1,z_1)\) ,\((x_2,y_2,z_2)\) 求和这两个向量都垂直的一个向量

分析

我们有熟知的结论,三维空间中,两个向量的叉积就是这两个向量确定平面的法向量

本题由于有范围限制,因此也可以枚举每一个向量,判断是否同时和两向量的点乘为0

代码

int main(){
	int x = rd();
	int y = rd();
	int z = rd();
	int xx = rd();
	int yy = rd();
	int zz = rd();
	for(int i = -200;i <= 200;i++)
		for(int j = -200;j <= 200;j++)
			for(int k = -200;k <= 200;k++)
				if(i * x + j * y + k * z == 0 && i * xx + j * yy + k * zz == 0) {
					printf("%d %d %d\n",i,j,k);
					return 0;
				}
}

B.

题意

给出\(n\)个三元组,取其中\(a\)个1号,\(b\)个2号,\(c\)个3号,且保证\(a +b + c = n\) ,问能够取到的最大值

\[1 \leq n \leq 5000 \]

分析

题目显然要求我们\(O(n^2polylog)\)的做法,暴力\(DP\)的复杂度是\(O(n^3)\)

考虑二元组的情况,这个时候显然贪心从二元组差值大的开始取是最优的

于是可以\(dp[i][j]\)表示前\(i\)个物品,选择了\(j\)个三号物品,这样每次不选三号物品的时候只需要按照之前的贪心策略选

代码

实现的时候需要注意,应当给\(DP\)数组初始化,否则从\(dp[i-1][j]\)的时候会出问题

const int maxn = 5e3 + 5;

struct S{
    int a,b,c;
    S(){}
    S(int _a,int _b,int _c){
	a = _a;
	b = _b;
	c = _c;
    }
    friend bool operator < (const S &x,const S &y){
	if(x.b - x.a == y.b - y.a) return x.c > y.c;
	return x.b - x.a > y.b - y.a;
    }
};

ll dp[maxn][maxn];

int main(){
    int n = rd();
    int a = rd();
    int b = rd();
    int c = rd();
    vector<S> v(n + 1);
    for(int i = 1;i <= n;i++){
	v[i].a = rd();
	v[i].b = rd();
	v[i].c = rd();
    }
    sort(v.begin() + 1,v.end());
    for(int i = 0;i <= n;i++)
	for(int j = i + 1;j <= c;j++)
	    dp[i][j] = -1e18;
    for(int i = 1;i <= n;i++){
	for(int j = 0;j <= min(c,i);j++){
	    if(j) dp[i][j] = max(dp[i][j],dp[i - 1][j - 1] + v[i].c);
	    if(i - j <= b) dp[i][j] = max(dp[i][j],dp[i - 1][j] + v[i].b);
	    else dp[i][j] = max(dp[i][j],dp[i - 1][j] + v[i].a);
	}
    }
    printf("%lld",dp[n][c]);
}

C.

代码

int main(){
	int n = rd();
	int m = rd();
	int tot = 0;
	vector<pii> v(n);
	VI ans(n + 1);
	for(int i = 0;i < n;i++){
		v[i].se = rd();
		v[i].fi = rd();
		tot += v[i].fi;
	}
	for(int i = 0;i < n;i++){
		if(v[i].se != m) {
			if(v[i].fi * n >= tot) v[i].fi -= 2,v[i].fi = max(v[i].fi,0ll);
		}
		else {
			if(v[i].fi < 60) v[i].fi = 60;
		}
		ans[v[i].se] = v[i].fi;
	}
	for(int i = 1;i <= n;i++)
		printf("%d ",ans[i]);
}

D.

给定一个可重集,要求构造\(2 \times n\) 的序列,这个序列需要满足每一行从左到右不递减,第一行比第二行不递减,数字带标号 求可能的方案数

\[2 \leq n \leq 5000,1\leq a_i \leq n \]

分析

此题有点像 ACWING271. 杨老师的照相排列,做法应该可以很快确定DP即可。难点在于难以找到有效的DP转移方法。

考虑类似插入型DP那样,\(DP\)的时候按照大小顺序插入就变得很方便。因为两行之间有偏序关系,假设从大到小插入数,如果能够钦定第一行比第二行的人多,那么转移的时候枚举当前人插在第一行还是第二行,就能保证要求的条件满足。

因为相同大小的数可以随意放,因此每次枚举每一种数就行,\(dp[i][j]\)表示前\(i\)个人,第一行比第二行多\(j\)个人时的方案。用\(t\)表示当前数的第一行比第二行多的个数,\(c[i]\)表示\(i\)的个数,不难得到转移方程

\[dp[j + t] = dp[j] *k!(c[i] - k)! \binom{c[i]}{k} \]

代码

int main(){
	int n = rd();
	factPrework(n);
	for(int i =  1;i <= n;i++){
		a[i] = rd();
		c[a[i]]++;
	}
	dp1[0] = 1;
	for(int i = 1;i <= n;i++){
		if(!c[i]) continue;
		for(int j = 0;j <= n;j++) dp2[j] = dp1[j],dp1[j] = 0;
		for(int j = 0;j <= i;j++){
			for(int k = 0;k <= c[i];k++){
				int t = k - (c[i] - k);
				int x = (i - (j + t)) / 2; 
				if(j + t >= 0 && j + t + x <= n) {
					add(dp1[j + t],mul(mul(dp2[j],mul(fac[k],fac[c[i] - k])),C(c[i],k)));
				}
			}
		}
	}
	printf("%d",dp1[0]);
}

E

只要懂基本的期望知识即可

代码

char ch[3];

int main(){
	int n = rd();
	int k = rd();
	double ans = 0;
	for(int i = 1;i <= n;i++){
		scanf("%s",ch);
		double p;
		scanf("%lf",&p);
		if(ch[0] == 'D') {
			ans += p * 16;
		}
		else if(ch[0] == 'C') {
			ans += p * 24;
		}
		else if(ch[0] == 'B'){
			ans += p * 54;
		}
		else if(ch[0] == 'A'){
			ans += p * 80;
		}
		else {
			ans += p * 10000;
		}
	}
	ans *= k;
	ans -= k * 23;
	printf("%.10f",ans);
}

G

题意

给出\(n\)个数,\(P = \prod a_i\)\(ans_i = \frac{P}{a_i} \ mod \ 998244353\)

\[1 \leq a_i \leq 1e9 \]

分析

幸好这道题没在正式比赛出,否则完了。没有注意到\(998244353\)在模\(998244353\)下没有逆,直接用逆元去做WA了两发,这样的话得特判掉998244353这种情况。

事实上可以直接维护前缀积和后缀积

代码

SB讨论

int main(){
	int n = rd();
	VI v(n + 1);
	int ans = 1;
	int res = 1;
	int cnt = 0;
	for(int i = 1;i <= n;i++){
		v[i] =rd();
		ans = mul(ans,v[i]);
		if(v[i] != MOD) res = mul(res,v[i]);
		else cnt++;
	}
	if(cnt > 1) {
		for(int i = 1;i <= n;i++)
			printf("0 ");
		return 0;
	}
	else
	for(int i = 1;i <= n;i++){
		if(v[i] != MOD)
			printf("%d ",mul(ans,ksm(v[i])));
		else printf("%d ",res);
	}
}

J

题意

两人轮流取卡片,获得的价值是所有物品的价值的和的绝对值,先后手都希望两人的最终价值比对方的越大越好

\[1 \leq n \leq 5000 \]

分析

其实比较感性得也可以理解直接取最大的即可。

严格的讲 假设Alice获得价值为\(|A|\),Bob获得的\(|B|\),本质都想使自己价值尽可能大。
由于所获价值都带有绝对值,因此对所有数取反并不会影响答案,我们不失一般性地设\(S = \sum a_i \geq 0\)

那么

\[ans = |A| - |B| = |A| - |S - A| = \begin{cases}S & A \geq S \\ 2A-S & 0<A<S \\ -S & A \leq 0 \end{cases} \]

可知ans具有单调性,所以只要每次都取最大的数即可

代码

int a[5005];

int main(){
	int n = rd();
	ll tot = 0;
	for(int i = 1;i <= n;i++)
		a[i] = rd();
	sort(a + 1,a + n + 1);
	reverse(a + 1,a + n + 1);
	ll ans = 0;
	for(int i = 1;i <= n;i += 2)
		ans += a[i];
	ans = abs(ans);
	ll res = 0;
	for(int i = 2;i <= n;i += 2)
		res += a[i];
	res = abs(res);
	ans = ans - res;
	reverse(a + 1,a + n + 1);
	ll ans2 = 0;
	for(int i = 1;i <= n;i += 2)
		ans2 += a[i];
	ans2 = abs(ans2);
	res = 0;
	for(int i = 2;i <= n;i += 2)
		res += a[i];
	res = abs(res);
	ans = max(ans,ans2 - res);
	printf("%lld",ans);
}

K

题意

给出\(n\)个字符串,两人轮流操作,不能操作者输

每次有以下两种选择:1.选择一个非空字符串,取走任意一个字符。2.选择一个非空字符串,取走任意两个不同字符

\[1 \leq n \leq10,1 \leq |s| \leq40 \]

分析

注意到对一个字符串来说,选择的位置没有限制,即选择只和第二个操作带来的:不同的字符个数有关。

可以打表得到\(\sum_{i=1}^{40} P(i) = 215308\) 状态数不多,因此只需要暴力求SG函数,这里为了减少常数,使用了对集合的哈希

代码

const ull base = 131;
const int maxn = 45;

unordered_map<ull,int> vis;

ull get_hash(VI &cur){
    ull res = 0,fac = 1;
    for(auto &it: cur){
	res += fac * it;
	fac = fac * base;
    }
    return res;
}

int dfs(VI cur){
    sort(cur.rbegin(),cur.rend());
    ull Hash = get_hash(cur);
    if(vis.count(Hash)) return vis[Hash];
    if(!Hash) return 0;
    set<int> st;
    for(int i = 0;i < (int)cur.size();i++){
	if(cur[i]) {
	    cur[i]--;
	    st.insert(dfs(cur));
	    cur[i]++;
	}
	else break;
    }
    for(int i = 0;i < (int)cur.size();i++){
	if(!cur[i]) break;
	for(int j = i + 1;j < (int)cur.size();j++){
	    if(cur[j]) {
		cur[i]--;
		cur[j]--;
		st.insert(dfs(cur));
		cur[i]++;
		cur[j]++;
	    }
	    else break;
	}
    }
    int Mex = 0;
    for(auto &it:st){
	if(it != Mex) break;
	Mex++;
    }
    return vis[Hash] = Mex;
}

char s[45];

int main(){
    int T = rd();
    while(T--){
	int n = rd();
	int ans = 0;
	for(int i = 0;i < n;i++){
	    scanf("%s",s);
	    int len = strlen(s);
	    VI cnt(26);
	    for(int j = 0;j < len;j++)
		cnt[s[j] - 'a']++;
	    ans ^= dfs(cnt);
	}
	if(ans) puts("Alice");
	else puts("Bob");
    }
}

分拆数可以用\(O(n^2)\)的递推求得,也可以用\(O(n\sqrt{n})\)五边形数定理求得

int get(int x){
    int ans = 0;
    for(int i = 1;i * i <= x;i++){
	if(x % i) continue;
	ans += i;
	if(i * i == x) break;
	ans += x / i;
    }
    return ans;
}

int dp[45];

int main(){
    int ans = 0;
    dp[0] = 1;
    for(int i = 1;i <= 40;i++){
	for(int j = 0;j <= i - 1;j++)
	    dp[i] += get(i - j) * dp[j];
	dp[i] /= i;
    }
    cout << dp[19]
}
int w[maxn];
int f[maxn];

int main(){
    int k = 1;
    for(int i = 1;w[k - 1] <= maxn - 5;i++){
	w[k++] = (3 * i * i - i) / 2;
	w[k++] = (3 * i * i + i) / 2;
    }
    f[0] = 1;
    for(int i = 1;i <= maxn - 5;i++){
	for(int j = 1;w[j] <= i;j++){
	    if(((j - 1) >> 1) & 1) add(f[i],MOD - f[i - w[j]]);
	    else add(f[i],f[i - w[j]]);
	}
    }
    int T = rd();
    while(T--){
	int n = rd();
	printf("%d\n",f[n]);
    }
}

I

题意

要求维护三个序列。支持以下4种操作

1.查询\(x\)个序列区间和

2.第\(x\)个序列区间加\(v\)

3.第\(x\)个序列和第\(y\)个序列区间对应位置交换

4.第\(x\)个序列区间加上第\(y\)个序列对应位置的值

分析

三中操作可以对应线性代数中的初等变换,这些初等变换可以看做一个列向量左乘一个初等矩阵。

\[\left [\begin{matrix} a_1 \\ a_3 \\ a_2 \end{matrix} \right] = \left[\begin{matrix} 1 & 0 &0\\ 0 & 0 & 1\\ 0 & 1 & 0\end{matrix}\right] \cdot \left [\begin{matrix} a_1 \\ a_2 \\ a_3 \end{matrix} \right] \]

显然这样的$3 \times 3 $的矩阵就可以做操作34了,操作2,则需要额外维护一个信息。

因此线段树上每个节点维护一个\(4 \times 1\)的列向量即可,每次区间乘一个\(4 \times 4\)的初等矩阵

代码

struct mat{
    int a[4][4];
    mat(){memset(a,0,sizeof a);}
    mat operator * (const mat &c) const {
	mat res;
	for(int i = 0;i < 4;i++)
	    for(int j = 0;j < 4;j++)
		for(int k = 0;k < 4;k++)
		    add(res.a[i][j],(ll)a[i][k] * c.a[k][j] % MOD);
	return res;
    }
    bool operator != (const mat &c) const{
	for(int i = 0;i < 4;i++)
	    for(int j = 0;j < 4;j++)
		if(a[i][j] != c.a[i][j]) return true;
	return false;
    }
}I;

inline void mul(int *arr,mat &c){
    int tmp[4] = {0};
    for(int i = 0;i < 4;i++)
	for(int j = 0;j < 4;j++)
	    add(tmp[i],(ll)c.a[i][j] * arr[j] % MOD);
    for(int i = 0;i < 4;i++)
	arr[i] = tmp[i];
}

const int maxn = 3e5 + 5;

int sum[maxn << 2][4];

struct SegmentTree{
	int n;
	vector<mat> tag;
	SegmentTree(int n):n(n),tag(((n + 1) << 2)) {}
	inline void push_up(int i){
		for(int j = 0;j < 4;j++)
		    sum[i][j] = (sum[i << 1][j] + sum[i << 1|1][j]) % MOD;
	}
	void build(int i,int l,int r){
	    tag[i] = I;
	    if(l == r) {
		sum[i][0] = 1;
		return;
	    }
	    int mid = l + r >> 1;
	    build(i << 1,l,mid);
	    build(i << 1|1,mid + 1,r);
	    sum[i][0] = sum[i << 1][0] + sum[i << 1|1][0];
	    if(sum[i][0] >= MOD) sum[i][0] -= MOD;
	}
	inline void update(int i,mat &v){
	    tag[i] = v * tag[i];
	    mul(sum[i],v);
	}
	inline void push(int i){
		if(tag[i] != I) {
			update(i << 1,tag[i]);
			update(i << 1|1,tag[i]);
			tag[i] = I;
		}
	}
	void update(int i,int l,int r,int L,int R,mat &v){
		if(l > R || r < L) return;
		if(l >= L && r <= R) return update(i,v);
		int mid = l + r >> 1;
		push(i);
		update(i << 1,l,mid,L,R,v);
		update(i << 1|1,mid + 1,r,L,R,v);
		push_up(i);
	} 
	int query(int i,int l,int r,int L,int R,int x){
		if(l > R || r < L) return 0;
		if(l >= L && r <= R) return sum[i][x];
		int mid = l + r >> 1;
		push(i);
		return (query(i << 1,l,mid,L,R,x) + query(i << 1|1,mid + 1,r,L,R,x)) % MOD; 
	}
};

int main(){
    I.a[0][0] = I.a[1][1] = I.a[2][2] = I.a[3][3] = 1;
    int n = rd();
    int q = rd();
    SegmentTree seg(n);
    seg.build(1,1,n);
    while(q--){
	int op = rd();
	if(op == 0) {
	    int x = rd();
	    int l = rd();
	    int r = rd();
	    printf("%d\n",seg.query(1,1,n,l,r,x));
	}
	else if(op == 1) {
	    int x = rd();
	    int l = rd();
	    int r = rd();
	    int y = rd();
	    mat tmp = I;
	    tmp.a[x][0] = y;
	    seg.update(1,1,n,l,r,tmp);
	}
	else if(op == 2){
	    int x = rd();
	    int y = rd();
	    int l = rd();
	    int r = rd();
	    mat tmp = I;
	    if(x != y) tmp.a[x][y] = tmp.a[y][x] = 1,tmp.a[x][x] = tmp.a[y][y] = 0;
	    seg.update(1,1,n,l,r,tmp);
	}    
	else{
	    int x = rd();
	    int y = rd();
	    int l = rd();
	    int r = rd();
	    mat tmp = I;
	    tmp.a[y][x]++;
	    seg.update(1,1,n,l,r,tmp);
	}
    }
}
posted @ 2021-09-13 22:25  MQFLLY  阅读(428)  评论(6编辑  收藏  举报