zoukankan      html  css  js  c++  java
  • 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
    ",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 imes n) 的序列,这个序列需要满足每一行从左到右不递减,第一行比第二行不递减,数字带标号 求可能的方案数

    [2 leq n leq 5000,1leq 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)! inom{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| = egin{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(nsqrt{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
    ",f[n]);
        }
    }
    

    I

    题意

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

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

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

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

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

    分析

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

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

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

    因此线段树上每个节点维护一个(4 imes 1)的列向量即可,每次区间乘一个(4 imes 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
    ",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);
    	}
        }
    }
    
  • 相关阅读:
    Keras学习率调整
    机器学习算法的调试---梯度检验(Gradient Checking)
    Python 上下文管理器
    Python垃圾回收机制
    Css 动画的回调
    全新的membership框架Asp.net Identity——绕不过的Claims
    CSS代码重构与优化
    html5 本地存储
    ASP.NET MVC 随想录
    谈谈Angular关于$watch,$apply 以及 $digest的工作原理
  • 原文地址:https://www.cnblogs.com/hznumqf/p/15265213.html
Copyright © 2011-2022 走看看