「模拟赛」暑期集训CSP提高模拟6(7.23)
\(140 pts,Rank 23\)
题目列表
花间叔祖
\(98pts\)
题意:
给定一个数组,选择一个大于等于 2 的模数,然后把数组中的数变成 \(mod\) 该模数后的数。
只能操作一次,问操作后最少有几种不同的数。
赛时分析:
开始 5 分钟想到了算 \(a_i\) 中所有数的最小质因数,若不是 1,那么答案为 1,否则为 2,觉得很对,测大样例,WA,??自己觉得思路很对,由于大样例锅了很多次了,所以怀疑是不是大样例的问题,就先跳了,等着看改不改大样例。半个小时之后,回来,还没改,这应该不是大样例的问题了,于是拿大样例调试,果然发现了可以使答案为 1,想法假了!于是自己手模小样例发现问题所在,列出式子,直接出来了。结果少考虑一个点,导致没拿满.
正解:
容易得到答案不是 1 就是 2,若存在一个模数使得 \(a_i(i\in[1,n])\) mod 该数都为 \(d\),那么有 \(k_i*x+d=a_i\)(\(x\) 为模数),那么 \(a_i-a_j \ (a_i>a_j)\) 化简可以得到 \(a_i-a_j=(k_i-k_j)*x\),那么我们只要拿 \(a\) 数组中的数两两作差得到 \(b\) 数组判有无公因数即可。
赛时我拿 \(a\) 数组排序后相邻的两个数相减没考虑可能为 0 的情况所以挂了 2pts。
code:
#include<bits/stdc++.h>
#define mp make_pair
#define ll long long
using namespace std;
const int N = 2e5 + 10;
int n, a[N], Ygcd, cnt, Xgcd;
int b[N];
signed main(){
// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
scanf("%d%d%d", &n, &a[1], &a[2]);
Ygcd = __gcd(a[1], a[2]);
for(int i=3; i<=n; i++){
scanf("%d", &a[i]);
Ygcd = __gcd(Ygcd, a[i]);
}
if(Ygcd != 1){
puts("1"); return 0;
}
sort(a+1, a+1+n);
for(int i=2; i<=n; i++){
b[++cnt] = a[i] - a[1];
}
Xgcd = __gcd(b[1], b[2]);
for(int i=2; i<=cnt; i++){
Xgcd = __gcd(Xgcd, b[i]);
}
if(Xgcd != 1) puts("1");
else puts("2");
return 0;
}
合并 r
题意:
有 \(n\) 个神奇的 \(r\),每个 \(r\) 有一个美味值,美味值都能表示为 \(\frac{1}{2^i}\),其中 \(i∈N\)(自然数集)。
Delov 一口吃掉了美味值之和为 \(k\) 的 \(n\) 个 \(r\), 现在他想知道每个 \(r\) 的美味值,但是可能的结果太多了,于是他只想知道有多少种可能性,你只需要告诉他方案数 \(mod 998244353\) 的结果。
两种方案不同,则存在 \(i\), 使得美味值为 \(\frac1{2^i}\) 的 \(r\) 的个数在两种方案中不同。
正解:
原题 \(ARC107D\)
\(f_{i, j}\) 表示 \(i\) 个数和为 \(j\) 的方案数
我们这样考虑,\(\frac{1}{2^i} = 1 / 2 / 2 ....\)
就是说,当前如果凑成了 \(j\),那么所有数除以二,就能凑成 \(j / 2\)
那么我们只需两种操作,加一与除二,加一代表多选了一个数(初始为 \(1\) ),除二代表将原方案选的数中二的幂次都加一
即 \(f_{i, j} = f_{i - 1, j - 1} + f_{i, j * 2}\)
初始化 \(f_{0,0} = 0\)
code:
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 10;
const int p = 998244353;
int n, k;
int f[N][N];
int main(){
// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>k;
f[0][0] = 1;
for(int i=1; i<=n; i++){
for(int j=i; j>=1; j--){
f[i][j] = f[i-1][j-1];
if(j * 2 <= i) f[i][j] += f[i][j*2];
f[i][j] %= p;
}
}
cout<<f[n][k];
return 0;
}
回收波特:
题意:
Delov 不和 npy、 们玩了,这回他和他的波特们一起玩。
他和他的 \(n\) 个波特们在一条道路上的不同地点,不妨认为这是一条数轴,而 Delov 位于原点。不幸的是波特们都快没电了,现在 Delov 要开始回收波特了。
Delov 只有一个控制所有波特的遥控器,所以每次他会给出一个参数 \(di\) ,波特就会向着目前 Delov 相对它所在的方向移动 \(di\) 的单位长度,恰好到达原点的波特会被回收不再移动。
在发送了 \(m\) 条指令后波特们没电了,现在 Delov 想知道每个波特是否被回收,如果被回收,他想知道该波特是在第几个指令后被回收,便于充电;如果没被回收,他想知道该波特最终处于的位置,便于手动回收。
你能帮帮他吗?
赛时分析:
一开始一眼就觉得会了,二分思路,时间复杂度正确,然后大喊一句“正解”开打,打了一半二分发现假了。。。于是就先按暴力 \(30pts\) 打,在打假的二分上套了一层二分,最优情况 \(O(m \log^2n)\),最坏 \(O(mn\log\ n)\),打完怕以为时间内复杂度不稳定连该拿的暴力分都拿不到,于是加了个 if(n*m<=200000000)
然后跟暴力代码,否则跟玄学复杂度代码,最终 \(32 pts\),赛后交上只有玄学复杂度代码 \(42pts\),。。。
正解:
(复制的下发题解思路较简单)
官方题解写的很棒,但是是英语,英语好的/使用翻译的同学可以移步
粘个官方的图
注意到值域有限,考虑对值域内所有数处理出答案
观察一次操作过后,一部分超过原点,一部分到达原点,一部分没过原点,
到达的不用管而超过的部分,后续操作和关于原点对称的位置的操作一模一样,于是可以合并
每次将正半轴负半轴较短的一截与另一边合并起来
这样每个点只会操作 \(o(1)\) 次,复杂度就可以保证了。
code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, vis[N], ans[N];
int x[N], d[N];
std::vector<int>v[N], G;
void dfs(int x, int flag){
for(auto y : v[x]){
vis[y] = vis[x], ans[y] = -1.0 * flag * ans[x];
dfs(y, flag);
}
}
int main(){
// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1; i<=n; i++) cin>>x[i];
for(int i=1; i<=m; i++) cin>>d[i];
int l = 1, r = N - 10, pos = 0, f = 1;//f=1:0 in the left
for(int i=1; i<=m; i++){
int D = d[i];
pos += f * D; // now 0's position
if(pos > r) f = -1;
else if(pos < l) f = 1;
else{
vis[pos] = true, ans[pos] = i;
G.push_back(pos);
int lnum = pos - l, rnum = r - pos;
if(lnum > rnum){
for(int j=1; j<=r-pos; j++) //connect the side
v[pos-j].push_back(pos+j);
r = pos - 1, f = -1;
}
else{
for(int j=1; j<=pos-l; j++)
v[pos+j].push_back(pos-j);
l = pos + 1, f = 1;
}
}
}
for(int i=l; i<=r; i++){
ans[i] = i - pos;
dfs(i, 1);
}
for(int i : G) dfs(i, -1);
for(int i=1; i<=n; i++){
if(vis[x[i]]) printf("Yes");
else printf("No");
printf(" %d\n", ans[x[i]]);
}
return 0;
}