C. Candy Store
C. Candy Store
The store sells types of candies with numbers from to . One candy of type costs coins. In total, there are candies of type in the store.
You need to pack all available candies in packs, each pack should contain only one type of candies. Formally, for each type of candy you need to choose the integer , denoting the number of type candies in one pack, so that is divided without remainder by .
Then the cost of one pack of candies of type will be equal to . Let's denote this cost by , that is, .
After packaging, packs will be placed on the shelf. Consider the cost of the packs placed on the shelf, in order . Price tags will be used to describe costs of the packs. One price tag can describe the cost of all packs from to inclusive if . Each of the packs from to must be described by at least one price tag. For example, if , to describe all the packs, a price tags will be enough, the first price tag describes the packs , the second: , the third: .
You are given the integers . Your task is to choose integers so that is divisible by for all , and the required number of price tags to describe the values of is the minimum possible.
For a better understanding of the statement, look at the illustration of the first test case of the first test:
Let's repeat the meaning of the notation used in the problem:
— the number of candies of type available in the store.
— the cost of one candy of type .
— the number of candies of type in one pack.
— the cost of one pack of candies of type is expressed by the formula .
Input
Each test contains multiple test cases. The first line contains the number of test cases (). Description of the test cases follows.
The first line of each test case contains a single integer () — the number of types of candies.
Each of the next lines of each test case contains two integers and (, ) — the number of candies and the cost of one candy of type , respectively.
It is guaranteed that the sum of over all test cases does not exceed .
Output
For each test case, output the minimum number of price tags required to describe the costs of all packs of candies in the store.
Example
input
5 4 20 3 6 2 14 5 20 7 3 444 5 2002 10 2020 2 5 7 7 6 5 15 2 10 3 7 7 5 10 1 11 5 5 1 2 2 8 2 6 7 12 12 3 5 3 9 12 9 3 1000000000 10000
output
2 1 3 2 5
Note
In the first test case, you can choose , , , . Then the cost of packs will be equal to . price tags are enough to describe them, the first price tag for and the second price tag for . It can be shown that with any correct choice of , at least of the price tag will be needed to describe all the packs. Also note that this example is illustrated by a picture in the statement.
In the second test case, with , , , the costs of all packs will be equal to . Thus, price tag is enough to describe all the packs. Note that is divisible by for all , which is necessary condition.
In the third test case, it is not difficult to understand that one price tag can be used to describe nd, rd and th packs. And additionally a price tag for pack and pack . Total: price tags.
解题思路
先给出比赛时想到的做法。思路比较暴力,代码也很长。
对于总数量为的糖果,第种糖果的每一小包中允许的数量为的约数。因此容易想到直接分解得到每个的所有约数,然后每个约数都乘上,那么就得到第种糖果允许存在的所有代价。最后从前往后枚举所有糖果并尽可能取合法代价的交集,这部分当时比赛没想到,后面再细说。
首先每个最大能够取到,如果直接暴力分解约数那么时间复杂度就是,肯定会TLE。这个时候我们就退而求次,先筛出内的所有素数,然后对分解质因数,由于在内一个数最多有个约数,因此可以直接通过质因数来暴搜找到的所有约数,那么时间复杂度就降到了级别,其中内的素数个数为个左右,因此计算量大约是级别,时间限制为感觉不会T(?)。
其实按照我现在这种做法在cf上面不会T(2023-03-27),但为了保证严谨性,我特意构造了组极限数据跑了下,然后我成功把自己给hack了。构造的数据很简单,就是和共交替出现次就可以了。其中有个约数,且。
但还是继续说一下做法吧。现在给出了种还可以的分解质因数的方法,接下来就是如何选代价的问题了。假设第种糖果的代价为集合,如果第种糖果想与第种糖果的代价一样,那么很明显能选的代价为两个集合与的交集。以此类推,如果第种糖果到第种糖果的代价都相同,那么就要满足。
因此我们可以从前往后枚举,对于第种糖果每次求与的交集(此时的是到的交集),如果得到的结果不为空,那么说明第种糖果可以有相同的代价。如果直接暴力求两个集合的交集那么时间复杂度是,一共要求个集合的交集,必定会超时。因此在求交集的部分需要优化。
注意到对于必定满足且,因此我们只需要枚举的每一个元素,如果满足这两个条件那么我们就保留,时间复杂度就降到了。可以发现甚至第种糖果的代价都不需要求了(即不需要对分解约数)。而如果在这个过程中没有选到任何元素,即交集为空,我们才需要求第种糖果的代价。
TLE代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 2e5 + 10; 7 8 int primes[N], cnt; 9 bool vis[N]; 10 int a[N], b[N]; 11 vector<vector<int>> fs; 12 vector<LL> c[N]; 13 14 void get_prime(int n) { 15 for (int i = 2; i <= n; i++) { 16 if (!vis[i]) primes[cnt++] = i; 17 for (int j = 0; primes[j] <= n / i; j++) { 18 vis[primes[j] * i] = true; 19 if (i % primes[j] == 0) break; 20 } 21 } 22 } 23 24 void dfs(int k, int u, int prod) { 25 if (u == fs.size()) { // 找到约数prod 26 c[k].push_back(1ll * prod * b[k]); // 记录第k种糖果所有可能的代价 27 return; 28 } 29 int t = 1, p = fs[u][0], s = fs[u][1]; 30 for (int i = 0; i <= s; i++) { 31 dfs(k, u + 1, prod * t); 32 t *= p; 33 } 34 } 35 36 void get(int k) { 37 fs.clear(); 38 int t = a[k]; 39 for (int i = 0; primes[i] <= t / primes[i]; i++) { // 质因数分解 40 int p = primes[i]; 41 if (t % p == 0) { 42 int s = 0; 43 while (t % p == 0) { 44 t /= p; 45 s++; 46 } 47 fs.push_back({p, s}); 48 } 49 } 50 if (t > 1) fs.push_back({t, 1}); 51 dfs(k, 0, 1); // 暴搜出a[k]的所有约数 52 } 53 54 void solve() { 55 int n; 56 scanf("%d", &n); 57 for (int i = 0; i < n; i++) { 58 scanf("%d %d", a + i, b + i); 59 c[i].clear(); 60 } 61 get(0); // 求第0种糖果的所有可能代价 62 int ret = 1; // 答案至少是1 63 for (int i = 1; i < n; i++) { 64 for (auto &x : c[i - 1]) { // 求交集 65 if (x % b[i] == 0 && a[i] % (x / b[i]) == 0) c[i].push_back(x); 66 } 67 if (c[i].empty()) { // 交集为空 68 ret++; // 需要的代价种类+1 69 get(i); // 求第i种糖果的所有可能代价 70 } 71 } 72 printf("%d\n", ret); 73 } 74 75 int main() { 76 get_prime(N - 1); // 筛出sqrt(1e9)内的质数,用于质因数分解 77 int t; 78 scanf("%d", &t); 79 while (t--) { 80 solve(); 81 } 82 83 return 0; 84 }
下面给出正解,是真想不到。
假设第种糖果到第种糖果的代价一样,令,即有,其中。
那么可以发现对于,是的一个因子,即是的倍数,因此必然有。
同时由于,因此有,即是的一个因子,这就等价于。
因此如果有,那么必然有,反过来也成立。
然后从前往后枚举,维护连续序列的最大公约数和最小公倍数,贪心地选择,即如果满足则使用同一个代价,而如果则说明需要开另外一个代价。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 LL gcd(LL a, LL b) { 7 return b ? gcd(b, a % b) : a; 8 } 9 10 LL lcm(LL a, LL b) { 11 return a / gcd(a, b) * b; 12 } 13 14 void solve() { 15 int n; 16 scanf("%d", &n); 17 LL d = 0, m = 1; 18 int ret = 1; 19 while (n--) { 20 LL a, b; 21 scanf("%lld %lld", &a, &b); 22 m = lcm(m, b); 23 d = gcd(d, a * b); 24 if (d % m) { 25 ret++; 26 d = a * b, m = b; 27 } 28 } 29 printf("%d\n", ret); 30 } 31 32 int main() { 33 int t; 34 scanf("%d", &t); 35 while (t--) { 36 solve(); 37 } 38 39 return 0; 40 }
参考资料
Codeforces Round 860 (Div. 2) A~E:https://zhuanlan.zhihu.com/p/617245703
Editorial of Codeforces Round 860 (Div. 2):https://codeforces.com/blog/entry/114208
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17263049.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-03-27 图中的环
2022-03-27 合适数对