首页 > 其他分享 >CF1946F Nobody is needed 题解

CF1946F Nobody is needed 题解

时间:2024-08-03 15:39:10浏览次数:12  
标签:10 int 题解 sum Nobody times needed leqslant dp

Nobody is needed

推销我的洛谷博客。

题意

多组数据。

给定一个长度为 \(n\) 的排列 \(a\),你需要回答 \(q\) 组询问,每组询问给出 \(l,r\),求有多少个子序列 \(t\) 使得:

  • \(l \leqslant t_1 < t_2 < \cdots < t_k \leqslant r\)。
  • \(a_{t_i} | a_{t_{i + 1}}(1 \leqslant i < k)\)。

数据范围

  • \(1 \leqslant n,q \leqslant 10^6\)。
  • \(a\) 是一个排列
  • \(1 \leqslant \sum n,\sum q \leqslant 10^6\)。

思路

考虑使用 dp,令 \(dp_i\) 表示以 \(a_i\) 结尾的合法子序列个数,拓扑序为 \(i\) 从小到大。

那么转移呢,很显然若是写收集形则 \(dp_i\) 可以从所有 \(a_i\) 的约数处转移过来,总复杂度是 \(O(\sum\limits_{1 \leqslant i \leqslant n}\sqrt{i})\) 的,可以用代码计算一下,当 \(n=10^6\) 时,总共需要大约 \(6 \times 10^8\),不太能接受。

若是写扩散形,又不便于确定对于每组询问的答案。

可以考虑将拓扑序转为 \(i\) 从大到小,那么对于 \(i\),会发生修改的 \(dp_j\) 只有那些 \(i \leqslant j\) 且 \(a_i | a_j\),也就是说只有 \(\frac{n}{a_i}\) 处的 dp 需要修改,总复杂度为 \(O(\sum\limits_{1\leqslant i \leqslant n} \frac{n}{i})\),很典型的调和级数 \(O(n \log n)\),可以接受。

那么考虑怎么更新 dp 数组,正如上文所说,每次发生修改的只有 \(a_i\) 的倍数,而修改的贡献可以分为 \(a_i\) 对 \(a_i \times x(x \geqslant 2)\) 的贡献和 \(a_i \times x(x \geqslant 2)\) 对 \(a_i \times x \times y (x,y \geqslant 2)\) 的贡献,但如果 \(a_i\) 所对应的下标(即 \(i\))在 \(a_i \times x\) 的下标后面,则无法产生贡献,\(a_i \times x\) 对 \(a_i \times x \times y\) 同理。

为了保证查询时答案没有超出 \([l,r]\) 的范围,需要在每次更新完 dp 数组后处理那些 \(l = i\) 的查询,答案就是 \(\sum\limits_{i\leqslant j \leqslant r} dp_j\),为了快速求出答案,使用树状数组维护 dp 数组的和。

详细见代码。

复杂度

  • 时间:\(O(q \log n + n \log^2 n)\)。
  • 空间:\(O(n + q)\)。

Code

点击查看代码
#include <iostream>
#include <vector>
#define _1 (__int128)1

using namespace std;
using ll = long long;

const int N = 1e6 + 10;

inline int lowbit (int x) {
  return x & -x;
}

int T, n, q, a[N], b[N];
ll ans[N], tr[N], dp[N];
vector<pair<int, int>> qry[N];

void modify (int x, int y) {
  for (int i = x; i <= n; i += lowbit(i)) tr[i] += y;
}

ll Query (int x) {
  ll ret = 0;
  for (int i = x; i; i -= lowbit(i)) ret += tr[i];
  return ret;
}

void Solve () {
  cin >> n >> q;
  for (int i = 1; i <= n; i++)
    cin >> a[i], b[a[i]] = i, tr[i] = 0, qry[i] = vector<pair<int, int>> (); // 多测要清空,b[i] 用于记录 i 在 a 中对应的下标
  for (int i = 1, l, r; i <= q; i++)
    cin >> l >> r, qry[l].push_back({r, i}); // 离线处理询问
  for (int i = n; i; i--) {
    dp[a[i]] = 1; // 初始状态
    for (int j = a[i]; j <= n; j += a[i]) 
      if (b[j] >= b[a[i]]) // 将两种转移合并可以这样写
        for (int k = j * 2; k <= n; k += j) // 注意是 k += j 而不是 k += a[i]
          if (b[k] > b[j])
            dp[k] += dp[j];
    for (int j = a[i]; j <= n; j += a[i])
      modify(b[j], dp[j]), dp[j] = 0; // 用树状数组维护 dp 和,dp 要清空
    for (auto [j, id] : qry[i])
      ans[id] = Query(j) - Query(i - 1); // 差分处理答案
  }
  for (int i = 1; i <= q; i++)
    cout << ans[i] << ' ';
}

int main () {
  ios::sync_with_stdio(0), cin.tie(0);
  // Solve();
  for (cin >> T; T--; Solve(), cout << '\n') {}
  return 0;
}

标签:10,int,题解,sum,Nobody,times,needed,leqslant,dp
From: https://www.cnblogs.com/wnsyou-blog/p/18340587/CF1946F_solution

相关文章

  • NSSCTF Web 题解 Write up
    NSSCTFWeb题解Writeup一、Do_you_know_http1、开题2、分析页面显示请使用“WLLM”浏览器,我没听说过“WLLM”浏览器,那首先去User-Agent修改访问的浏览器。用HackBar分析,将UA的值改成WLLM。用EXECUTE请求页面显示你只可以在本地正常阅读,并给出了ip。那简单,还是用HackB......
  • AGC013B 题解
    注意到只要随便dfs,如果没有可以走的点,说明这个端点满足要求。因为有两个端点,所以从同一个点开始搜两次,拼在一起就行了。#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;constintN=1e5+5;vector<int>e[N];intn,m;boolvis[N];voiddfs(in......
  • AGC009B 题解
    注意到如果把每一对胜者败者连边,可以得到一颗树:(例子)但是因为胜者每次只能和一个败者打,所以要用类似多叉转二叉的方法,让有不止一个孩子的节点变成有一个孩子和一个虚点。如图,原来\(1\)有三个儿子\(2,3,4\),通过转换,变成了上图。上图可以直接变成对战图(\(x2\tox1\to1)\):(原......
  • AGC049A 题解
    弱化版:CF280CGameonTree(有向图的限制变成一棵根节点为1的外向树)弱化版解法:根据期望线性性,\(Ans=\sum_{i=1}^nE(p_i)\)。其中\(p_i\)是\(i\)被选到的概率。因为对于\(i\)和\(i\)的祖先节点,某个点在这些店里是第一个备选的概率相同。所以\(p_i=\dfrac{1}{dep_i}\)......
  • joisc2017 D3T1 长途巴士 题解
    joisc2017D3T1长途巴士题解这是学校ACM欢乐赛的题,赛时想到做法了,但是因为我各种唐诗没写出来翻了转化题面我们发现,只可以踢掉检查站前面一个连续段的接水人,并且不能踢掉司机,考虑画出对整个路程表示的线段上图几个小点是检查点,考虑在每个检查点之前踢掉一段的人,容易发......
  • ABC269F 题解
    注意到第\(i\)行和第\(i+2\)行被删除的格子的排列顺序相同,格子上的数差了\(2m\)。于是处理出第\(i,i+1\)行的答案\(a_i,a_{i+1}\),有值的格子的个数\(c_i,c_{i+1}\)。令\(s(i)=\dfrac{(i-1)i}2\),也就是\(\sum_{j=1}^{i-1}j\)。第\(i,i+2,i+4\cdots\)行的和:\(a_i\t......
  • ABC268F 题解
    考虑贪心。设字符串\(S\)里数字之和为\(S_d\),X的个数为\(S_c\)。考虑相邻的两个字符串\(A,B\)的贡献:考虑临项交换,这只影响到相邻两个串的相互贡献。注意到交换\(A,B\)只会影响到\(B_dA_c,A_dB_c\),那么产生的贡献\(\Delta=B_dA_c-A_dB_c\)。因为对于最优解,\(\Delta......
  • ACM第三次测试部分题解(B,C,D,E,J)
    (B)N^N(简单快速幂模板,直接套用就行)点击查看代码#include<iostream>usingnamespacestd;longlonga,b,n;intmain(){cin>>n;while(n--){scanf("%lld",&a);signedlonglongA=a%10,sum=1;while(a)......
  • AGC039B 题解
    因为一条边只能在\(V_i,V_i+1\)之间,如果把\(V_1,V_3,\cdots\)看作一部分,\(V_2,V_4,\cdots\)看作一部分,这就是个二分图。考虑一个二分图怎么“展开”成最多的集合。考虑答案上界。上界是点对最短路的最大值加一。如果集合个数超过上界,那么一定存在一条边跨越多个集合。猜......
  • AGC033C 题解
    这里树的直径\(l\)定义为两个有硬币的点的最长距离。做一次操作后,树的直径一定会变短。我们发现,如果在直径端点做操作,直径减一。在非直径端点操作,直径减二。于是变成了一个威佐夫博弈,但是要注意的是,在直径长度为0,1,2的时候,每次都只能让直径减一。因为直径长度为1,先手必......