Codeforces Round #641 (Div. 2)
Codeforces Round #641 (Div. 2)
A
题意
定义 \(f(x)\) 表示 \(x\) 的最小非一因子
给出 \(k\) 次操作,每次计算 \(f(x) + x\) 的值,并令 $x = f(x) + x $
求经过 \(k\) 次操作后的答案 \(x\)
思路
显然如果 \(x\) 是偶数,最小因子是2
偶数加偶数不会改变奇偶性,所以最后答案就是 \(k * 2\)
如果 \(x\) 是奇数,如果最小因子是一定是奇数
奇数加奇数改变奇偶性,划归到偶数情况
因此只要先暴力把 \(x\) 变成偶数,再加上剩余次数乘2即可
#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<'>' << ' ' << x<<endl;
#define ull unsigned long long
#define endl '\n'
#define lowbit(x) x&-x
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int N,K;
cin >> N >> K;
int ans = N;
while(K --) {
int r = 0;
for(int i = 2;i <= ans / i;i ++) {
if(ans % i == 0) {
r = i;
break;
}
}
if(!r) r = ans;
ans = ans + r;
if(ans % 2 == 0) break;
}
ans += K * 2 ;
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int T;cin>>T;
while(T--)
solve();
return 0;
}
B
题意
给 $1 \dots N $ 个数字分配权值 \(s\) ,现在要从这个序列中选出满足如下要求的最长序列,输出其长度
定义漂亮序列:
序列中任意的数字 \(a_i\) 整除 $ a_{i + 1} $ ,即 $ a_i | a_{i + 1} $
且它们的权值 $ s_i $ 构成的序列严格递增
思路
显然最坏长度为1
为了保证 $ s$ 的单调性,可以将数字按照它们的权值排序
问题转为,在一个前缀中选择最长序列的问题
我们可以枚举以第 \(i\) 个数字为序列结尾的情况
再枚举 \(i\) 的所有因子,看这个因子是否在这个序列中存在
如果存在,继续以这个因子为结尾向前枚举
单纯向前搜索显然超时,所以在搜索过程中加入记忆化即可
PS:排序时,如果权值相等,为保证权值的严格递增,需要将数字按照降序排列
PS2:如果不加记忆化,不仅时间复杂度不接受,正确性也是不能保证的,因为搜索时忽略前缀的顺序的,比如3,2,1,4 就很有可能出现 $4 -> 2 -> 1 $ 的搜索链,所以在搜索时还应该记录因子具体出现的位置。但因为加上了记忆化,前缀都是被搜索过的合法情况,就不用再在意这些,只要第一次搜索合法之后返回的都是合法的答案。
所以本题更加简便的写法应该是DP,这里就不写了
#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<'>' << ' ' << x<<endl;
#define ull unsigned long long
#define endl '\n'
#define lowbit(x) x&-x
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int N;
cin >> N;
vector<int> s(N + 1);
vector<int> ids(N + 1);
for(int i = 1;i <= N;i ++) {
cin >> s[i];
ids[i] = i;
}
sort(ids.begin() + 1,ids.end(),[&](int a,int b) {
if(s[a] != s[b]) return s[a] < s[b];
return a > b;
});
vector<bool> st(N + 1);
vector<int> f(N + 1,-1);
function<int(int)> dfs = [&](int x) {
if(f[x] != -1) return f[x];
if(x == 1) {
return (int)(st[x]);
}
vector<int> d;
d.push_back(1);
int ans = 1;
for(int i = 2;i <= x / i;i ++) {
if(x % i == 0) {
d.push_back(i);
if(i * i != x) {
d.push_back(x / i);
}
}
}
for(int i = 0;i < d.size();i ++) {
if(st[d[i]]) ans = max(ans,1 + dfs(d[i]));
}
return f[x] = ans;
};
int ans = 1;
for(int i = 1;i <= N;i ++) {
int cur = ids[i];
st[cur] = 1;
ans = max(ans,dfs(cur));
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int T;cin>>T;
while(T--)
solve();
return 0;
}
C
题意
求一个序列所有子串的$ lcm $ 的 $ gcd$
思路
最暴力的解法就是枚举坐端点,枚举右端点计算
现在考虑如何优化掉对右端点枚举
设对枚举到的左端点 \(a\) ,它之后的后缀都会对答案有贡献
可表示为
可以发现,对任意的 $ lcm(a,x) $ ,它们都有公因子 $ a $
$ gcd $ 实际上是对数字的每个质因子的幂次取 $ min $ 的操作
$ lcm $ 则是对每个质因子的幂次取 \(max\) 的操作
因为区间 $ gcd$ 的可以拆成两个数的 \(gcd\) ,所以可直接分析只有三个数的情况
从质因子的视角下看这个式子
设任意一个质因为 \(p\) ,$ a $ 的指数为 $ x $ ,\(b\) 的指数为 \(y\) , \(c\) 的指数为 \(z\) ,对每一个质因子,上式变为
因此有
之后每个 \(gcd_i\) 再取个 $ gcd $ 就是答案
#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<'>' << ' ' << x<<endl;
#define ull unsigned long long
#define endl '\n'
#define lowbit(x) x&-x
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int N;
cin >> N;
vector<int> a(N + 1);
for(int i = 1;i <= N;i ++) {
cin >> a[i];
}
vector<int> g(N + 1);
g[N] = a[N];
for(int i = N - 1;i > 0;i --) {
g[i] = __gcd(g[i + 1],a[i]);
}
int ans = 0;
for(int i = 1;i < N;i ++) {
ans = __gcd(ans,g[i + 1] * a[i] / __gcd(a[i],g[i + 1]));
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
//int T;cin>>T;
//while(T--)
solve();
return 0;
}
知乎上有更加对这个式子更详细的证明 怎么证明GCD(a,LCM(b,c))=LCM(GCD(a,b),GCD(a,c))? - 知乎 (zhihu.com)
D
题意
对一个序列进行区间操作
每次可以把一段区间变成这个区间第 $ \frac {|s| + 1 }{2}$ 大的数
问最后是否可以全变成目标数字 \(k\)
思路
显然如果序列中没有 \(k\) 一定不行
按区间长度考虑
如果区间长度为2,如果可以把两个数字变为 $ k$
之后只要不断选择长度为3的区间,总可以达成目标
我们先把这上述可以达成要求的串叫好串
如果长度为2的区间不行,比如 \([2,1,4,3,5]\) 在 $k = 4 $ 时就是不行的
考虑长度为3的情况
不妨设小于 $ k $ 为 0, 等于 $ k $ 为 1, 大于 $ k$ 为 2
- \([0,0,0],[1,1,1],[2,2,2]\) ,很显然不做讨论
- \([0,0,1]\) ,会将 1 变为 0,对答案不利,同理 \([1,2,2]\) 也是
- \([0,1,1]\) 可以化归到长度为2的情况
- \([0,0,2]\) 的情况会让一个大数变小数,此时如果这个大数旁边就是一个 \(k\) 就会和这个大数构成好串,对答案来说是不优的
- \([0,2,2]\) 的情况可以将小数变成大数,然后不断扩散这个大数,直到遇到第一个 \(k\) ,之后又会得到一个好串是可行的
- 之后对于 \([0,1,2],[1,1,2]\) 的情况同上分析,核心是通过这样的变换是否可以构成一个好串
对长度大于三的区间,可以发现,总可以用长度为三或者长度为二的序列拼出,本质上和长度为三或二的区间一样。
综上,只要对长度为2和3的区间扫一遍,判断是否有满足条件的情况即可
#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<'>' << ' ' << x<<endl;
#define ull unsigned long long
#define endl '\n'
#define lowbit(x) x&-x
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int N,K;
cin >> N >> K;
bool ok = 0;
vector<int> a(N + 1);
for(int i = 1;i <= N;i ++) {
cin >> a[i];
ok |= (K == a[i]);
}
if(N == 1) {
if(K == a[1]) {
cout << "yes\n";
}else {
cout << "no\n";
}
return;
}
if(!ok) {
cout << "no\n";
}else {
for(int i = 1;i < N;i ++) {
int x = a[i],y = a[i + 1];
if(x > y) swap(x,y);
if(x == K) {
cout << "yes\n";
return;
}
}
for(int i = 1;i < N - 1;i ++) {
vector<int> t;
for(int j = i;j < 3 + i;j ++) {
t.push_back(a[j]);
}
sort(t.begin(),t.end());
if(t[1] >= K && t[2] >= K) {
cout << "yes\n";
return;
}
}
cout << "no\n";
}
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int T;cin>>T;
while(T--)
solve();
return 0;
}