【vjudge訓練記錄】11月個人訓練賽1

MNNUACM_2024ZY發表於2024-11-03

訓練情況

賽後反思

被小資料背刺了,吃了幾發RE,不過還是調出來了

A題

我們先考慮將連續的 v 先換成 w,之後就是統計子序列 wow 的個數,我們只需要找每個 o 前面有多少個 w,之後有多少個 w,根據乘法原理可知,這個 o 對答案的貢獻就是兩個相乘,維護前面和後面的 w 我們可以考慮使用前字尾和。

#include <bits/stdc++.h>
#define int long long

using namespace std;

signed main(){
	string s; cin>>s;
	int n = s.size();
	if(n<=2){
		cout<<0<<endl;
		return 0;
	}
	string t;
	for(int i = 0;i<n;i++){
		if(s[i] == 'o') t+="o";
		if(i==0) continue;
		if(s[i] == 'v' && s[i-1] == 'v') t+="w";
	}
	n = t.size();
	vector<int> pre(n+1);
	vector<int> suf(n+1);
	pre[0] = (t[0] == 'w');
	suf[n-1] = (t[n-1] == 'w');
	for(int i = 1;i<n;i++){
		pre[i] = pre[i-1] + (t[i] == 'w');
	}
	for(int i = n-2;~i;i--){
		suf[i] = suf[i+1] + (t[i] == 'w');
	}
	int ans = 0;
	for(int i = 0;i<n;i++){
		if(t[i] == 'o') ans += (pre[i]*suf[i]);
	}
	cout<<ans<<endl;
	return 0;
}

B題

找數列中是否有 \(m\) 個大於 \(\frac{sum}{4m}\) 個元素,我們先直接求個和,再判斷每一位是否符合要求即可,注意除法會有精度和取整問題,所以我們考慮把除法移到左邊去乘。

#include <bits/stdc++.h>

using namespace std;

int main(){
	int n,m; cin>>n>>m;
	vector<int> a(n + 1);
	int sum = 0;
	for(int i = 1;i<=n;i++) cin>>a[i],sum+=a[i];
	int cnt = 0;
	for(int i = 1;i<=n;i++){
		if(a[i]*4*m < sum) cnt++;
	}
	if(n-cnt>=m) cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
	return 0;
}

C題

很顯然要絕對值最接近 \(0\)\(n\) 要減去 \(k\) 倍的 \(m\),並且這個 \(k\) 要足夠大,這就是直接 \(n \mod m\) 即可,再和負數的絕對值情況取大值即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
	int n,m; cin>>n>>m;
	cout<<min(n%m,abs(n%m-m));
	return 0;
}

D題

簡單的 DP,我們設計狀態 \(dp_{ij}\) 為走到第 \(i\) 行,\(j\) 列的路線數,它可以從它的上一行和上一列轉移過來,求一個和即可,記得跳過全部偶數的情況。

#include <bits/stdc++.h>
#define int long long

using namespace std;

int dp[33][33];

signed main(){
	int n,m; cin>>n>>m;
	dp[1][1] = 1;
	for(int i = 1;i<=n;i++){
		for(int j = 1;j<=m;j++){
			if(i%2==0&&j%2==0) continue;
			if(i==1&&j==1) continue;
			dp[i][j] = dp[i-1][j] + dp[i][j-1];
		}
	}
//	for(int i = 1;i<=n;i++){
//		for(int j = 1;j<=m;j++){
//			cout<<dp[i][j]<<" ";
//		}
//		cout<<endl;
//	}
	cout<<dp[n][m]<<endl;
	return 0;
}

E題

我們判斷曲線是否會經過圓,我們只需要判斷起點和終點一個在圓內,一個在圓外即可,這樣無論曲線怎麼畫都得經過這個圓,直接統計答案即可。

#include <bits/stdc++.h>

using namespace std;

double dis(int x,int y,int xx,int yy){
	return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}

int main(){
	int n; cin>>n;
	vector<int> x(n + 1),y(n + 1),r(n + 1);
	for(int i = 1;i<=n;i++) cin>>x[i];
	for(int i = 1;i<=n;i++) cin>>y[i];
	for(int i = 1;i<=n;i++) cin>>r[i];
	int xa,ya,xb,yb; cin>>xa>>ya>>xb>>yb;
	int ans = 0;
	for(int i = 1;i<=n;i++){
		double res1 = dis(xa,ya,x[i],y[i]);
		double res2 = dis(xb,yb,x[i],y[i]);
		if((res1<r[i]&&res2>r[i])||(res1>r[i]&&res2<r[i]))ans++;
	}
	cout<<ans<<endl;
	return 0;
}

F題

我們發現這是一道最長公共子序列題,但是這題沒有辦法使用 \(O(n^2)\) 的 DP,但是我們發現這一題是一個排列,我們可以考慮記錄每個排列出現的位置,既然要最長公共子序列,我們只需要每一個排列的下標遞增即可,透過下標位置的遞增,這就可以轉換成最長上升子序列,使用二分演算法 \(O(nlogn)\) 做。

#include<bits/stdc++.h>

using namespace std;

int main(){
	int n; cin>>n;
	vector<int> a(n + 1);
	vector<int> b(n + 1);
	vector<int> pos(n + 1);
	for(int i = 1;i<=n;i++) cin>>a[i],pos[a[i]] = i;
	for(int i = 0;i<=n;i++) a[i] = INT_MAX;
	for(int i = 1;i<=n;i++) cin>>b[i];
	for(int i = 1;i<=n;i++){
		int p = lower_bound(a.begin() + 1,a.end(),pos[b[i]]) - a.begin();
		a[p] = pos[b[i]];
	}
	int ans = lower_bound(a.begin() + 1,a.end(),a[0]) - a.begin() - 1;
	cout<<ans<<endl;
	return 0;
}

G題

我們想要 gcd 最大,我們顯然發現一對數的 gcd 是會小於等於它的較小的數,所以我們構造的數列的 gcd 必定等於最小的 a,之後就是統計能這樣構造的陣列一共有多少個,只要每個數在 \(a_i\) 的範圍內,並且都是那個 gcd (最小的 \(a_i\))的倍數即可,計算出每一位有多少种放置方法,最後根據乘法原理全部乘起來就是答案了。

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int modd = 1e9+7;
signed main(){
	int n; cin>>n;
	vector<int> a(n + 1);
	for(int i = 1;i<=n;i++) cin>>a[i];
	sort(a.begin() + 1,a.end());
	int cnt = 1;
	for(int i=1;i<=n;i++){
		cnt = cnt % modd * (a[i]/a[1])%modd;
	}
	cout<<a[1]<<" "<<cnt%modd<<endl;
	return 0;
}

相關文章