这题好强强啊,貌似是集训队原题?集训队原题当中值域是1e9的范围,这样各种乱搞是妥妥的不能过了,只能写正解的离线+树状数组维护前缀积。

  最开始我写了几种乱搞做法,包括莫队和线段树做法。其中表现比较优秀的是线段树的做法,非常的暴力,就是每一个区间都维护vector记录区间lcm的质因数分解结果合并区间归并排序一路维护就可以了。由于51nod数据比较弱,数据值域大小有限,所以还可以跑过大部分的点,但A掉仍然是不能够。

  线段树乱搞:

#include <bits/stdc++.h>
using namespace std;
#define maxn 1000000
#define mod 1000000007
#define LL long long
int n, m, a[maxn];
int tot, rec[maxn], prime[maxn];
bool is_prime[maxn]; int read()
{
int x = , k = ;
char c;
c = getchar();
while(c < '' || c > '') { if(c == '-') k = -; c = getchar(); }
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x * k;
} struct node
{
int num, cnt;
node(int a = , int b = ) { num = a, cnt = b; }
friend bool operator <(const node& a, const node& b)
{ if(a.num != b.num) return a.num < b.num; return a.cnt > b.cnt; }
friend bool operator ==(const node& a, const node& b)
{ return a.num == b.num; }
};
vector <node> T[maxn], S, tem1, tem2; void Get_Prime()
{
for(int i = ; i < maxn; i ++)
{
if(!is_prime[i]) prime[++ tot] = i, rec[i] = i;
for(int j = ; j <= tot; j ++)
{
if(i * prime[j] >= maxn) break;
is_prime[i * prime[j]] = ;
rec[i * prime[j]] = prime[j];
if(!(i % prime[j])) break;
}
}
} LL Qpow(LL x, int times)
{
LL base = ;
for(; times; times >>= , x = (x * x) % mod)
if(times & ) base = (base * x) % mod;
return base;
} void Push(vector <node> &S, node a)
{
if(!S.size() || a.num != S[S.size() - ].num) S.push_back(a);
} void Merge(vector <node> &M1, vector <node> &M2)
{
int i = , j = ;
tem1.resize(), tem2.resize();
for(int i = ; i < M1.size(); i ++) tem1.push_back(M1[i]);
for(int i = ; i < M2.size(); i ++) tem2.push_back(M2[i]);
M1.resize();
while(i < tem1.size() && j < tem2.size())
{
if(tem1[i] < tem2[j]) Push(M1, tem1[i]), i ++;
else Push(M1, tem2[j]), j ++;
}
while(i < tem1.size()) Push(M1, tem1[i]), i ++;
while(j < tem2.size()) Push(M1, tem2[j]), j ++;
} void Build(int p, int l, int r)
{
if(l == r)
{
int tem = a[l], last = -;
while(tem != )
{
if(T[p].size() && rec[tem] == T[p][last].num) T[p][last].cnt ++;
else T[p].push_back(node(rec[tem], )), last ++;
tem /= rec[tem];
}
return;
}
int mid = (l + r) >> ;
Build(p << , l, mid), Build(p << | , mid + , r);
T[p].resize();
for(int i = ; i < T[p << ].size(); i ++) T[p].push_back(T[p << ][i]);
Merge(T[p], T[p << | ]);
} void Query(int p, int l, int r, int L, int R)
{
if(L <= l && R >= r)
{
Merge(S, T[p]);
return;
}
if(L > r || R < l) return;
int mid = (l + r) >> ;
Query(p << , l, mid, L, R), Query(p << | , mid + , r, L, R);
} signed main()
{
Get_Prime();
n = read(), m = read();
for(int i = ; i <= n; i ++) a[i] = read();
Build(, , n);
for(int i = ; i <= m; i ++)
{
int L = read(), R = read();
S.resize();
Query(, , n, L, R);
LL ret = ;
for(int i = ; i < S.size(); i ++) ret = (ret * Qpow(S[i].num, S[i].cnt)) % mod;
printf("%lld\n", ret);
}
return ;
}

  现在来介绍一下正解的做法。有一个很妙的转化:lcm为各个数质因数分解之后每一位取max之后相乘,这样不是很好做。但我们对于一个数\(p^{a}\),可以分析发现它对于答案的贡献正好就是\(a\)次。我们可以把它看做是\(p^{1}, p^{2}...p^{a}\)这样\(a\)个不同的数,每个数对于答案均有\(*p\)的贡献(其中一个区间内相同的数只产生一次贡献)。

  那么现在我们的问题转化为了问一个区间中有多少个不同的数并将它们的权值累乘起来。一个区间当中有多少个不同的数,这里有一个常见的转化,即为统计一个区间当中有多少个\(last[i] < l\)的数(\(last[i]\) 表示 \(i\) 这个数字上一次出现的位置,没有出现可以视作 \(0\))。那么我们可以离线 + 树状数组,按照左端点排序依次处理。维护一个指针 \(now\),树状数组中维护的值为所有范围在 \(now --> tot\) 的数字之间 \(last[i] < now\) 的数字权值积。如何维护?只需要在一个数 \(< now\) 被从树状数组中删去的时候向树状数组中加入它的后继元素即可。

#include <bits/stdc++.h>
using namespace std;
#define maxn 50005
#define mod 1000000007
#define LL long long
#define lowbit(i) (i & (-i))
int n, m, tot, a[maxn << ], b[maxn << ];
int C[maxn << ], nxt[maxn << ], last[maxn << ];
int l[maxn], r[maxn], rec[maxn], ans[maxn]; int read()
{
int x = , k = ;
char c;
c = getchar();
while(c < '' || c > '') { if(c == '-') k = -; c = getchar(); }
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x * k;
} struct node
{
int l, r, id;
node(int a = , int b = , int c = ) { l = a, r = b, id = c; }
friend bool operator <(const node& x, const node& y)
{ return x.l < y.l; }
}Q[maxn]; int Qpow(int x, int times)
{
int base = ;
for(; times; times >>= , x = ((LL) x * x) % mod)
if(times & ) base = ((LL) base * x) % mod;
return base;
} void update(int x, int y)
{
for(; x <= tot; x += lowbit(x))
C[x] = (LL) C[x] * y % mod;
} int query(int x)
{
int ret = ;
for(; x; x -= lowbit(x)) ret = (LL) ret * C[x] % mod;
return ret;
} int main()
{
n = read(), m = read();
for(int i = ; i <= n; i ++)
{
int x = read(), t = sqrt(x);
if(x == ) continue;
l[i] = tot + ;
for(int j = ; j <= t; j ++)
{
if(!(x % j))
{
int k = j;
while(!(x % j))
{
x /= j; a[++ tot] = k;
b[tot] = j; k *= j;
}
}
}
if(x > ) ++ tot, a[tot] = b[tot] = x;
r[i] = tot;
}
for(int i = ; i <= tot; i ++) C[i] = ;
for(int i = ; i <= tot; i ++)
{
if(rec[a[i]]) nxt[rec[a[i]]] = i;
else update(i, b[i]);
rec[a[i]] = i;
}
for(int i = ; i <= m; i ++)
{
int x = read(), y = read();
Q[i] = node(l[x], r[y], i);
}
sort(Q + , Q + + m);
for(int i = , j = ; i <= tot; i ++)
{
while(Q[j + ].l == i) ++ j, ans[Q[j].id] = query(Q[j].r);
update(i, Qpow(b[i], mod - ));
if(nxt[i]) update(nxt[i], b[nxt[i]]);
}
for(int i = ; i <= m; i ++)
printf("%d\n", ans[i]);
return ;
}

  其实感觉这么多的题目做下来这个转化还是比较常见的。当一个数\(p\)对于它的\(k\)倍 \ \(k\) 次方产生为 \(k\) 的贡献时,我们就可以考虑将其看做 \(p, 2 * p, 3 * p...\) 或 \(p^{1}, p^{2}, p^{3}...\) 这么些不同的数字分别对它产生了为 \(1\) 的贡献,累加或累乘(贡献为乘积形式时)即可。如 Test 4 的 第一题、对于 \(N!\) 分解质因数等。

  而这个 \(last[i] < l\) 对于处理“在区间中只出现了一次”也是一个很棒也很常见的思路。好题!好好记录一下( • ̀ω•́ )✧

【题解】51nod 1203JZPLCM问题的更多相关文章

  1. 题解 51nod 1597 有限背包计数问题

    题目传送门 题目大意 给出 \(n\),第 \(i\) 个数有 \(i\) 个,问凑出 \(n\) 的方案数. \(n\le 10^5\) 思路 呜呜呜,傻掉了... 首先想到根号分治,分别考虑 \( ...

  2. 2018 noip 备战日志

    我是写给自己看的…… Day1 10.8 今天开始停晚修课了,开始认真备战考试了. 今天晚上效率不错,竟然不会累,应该是平时一直这个时间写作业大脑高度集中, 现在换了编程也一样可以集中到这个状态 一些 ...

  3. 51nod图论题解(4级,5级算法题)

    51nod图论题解(4级,5级算法题) 1805 小树 基准时间限制:1.5 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 她发现她的树的点上都有一个标号(从1到n),这些树都在空 ...

  4. 51nod 1812 树的双直径 题解【树形DP】【贪心】

    老了-稍微麻烦一点的树形DP都想不到了. 题目描述 给定一棵树,边权是整数 \(c_i\) ,找出两条不相交的链(没有公共点),使得链长的乘积最大(链长定义为这条链上所有边的权值之和,如果这条链只有 ...

  5. 51NOD 1773:A国的贸易——题解

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1773 参考1:FWT讲解 https://www.cnblogs.com ...

  6. 51NOD 1934:受限制的排列——题解

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1934 听说会笛卡尔树的人这题都秒了啊…… 参考:https://blog ...

  7. 51NOD 1038:X^A Mod P——题解

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1038 X^A mod P = B,其中P为质数.给出P和A B,求< ...

  8. 51NOD 1353:树——题解

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1353 今天小a在纸上研究树的形态,众所周知的,有芭蕉树,樟树,函树,平衡 ...

  9. 【胡搞的不能AC的题解,暴力搜索一发博弈问题】1995 三子棋 - 51Nod

    1995 三子棋 题目来源: syu校赛 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 原题链接: https://www.51nod.com/onlineJudge/ ...

随机推荐

  1. AOSP 设置编译输出目录

    export OUT_DIR=/media/caoxinyu/TomasYu/out 注意:export OUT_DIR= OUT_DIR 后面直接跟= ,不要有空格.否则报错.

  2. Win10 远程服务器版

    朋友的电脑刚装了1803版的Win10,然后他用KMS_VL_ALL6.9激活了一下,竟然变成了一个奇怪的版本:“远程服务器版”!第一次见这玩意,还真稀罕.帮他研究了一下,发现KMS_VL_ALL在激 ...

  3. Unity AssetBundle工作流

    一.创建AssetBundle 1.在资源的Inspector视图下有一个AssetBundle的UI,第一个选项表示AssetBundle名称,第二个用于设置AssetBundle Variant, ...

  4. C# Builder

    如下: class Program { static void Main(string[] args) { ).BuildB(2.1).BuildUp(); Console.Read(); } } p ...

  5. 容器云技术:容器化微服务,Istio占C位出道

    在精彩的软件容器世界中,当新项目涌现并解决你认为早已解决的问题时,这感觉就像地面在你的脚下不断地移动.在许多情况下,这些问题很久以前被解决,但现在的云原生架构正在推动着更大规模的应用程序部署,这就需要 ...

  6. 创建并运行第一个Django项目

    首先, 添加Django模块: 在CMD命令行输入 python -m django --version 查看Django版本: 创建第一个Django项目: 整个工程的目录结构: mysite目录是 ...

  7. Selenium(Python) ddt数据驱动

    首先, 添加ddt模块: import unittestfrom time import sleep from ddt import ddt, data, unpack# 导入ddt模块from se ...

  8. ORACLE高级部分内容

    1.pl/sql基本语句 DECLARE BEGIN END; / 循环语句 DECLARE I  NUMBER(2):=1; BEGIN WHILE I<100 LOOP I:=I+1; EN ...

  9. python程序设计——函数设计与调用

    一.函数定义与调用 def 函数名([参数列表]): '''注释''' 函数体 # 输出小于n的斐波那契数 >>def fib(n): a,b=1,1 while a < n: pr ...

  10. 【shell 练习5】编写简单的多级菜单

    一.简单的多级菜单 [root@web129 ~]# cat menu.sh #!/bin/bash #shell菜单演示 function menu() { echo -e `date` cat & ...