Codeforces Round #757 (Div. 2) C ~ D2

本文介绍了两道算法题目,分别涉及子集异或和与最大公约数开头的序列最大值的计算。第一题通过按位操作求解,第二题采用动态规划并优化了计算过程,降低了时间复杂度。文章提供了详细的思路分析和C++代码实现,适合提升算法理解与编程能力。
摘要由CSDN通过智能技术生成

传送门

C、

题面

在这里插入图片描述
在这里插入图片描述

结论

n n n个元素的子集的异或和 = 2 n − 1 ∗ A =2^{n-1}*A =2n1A

其中 A A A是所有元素按位或起来的结果

思路

题目保证每个位置上的元素都会被包含在内至少一次;


按位考虑;

如果某元素的某一位是 1 1 1,我们把这个元素(记为 x x x)抽出来;

那么剩下的 n − 1 n-1 n1个元素,子集有 2 n − 1 2^{n-1} 2n1

子集中这一位要么是 0 0 0,要么是 1 1 1

如果是 0 0 0的话,我们就选上 x x x,那么就得 1 1 1了;

如果是 1 1 1,那么我们不选 x x x,那么还是 1 1 1

因此如果某一位是 1 1 1,那么贡献为 2 n − 1 2^{n-1} 2n1

因此我们只需要将所有的数按位或起来,再乘上贡献就是答案了;

Code

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

const int MOD = 1e9 + 7;

int qpow(int x,int y){
    ll base = x,ret = 1;
    while(y){
        if(y&1){
            ret *= base;
            ret %= MOD;
        }
        base *= base;
        base %= MOD;
        y>>=1;
    }
    return ret;
}

void solve(){
    int n,m;
    cin >> n >> m;
    int l,r,x;
    ll tot = 0;
    while(m--){
        cin >> l >> r >> x;
        tot |= x;
    }
    cout << (tot%MOD * qpow(2,n-1)%MOD) << '\n';
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

D1、

题面

在这里插入图片描述
在这里插入图片描述

思路

在这里插入图片描述
不难想到 d p ( 1 ) = n dp(1)=n dp(1)=n

d p dp dp是从小的状态往大的状态转移;

时间复杂度 O ( m l o g m ) O(mlogm) O(mlogm)

Code

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10,M = 5e6 + 1;

#define int ll

int cnt[M + 5];//cnt(i) 表示是i的倍数的元素个数

int dp[M + 5];//dp(i) 表示以 gcd = i 开头,能得到的最大值

void solve(){
    int n;
    cin >> n;
    for(int i=1,x;i<=n;++i){
        cin >> x;
        ++cnt[x];
    }
    for(int i=1;i<M;++i)
        for(int j=2*i;j<M;j+=i)
            cnt[i] += cnt[j];
    dp[1] = n; //将数值1放在开头 那么答案必然是数的个数
    int ans = 0;
    for(int y=1;y<M;++y){
        for(int x=2*y;x<M;x+=y){
            dp[x] = max(dp[x],dp[y] + (x-y) * cnt[x]);
        }
        ans = max(ans,dp[y]);
    }
    cout << ans << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

D2、

题面

在这里插入图片描述

思路

比起D1来说, a i a_i ai的值变大了;

这个优化的思想跟普通埃筛优化埃筛是一样的;

首先看一下普通埃筛和优化埃筛的代码;

//普通埃筛 nlogn
for(int i=2;i<=n;++i){
    if(!vis[i]){
        primes.push_back(i);
    }
    for(int j=2*i;j<=n;j+=i){
        vis[j] = 1;
    }
}
 //优化埃筛 nloglogn
 for(int i=2;i<=n;++i){
        if(!vis[i]){
            primes.push_back(i);
            for(int j=2*i;j<=n;j+=i){
                vis[j] = 1;
            }
        }
    }

在上面我们是枚举了所有的倍数

但是实际的转移有很多的合数的重复的、不必要的;

因此我们可以只从质数转移过来,就像优化版埃筛一样;


优化了两个地方,一个是计算cnt,另一个是转移dp

这样处理的时间复杂度是 O ( m l o g l o g m ) O(mloglogm) O(mloglogm),见Code1


然后我觉得cnt数组的转移有点抽象;

我们可以分解因子来处理cnt,也是可以过的,不过稍微慢一点;

时间复杂度是 O ( n m ) O(n\sqrt{m}) O(nm ),见Code2

Code1

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10,M = 2e7 + 1;

#define int ll

int cnt[M + 5];//cnt(i) 表示是i的倍数的元素个数

int dp[M + 5];//dp(i) 表示以 gcd = i 开头,能得到的最大值

bool st[M];

vector<int> primes;

void init(){
    for(int i=2;i<M;++i){
        if(!st[i]) primes.push_back(i);
        for(auto p : primes){
            if(1ll * p * i > M) break;
            st[p*i] = 1;
            if(i % p == 0) break;
        }
    }
}

void solve(){
    init();
    int n;
    cin >> n;
    for(int i=1,x;i<=n;++i){
        cin >> x;
        ++cnt[x];
    }
    //因为越小的数倍数的个数越多
    //因此我们从大往小转移 
    for(auto p : primes)
        for(int j=M/p;j>=1;--j)
            cnt[j] += cnt[j * p];
    
    /*for(int i=1;i<M;++i)
        for(int j=2*i;j<M;j+=i)
            cnt[i] += cnt[j];*/
    dp[1] = n; //将数值1放在开头 那么答案必然是数的个数
    int ans = 0;
    for(int y=1;y<M;++y){
        for(auto p : primes){
            if(p * y >= M) break;
            int x = p*y;
            dp[x] = max(dp[x],dp[y] + (x-y) * cnt[x]);
        }
        ans = max(ans,dp[y]);
    }
    /*for(int y=1;y<M;++y){
        for(int x=2*y;x<M;x+=y){
            dp[x] = max(dp[x],dp[y] + (x-y) * cnt[x]);
        }
        ans = max(ans,dp[y]);
    }*/
    cout << ans << '\n';
}

signed main(){
    //std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

Code2

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10,M = 2e7 + 1;

ll cnt[M + 5];//cnt(i) 表示是i的倍数的元素个数

ll dp[M + 5];//dp(i) 表示以 gcd = i 开头,能得到的最大值

bool st[M];

vector<int> primes;

void init(){
    for(int i=2;i<M;++i){
        if(!st[i]) primes.push_back(i);
        for(auto p : primes){
            if(1ll * p * i > M) break;
            st[p*i] = 1;
            if(i % p == 0) break;
        }
    }
}

void solve(){
    init();
    int n;
    cin >> n;
    for(int i=1,x;i<=n;++i){
        cin >> x;
        for(int j=1;j*j<=x;++j){
            if(x%j == 0){
                ++cnt[j],++cnt[x/j];
                if(j == x/j) --cnt[x/j];
            }
        }
        //++cnt[x];
    }
    //因为越小的数倍数的个数越多
    //因此我们从大往小转移 
    /*for(auto p : primes)
        for(int j=M/p;j>=1;--j)
            cnt[j] += cnt[j * p];*/
    
    /*for(int i=1;i<M;++i)
        for(int j=2*i;j<M;j+=i)
            cnt[i] += cnt[j];*/
    dp[1] = n; //将数值1放在开头 那么答案必然是数的个数
    ll ans = 0;
    for(int y=1;y<M;++y){
        for(auto p : primes){
            if(p * y >= M) break;
            int x = p*y;
            dp[x] = max(dp[x],dp[y] + (x-y) * cnt[x]);
        }
        ans = max(ans,dp[y]);
    }
    /*for(int y=1;y<M;++y){
        for(int x=2*y;x<M;x+=y){
            dp[x] = max(dp[x],dp[y] + (x-y) * cnt[x]);
        }
        ans = max(ans,dp[y]);
    }*/
    cout << ans << '\n';
}

signed main(){
    //std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

参考资料

视频讲解

题解1

大佬的D2代码

题解2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值