洛谷 P9027 [CCC2021 S5] Math Homework 题解

前言

模拟赛的 T3,寄了,所以写篇题解。

题目分析

简化题意

原题已经简化的不能再简化了,这里直接上原题:

构造一个长度为 N N N 的整数序列 A A A,使得:

  1. ∀ i , 1 ≤ A i ≤ 1 0 9 \forall i,1\leq A_i\leq 10^9 i,1Ai109
  2. ∀ i , gcd ⁡ ( A X i , A X i + 1 , ⋯   , A Y i ) = Z i \forall i,\gcd(A_{X_i},A_{X_i+1},\cdots,A_{Y_i})=Z_i i,gcd(AXi,AXi+1,,AYi)=Zi

或者报告无解。

思路

考虑弱化:若将题目中的数据范围 1 ≤ Z i ≤ 16 1\leq Z_i\leq 16 1Zi16 改为 1 ≤ Z i ≤ 2 1\leq Z_i\leq 2 1Zi2,该如何考虑?

因为弱化版的数据范围中, Z i Z_i Zi 的取值只有可能是 1 1 1 2 2 2,所以可以优先考虑值为 2 2 2 Z i Z_i Zi,将其范围内的 A A A 全部赋值为 2 2 2 或者任意偶数,再考虑值为 1 1 1 Z i Z_i Zi,看其范围内有没有空出的(即值不为 2 2 2 的)地方可以放 1 1 1,若有,随便找一个地方放了,若没有则输出 Impossible。下面是弱化版的部分代码:

    bool flagim = false;
    sort(a + 1, a + 1 + m, cmpsub1);
    for(int i = 1;i <= m;i++){
        if(a[i].x == 2){
            for(int j = a[i].l;j <= a[i].r;j++){
                b[j] = 2;
            }
        }
        else{
            bool flag = false;
            for(int j = a[i].l;j <= a[i].r;j++){
                if(b[j] == 1){
                    flag = true;
                    break;
                }
            }
            if(!flag){
                flag = false;
                for(int j = a[i].l;j <= a[i].r;j++){
                    if(b[j] == 0){
                        flag = true;
                        b[j] = 1;
                        break;
                    }
                }
                if(!flag) flagim = true;
            }
        }
    }
    if(flagim){
        printf("Impossible\n");
        return 0;
    }
    for(int i = 1;i <= n;i++){
        printf("%d ", b[i] ? b[i] : 1);
    }
    printf("\n");

现在回到本题,本题与弱化版的唯一区别在于其 Z i Z_i Zi 的取值范围更大了。受弱化版的启示,我们可以想出本题的正解。

对于题目给定的每一个区间,标记此区间内必有的因子,标记完后, A i A_i Ai 的值就是它被标记的数字的最小公倍数的倍数(为了方便起见,在这里我们令它为最小公倍数的 1 1 1 倍,即它的最小公倍数本身)。对于无解的情况,可以在构造 A A A 后再次检验 A A A 中的每个区间是否满足题目的要求。

考虑具体实现细节:

  1. 打标记时进行的是区间操作,且标记的数 X i X_i Xi 很小,最大值仅为 16 16 16。所以可以建 16 16 16 个差分数组,第 i i i 个差分数组的第 j j jd[i][j] 表示 A j A_j Aj 是否要以 i i i 为因子。
  2. 对于验证 A A A 是否满足要求,易发现不可以直接暴力遍历求最小公倍数,需要使用线段树、ST 表等算法(数据结构)求解。这里我是用的是 ST 表,dp[i][j] 表示从 A i A_i Ai 开始的 2 j 2^j 2j 个数的 gcd,转移方程是 d p i , j ← gcd ⁡ ( d p i , j − 1 , d p i + 2 j − 1 , j − 1 ) dp_{i, j} \gets \gcd(dp_{i, j - 1}, dp_{i + 2^{j - 1}, j - 1}) dpi,jgcd(dpi,j1,dpi+2j1,j1)
  3. 因为题目中要求 ∀ i , 1 ≤ A i ≤ 1 0 9 \forall i,1\leq A_i\leq 10^9 i,1Ai109,所以未作要求的数字应赋值为 1 1 1 1 0 9 10^9 109 内的正整数。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int NR = 1.5e5;
const int MR = 16;
const int KR = 19;
struct Node{
    // 输入的构造要求
    int l, r, x;
}b[NR + 10];
int d[MR + 10][NR + 10]; // 差分数组
int dp[NR + 10][KR + 10]; // ST 表
int lcm(int x, int y){
    // 求最小公倍数的函数
    return (x * y) / __gcd(x, y);
}
int cal(int l, int r){
    // ST 表计算函数
    int k = log2(r - l + 1);
    return __gcd(dp[l][k], dp[r - (1 << k) + 1][k]); // 使用 c++ 自带的 gcd 函数
}

int main(){
    // freopen("math.in", "r", stdin);
    // freopen("math.out", "w", stdout);
    // 读入
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1;i <= m;i++){
        scanf("%d%d%d", &b[i].l, &b[i].r, &b[i].x);
        // 差分标记
        d[b[i].x][b[i].l]++;
        d[b[i].x][b[i].r + 1]--;
    }
    // 计算差分数组的前缀和
    for(int i = 1;i <= MR;i++){
        for(int j = 1;j <= n;j++){
            d[i][j] += d[i][j - 1];
        }
    }
    // 计算 A[i] 的值, 这里为节省空间未创建 A 数组, 直接用 dp[i][0] 表示 A[i]
    for(int i = 1;i <= n;i++){
        dp[i][0] = 1; // 未作要求的数字应赋值为 1 到 1e9 内的正整数。
        for(int j = 1;j <= MR;j++){
            // A[i] 的值就是它被标记的数字的最小公倍数的倍数(为了方便起见, 在这里我们令它为最小公倍数的 1 倍, 即它的最小公倍数本身)
            if(d[j][i]) dp[i][0] = lcm(dp[i][0], j);
        }
    }
    // ST 表初始化
    for(int j = 1;j <= log2(n) + 1;j++){
        for(int i = 1;i <= n;i++){
            dp[i][j] = __gcd(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
        }
    }
    // 判断 A 是否满足要求
    for(int i = 1;i <= m;i++){
        if(cal(b[i].l, b[i].r) != b[i].x){
            // 不满足要求, 直接输出后结束程序
            printf("Impossible\n");
            return 0;
        }
    }
    // 满足要求,输出 A 数组
    for(int i = 1;i <= n;i++){
        printf("%d ", dp[i][0]);
    }
    printf("\n");
    return 0;
}
题目描述似乎缺失了关键信息,通常我会需要了解“P10780 食物”是什么具体的算法竞赛题目,它来自在线平台洛谷(Luogu),以及该题目的大致背景、条件和目标。洛谷食物(Food)可能是某种数据结构或算法问题,比如贪吃蛇、分配任务等。 然而,我可以给你提供一个通用的模板: **[洛谷 P10780 食物 - 题目解析]** 题目名称:P10780 食物(假设是关于食物分配或者饥饿游戏的问题) 链接:[插入实际题目链接] **背景:** 此题通常涉及动态规划或者搜索策略。场景可能是有n个参与者(选手或角色),每个都有特定的食物需求或者优先级,我们需要在有限的食物资源下合理分配。 **分析:** 1. **输入理解**:首先读入n个参与者的信息,包括每个人的需求量或优先级。 2. **状态定义**:可以定义dp[i][j]表示前i个人分配完成后剩余的食物能满足第j个人的最大程度。 3. **状态转移**:递推式可能涉及到选择当前人分配最多食物的版本,然后更新剩余的食物数。 4. **边界条件**:如果剩余食物不足以满足某人的需求,则考虑无法分配给他;如果没有食物,状态值设为0。 5. **优化策略**:可能需要对状态数组进行滚动更新,以减少空间复杂度。 **代码示例(伪代码或部分关键代码片段):** ```python # 假设函数分配_food(demand, remaining)计算分配给一个人后剩余的食物 def solve(foods): dp = [[0 for _ in range(max_demand + 1)] for _ in range(n)] dp = foods[:] # 从第一个到最后一个参与者处理 for i in range(1, n): for j in range(1, max_demand + 1): if dp[i-1][j] > 0: dp[i][j] = max(dp[i][j], dp[i-1][j] - foods[i]) dp[i][j] = max(dp[i][j], distribute_food_to(i, dp[i-1][j])) return dp[n-1][max_demand] ``` **相关问题--:** 1. 这道题是如何运用动态规划的? 2. 如果有优先级限制,应该如何调整代码? 3. 怎样设计搜索策略来解决类似问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值