CodeForces_R919 + 牛客练习赛120混合题解
这是1月12~1月13日期间的两场比赛,题目考察数学方向和思维方向的知识会多一点,这篇文章混合选取了部分题目并给出了题解,题目顺序由个人认为的难度由低到高给出。
【题目1】(牛客练习赛120_B生成函数,数学)
【题意】有三种数量无限的砝码和一个天平,天平的一端有一个质量为 m 的物品,问能否通过放置砝码使得天平平衡?
【思路】本题属于裴蜀定理的扩展,类似于:LG4549、CF510D
裴蜀定理:(摘自OI-Wiki)
有了这些前置知识以后,我们只需要关注m是否能被a,b,c的最大公因数整除即可,值得留意, 头文件<algorithm>里面是自带__gcd的。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b,c,m;
signed main(){
int ttt; cin >> ttt;
while(ttt--){
cin >> a >> b >> c >> m;
int gcd = a;
gcd = __gcd(gcd, b), gcd = __gcd(gcd, c);
if(m % gcd) puts("NO");
else puts("YES");
}
return 0;
}
【题目2】(Codeforces Round 919(Div. 2) Partitioning the Array,数学)
【英文题面】
【题意】给定长为 n 的序列 a ,对于 n 的所有因数,我们可以把序列a分成如下的 n/k 个子序列:
如果能找到一个大于等于2的正整数 m 使得这 n/k 个子序列对m取模后完全相同,则答案+1。
【思路】依题,,所以m是
的因数,由于是存在性证明,所以我们也是只需要看公共最大公因数来找到一个满足的m即可。
#include <bits/stdc++.h>
using namespace std;
const int N(2e5+5);
int a[N];
inline void sol(){
int n, ans = 0; cin >> n;
for(int i=0;i<n;i++) cin >> a[i];
for(int k=1;k<=n;k++)
if (n % k == 0){
int gcd = 0;
for (int i=0;i+k<n;i++) gcd = __gcd(gcd, abs(a[i+k]-a[i]));
ans += (gcd!=1);
}
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
int ttt; cin >> ttt;
while(ttt--) sol();
return 0;
}
【题目3】(牛客练习赛120_C选择交换,思维)
【题意】给定一个数列a,问经过数次交换以后能否让数列满足下式要求:
能的话输出YES并输出这数次交换的操作元素下标,不能的话输出NO。
【思路】显然,上式最终等于,而最终的序列一定是排好序的序列,故只需要进行判断和模拟即可。
#include <bits/stdc++.h>
#define pii pair<int,int>
#define endl '\n'
using namespace std;
template <class T>
inline void read(T &x){ x=0; char c = getchar(); bool f=0; for(;!isdigit(c);c=getchar()) f^= (c=='-'); for(;isdigit(c);c=getchar()) x=(x<<3) + (x<<1) + (c^48); x=f?-x:x;}
const int N(2e5+5);
struct node{
int v,idx;
bool operator<(const node& kkk) const {return v < kkk.v;}
}a[N],b[N];
int n;
void sol(){
vector<pii> res; read(n);
for(int i=1;i<=n;i++) read(a[i].v), a[i].idx=i, b[i]=a[i];
sort(b+1,b+1+n);
int sum=b[1].v+b[n].v;
for (int i=1;i<=(n+1)/2;i++)
if (b[i].v+b[n-i+1].v!=sum){ cout << "NO" << endl; return; }
for(int i=1;i<=n;i++) a[b[i].idx].idx = i;
for(int i=1;i<=n;i++)
while(a[i].idx!=i)
res.push_back({i,a[i].idx}), swap(a[i], a[a[i].idx]);
cout << "YES" << endl;
cout << (int)res.size() << endl;
for(auto &x:res)
cout << x.first << " " << x.second << endl;
}
int main(){
int ttt; read(ttt);
while(ttt--) sol();
return 0;
}
【题目4】(牛客练习赛120_D数数大师,数学)
【题意】给定长为 n 的序列 a,可以多次对序列中的某元素进行 +1 操作,问至多操作 k 次后,序列中至多有多少个区间和为奇数的区间。
【思路】我们其实只关心序列的奇偶性,故我们可以讨论在模 2 意义下的前缀和序列sum,区间 (l , r) 的奇偶性为 sum[ l-1 ] ⊗ sum[ r ],问题可以转化为尽可能让0和1的数量接近,而原题定义的操作相当于反转i以后的01串。而且可以发现至多操作1次就可以达到最优解,证明如下:
证明:假设sum序列的第 i 位以后 0 的个数为 (len-i)-m , 1 的个数为 m ,第 i 位以前(含第 i 位)的 0 个数为 i-n ,1 的个数为 n 。
所以操作前sum序列 0 的总数 (Σ0) 为 len-m-n ,1 的总数 (Σ1) 为 m+n ,(delta) = | len-2m-2n |。
操作一次后,第 i 位以后 0 的个数为 m , 1 的个数为 (len-i)-m ,此时 (Σ'0) = i-n+m ,(Σ'1) = len-i-m+n,(delta)' = | 2i-2n+2m-len |,显然 (delta)' 是关于i线性的,故至多操作1次就可以达到最优解。
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
template <class T>
inline void read(T &x){ x=0; char c = getchar(); bool f=0; for(;!isdigit(c);c=getchar()) f^= (c=='-'); for(;isdigit(c);c=getchar()) x=(x<<3) + (x<<1) + (c^48); x=f?-x:x;}
using namespace std;
const int N(2e5+5);
int a[N],sum[N];
void sol(){
int cnt=0,n,k;
read(n), read(k);
memset(sum,0,sizeof sum);
for(int i=1;i<=n;i++){
read(a[i]), a[i]&=1;
sum[i]=sum[i-1]^a[i];
cnt+=sum[i];
}
if(k) cnt=(n+1)/2;
cout << cnt*(n-cnt+1) << endl;
}
signed main(){
int ttt; read(ttt);
while(ttt--) sol();
return 0;
}
【题目5】(Codeforces Round 919(Div. 2) Array Repetition,数学)
【英文题面】
【题意】初始有一个空序列,有m次操作,每次给出一种操作:
1.在序列末尾添加一个数x;
2.将原序列复制c次插入到序列末尾。
每次询问为操作完的序列的第k个元素。
【思路】(来源于官方题解)首先,我们预计算lst和dp,其中lst数组是执行前i次操作后的最后一个元素,dp数组是执行前i次操作后的元素总数。显然,如果dp=k,则答案是lst。否则,我们就要求第一个i使得dpi>k。这将成为一个重复计算,而最后的答案就在其中一个重复计算中。此时,序列可以划分为这样:
设第k个元素为上图中的 l_2 ,该元素等价于(k % dp[i-1]) 个元素。
还有一道与此题极其类似的题可以拿来训练:CF380A
#include <bits/stdc++.h>
using namespace std;
#define ll long long
void solve(){
int n, q; cin >> n >> q;
ll dp[n + 1] = {};
int lstAdd[n + 1] = {};
vector<int> pos;
for (int i = 1, doAdd = true; i <= n; i++){
int a, v;
cin >> a >> v;
if (a == 1){
lstAdd[i] = v;
dp[i] = dp[i - 1] + 1;
}
else{
lstAdd[i] = lstAdd[i - 1];
dp[i] = ((v + 1) > 2e18 / dp[i - 1]) ? (ll)2e18 : dp[i - 1] * (v + 1);
if (doAdd)
pos.push_back(i);
}
// No need to consider any more repetitions after this point
if (dp[i] == 2e18) doAdd = false;
}
while (q--){
ll k; cin >> k;
for (int i = pos.size() - 1; ~i; i--){
int idx = pos[i];
if (dp[idx] > k and dp[idx - 1] < k){
if (k % dp[idx - 1] == 0){
k = dp[idx - 1];
break;
}
k %= dp[idx - 1];
}
}
cout<<lstAdd[lower_bound(dp + 1, dp + n + 1, k) - dp]<<" \n"[q == 0];
}
}
int main(){
ios_base::sync_with_stdio(0); cin.tie(0);
int tc; cin >> tc;
while (tc--) solve();
}