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\) ,它之后的后缀都会对答案有贡献

可表示为

\[gcd1 = gcd(lcm(a,b),lcm(a,c),lcm(a,d)\dots lcm(a,a_n)) \]

可以发现,对任意的 $ lcm(a,x) $ ,它们都有公因子 $ a $

$ gcd $ 实际上是对数字的每个质因子的幂次取 $ min $ 的操作

$ lcm $ 则是对每个质因子的幂次取 \(max\) 的操作

因为区间 $ gcd$ 的可以拆成两个数的 \(gcd\) ,所以可直接分析只有三个数的情况

\[gcd = (lcm(a,b),lcm(a,c)) \]

从质因子的视角下看这个式子

设任意一个质因为 \(p\) ,$ a $ 的指数为 $ x $ ,\(b\) 的指数为 \(y\) , \(c\) 的指数为 \(z\) ,对每一个质因子,上式变为

\[gcd' = min(max(x,y),max(x,z)) = max(x,min(y,z)) \]

因此有

\[gcd1 = lcm(a,gcd(b,c,d,\dots)) \]

之后每个 \(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

  1. \([0,0,0],[1,1,1],[2,2,2]\) ,很显然不做讨论
  2. \([0,0,1]\) ,会将 1 变为 0,对答案不利,同理 \([1,2,2]\) 也是
  3. \([0,1,1]\) 可以化归到长度为2的情况
  4. \([0,0,2]\) 的情况会让一个大数变小数,此时如果这个大数旁边就是一个 \(k\) 就会和这个大数构成好串,对答案来说是不优的
  5. \([0,2,2]\) 的情况可以将小数变成大数,然后不断扩散这个大数,直到遇到第一个 \(k\) ,之后又会得到一个好串是可行的
  6. 之后对于 \([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;
}
posted @ 2022-03-19 15:18  Mxrurush  阅读(27)  评论(0编辑  收藏  举报