前言
模拟赛的 T3,寄了,所以写篇题解。
题目分析
简化题意
原题已经简化的不能再简化了,这里直接上原题:
构造一个长度为 N N N 的整数序列 A A A,使得:
- ∀ i , 1 ≤ A i ≤ 1 0 9 \forall i,1\leq A_i\leq 10^9 ∀i,1≤Ai≤109;
- ∀ 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 1≤Zi≤16 改为 1 ≤ Z i ≤ 2 1\leq Z_i\leq 2 1≤Zi≤2,该如何考虑?
因为弱化版的数据范围中,
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 中的每个区间是否满足题目的要求。
考虑具体实现细节:
- 打标记时进行的是区间操作,且标记的数
X
i
X_i
Xi 很小,最大值仅为
16
16
16。所以可以建
16
16
16 个差分数组,第
i
i
i 个差分数组的第
j
j
j 项
d[i][j]
表示 A j A_j Aj 是否要以 i i i 为因子。 - 对于验证
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,j←gcd(dpi,j−1,dpi+2j−1,j−1)。 - 因为题目中要求 ∀ i , 1 ≤ A i ≤ 1 0 9 \forall i,1\leq A_i\leq 10^9 ∀i,1≤Ai≤109,所以未作要求的数字应赋值为 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;
}