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);
	}
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM