Codeforces Round #738 (Div. 2) E

8 篇文章 0 订阅
5 篇文章 0 订阅

首先可以想到 题目如果通过正面来求的话 那么所需要求的种类很复杂 所以很容易想到容斥的思想 我们就可以想到 将所有组建情况再减去 gcd不是 1 的情况即可
1.组建所有情况 就可以考虑 dp 前 i个数 组成 m 的种类有多少 很明显 是个背包 但是 复杂度为 nm * (ri - li)所以超时 考虑优化 因为 ri ~ li是连续的 所以可以使用前缀和优化至 2nm
2.gcd不是1的情况 那么就可以变成 gcd 为2 为 3.。。。。为 m的情况 考虑2的时候 我们可以把所有2的倍数往上放 3的时候 所有3的倍数往上放 但是又有个问题 2会产生 6的 3也会产生 6的 所以又要减去多余的 那么就继续利用 dp 考虑 第 i 个需要减去 或者 加上多少 由于 第 i个数 会对所以 i的倍数产生 影响所以 只需要 nsqrtn 去往前找因子的影响即可
3.如何实现 减去 和1.是同一思想 我们可以想到 i的倍数 组成的数 一定是 i的倍数 所以无论如何 产生的数 的个数 一定是调和级数个的 那就是 nlogn的复杂 再 50 就是 nlogn50 大约是 7e7 加上常数和mod 和剪汁 刚好能跑过去

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#include<map>

using namespace std;

const int N = 55,M = 1e5 + 10,mod = 998244353;
typedef pair<int,int> PII;
typedef long long ll;

int n,m;

int flag[M],cnt,prime[M],is[M];
int l[N],r[N];
void is_prime(){
    for(int i = 2; i < M; i++){
        if(!flag[i]){
            flag[i] = 1;
            prime[++cnt] = i;
        }
        for(int j = 1; i * prime[j] < M; j++){
            flag[i * prime[j]] = 1;
            if(i % prime[j] == 0){
                break;
            }
        }
    }
}

ll dp[N][M],Pref[M];
ll dp2[N][M],pref[M];

ll mos(ll a){
    a %= mod;
    a += mod;
    a %= mod;
    return a;
}

int main(){
    is_prime();
    cin >> n >> m;

    for(int i = 1; i <= n; i++){
        scanf("%d%d",&l[i],&r[i]);
    }

    for(int i = 0; i <= m; i++){
        Pref[i] = 1;
    }

    ll sum = 0;
    for(int i = 1; i <= n; i++){
        for(int j = l[i]; j <= m; j++){
            if(j > r[i]){
                dp[i][j] += Pref[j - l[i]] - Pref[j - r[i] - 1];
            }else dp[i][j] += Pref[j - l[i]];
            dp[i][j] %= mod;
        }
        Pref[0] = dp[i][0];
        for(int j = 1; j <= m; j++){
            Pref[j] = Pref[j - 1] + dp[i][j];
            Pref[j] %= mod;
        }
    }

    sum = Pref[m];
    
     for(int j = 2; j <= m; j++){
        int dd = 0;
        for(int i = 2; i * i <= j; i++){
            if(j % i == 0 && j != i * i){
                dd += is[i];
                dd += is[j / i];
            }
            if(j == i * i) dd += is[i];
        }
        is[j] = -1 - dd;
    }



    for(int j = 2; j <= m; j++){
        if(is[j] == 0) continue;
        for(int i = 0; i <= m / j; i++) pref[i] = 1;
        for(int i = 1; i <= n; i++){
            int d = (l[i] + j - 1) / j * j;
            if(d > r[i]){
                pref[m / j] = 0;
                break;
            }
            for(int k = d; k <= m; k += j){
                if(r[i] / j >= k / j){
                    dp2[i][k] += pref[k / j - d / j];
                }else dp2[i][k] += mos(pref[k / j - d / j] - pref[k / j - r[i] / j - 1]);
                dp2[i][k] %= mod;
            }
            pref[0] = dp2[i][0];
            for(int k = 1; k <= m / j; k++){
                pref[k] = pref[k - 1] + dp2[i][k * j];
                pref[k] %= mod;
            }
        }

        //47 50
        sum += is[j] * pref[m / j];

        sum %= mod;

        for(int i = 1; i <= n; i++){
            for(int k = (l[i] + j - 1) / j * j; k <= m; k += j){
                dp2[i][k] = 0;
            }
        }
    }


    cout << mos(sum) << endl;




	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值