訓練情況
賽後反思
被小資料背刺了,吃了幾發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;
}