【2024.8.1】Traveller解题报告——根号分治

写在前面

作为一名ACM新人,我最近正在研究根号分治算法。与其说是算法,不如说是一种思想,像dp一样。根号分治的思想就是把数据范围分成大于根号的部分和小于根号的部分,在这两个部分中分别运用不同的算法去解决相应的问题。

洛谷 P3396 哈希冲突

题目大意

给你一个长度为 n n n的正整数序列和 m m m次操作,对于每次操作,按照如下格式输入,你需要回答如下问题:

  • A A A x x x y y y:统计下标模 x x x y y y的元素总和,即 a y + a x + y + a 2 x + y + ⋯ a_y+a_{x+y}+a_{2x+y}+\cdots ay+ax+y+a2x+y+
  • C C C x x x y y y:令 a x = y a_x=y ax=y

数据范围: 1 ≤ n , m ≤ 1.5 × 1 0 5 1\le n,m \le1.5\times 10^5 1n,m1.5×105 1 ≤ a i ≤ 1 0 3 1 \le a_i \le 10^3 1ai103,且对于操作 A A A,保证 1 ≤ y < x ≤ n 1\le y < x \le n 1y<xn

样例参见题目链接。

解题思路

这道题是根号分治的经典例题。先考虑操作A,我们观察可以发现,对于每一个 x x x,直接计算一遍答案的时间复杂度为 O ( n x ) O(\frac{n}{x}) O(xn) x x x越大时,需要计算的次数就越少,同时,对于每一个 x x x,我们都可以预处理出结果,预处理所需的时间复杂度为 O ( n ) O(n) O(n)。因此,我们考虑将以上两种方法结合起来,并且取一个平衡点 n \sqrt{n} n

x ≤ n x \le \sqrt{n} xn 时,采用预处理的方法,将所有答案预处理出来,时间复杂度为 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23),这样询问的复杂度就为 O ( 1 ) O(1) O(1)。而当 x ≥ n x \ge \sqrt{n} xn 时,采用直接计算的方法,计算一次的最坏时间复杂度为 O ( n ) O(\sqrt{n}) O(n )至于 x = n x=\sqrt{n} x=n 的时候,可以随意归为任何一类,无需特殊考虑

综上,操作A的时间复杂度为 O ( n ( n + m ) ) O(\sqrt{n}(n+m)) O(n (n+m)),可以通过此题。

而修改操作非常容易:对于每一次修改,我们需要先计算出修改前和修改后的差值,用于更新前面预处理的部分,然后再将原数组中的数更改,时间复杂度为 O ( n ) O(\sqrt{n}) O(n )

AC代码

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define pb push_back

#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define ff first
#define ss second
#define fori(x,y) for(int i=x;i<=(int)(y);++i)
#define forj(x,y) for(int j=x;j<=(int)(y);++j)
#define fork(x,y) for(int k=x;k<=(int)(y);++k)

#define debug(x) cout << #x << " = " << x << endl

typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

ll MOD = 998244353;
ll qpow(ll a,ll p) {ll res=1; while(p) {if (p&1) {res=res*a%MOD;} a=a*a%MOD; p>>=1;} return res;}

const int N = 2e5+7;
const int M = 505;  // 用来开根号分治的数组大小,一般略大于sqrt(n)
int arr[N];  // 用于储存原数组
int f[M][M];  // 用于存储根号分治预处理的数组
void solve() {
	int n, m;
    cin >> n >> m;
    fori(1, n) cin >> arr[i];

    const int G = sqrt(n);  // 根号分治的根号大小

    fori(1, G) forj(1, n) f[i][j%i] += arr[j];  // 预处理根号分治的数组

    while (m--) {
        string op;
        int x, y;
        cin >> op;
        cin >> x >> y;
        if (op[0] == 'A') {
            if (x<=G) cout << f[x][y] << endl;  // 小于根号的部分,直接输出预处理的值
            else {  // 大于根号分治的部分,直接暴力求解
                int sum = 0;
                for (int i=y;i<=n;i+=x) sum += arr[i];
                cout << sum << endl;
            }
        }
        else {
            int ch = y-arr[x];  // 记录修改的大小
            arr[x] = y;
            fori(1, G) f[i][x%i] += ch;  // 在根号分治的预处理结果中修改
        }
    }
}

signed main() {
	IOS;
	int t = 1;
	while (t--) {
		solve();
	}
	return 0;
}

Codeforces 1997E Level Up

题目大意

你在进行一场打怪游戏,你的初始等级为1,你将面对 n n n个怪物,每个怪物都有一个等级 a i a_i ai,每当你打死了 k k k个怪物,你的等级就会 + 1 +1 +1,但对于每一个怪物,如果你当前的等级严格大于该怪物的等级,那么这只怪物会逃跑,你将无法获取到打死这个怪物的经验值(即不能将这个怪物算作你打死的怪物)。也就是说,只有等级大于等于你目前的等级的怪物会被计算在内

q q q个询问,每次询问会给出 i   x i \ x i x,代表 k = x k=x k=x时,你是否会打第 i i i个怪物。如果会打,输出YES;如果怪物逃跑了,输出NO。

数据范围: 1 ≤ n , q ≤ 2 × 1 0 5 1 \le n,q \le 2\times 10^5 1n,q2×105 1 ≤ a i ≤ 2 × 1 0 5 1 \le a_i \le 2\times 10^5 1ai2×105 1 ≤ i , x ≤ n 1 \le i,x \le n 1i,xn

解题思路

这道题我在赛时没有来得及写完,因为前面的BC想复杂了想了好久,D还WA了一发,这一场直接怒掉了40多分(还是我太菜了)。后面才把这道题补了,其实也是非常经典的根号分治思想:首先离线,考虑 x ≤ n x\le \sqrt{n} xn 时,将答案直接预计算出来,复杂度为 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23)

x ≥ n x\ge \sqrt{n} xn 时,会稍微复杂一些,此时发现可能升级到的最大等级仅为 ⌊ n x ⌋ + 1 ≤ n + 1 \lfloor \frac{n}{x} \rfloor+1 \le \sqrt{n}+1 xn+1n +1,因此考虑预处理出所有大于等于 n + 1 \sqrt{n}+1 n +1的怪物数量前缀和,记录在一个二维数组当中,然后对于每个 x x x利用二分查找答案。预处理时间复杂度为 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23),查找答案的时间复杂度为 O ( n 3 2 log ⁡ n ) O(n^{\frac{3}{2}} \log n) O(n23logn)

总时间复杂度为 O ( n 3 2 log ⁡ n ) O(n^{\frac{3}{2}} \log n) O(n23logn),空间复杂度为 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23)

可能还有更快速且简便的做法,各位可以去参考其他dalao的解题思路。

AC代码

我的离线存储答案机制写的太垃圾了,差点导致MLE(最后1500ms,490000KB也是卡了个极限过去了)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define pb push_back

#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define ff first
#define ss second
#define fori(x,y) for(int i=x;i<=(int)(y);++i)
#define forj(x,y) for(int j=x;j<=(int)(y);++j)
#define fork(x,y) for(int k=x;k<=(int)(y);++k)

#define debug(x) cout << #x << " = " << x << endl

typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

ll MOD = 998244353;
ll qpow(ll a,ll p) {ll res=1; while(p) {if (p&1) {res=res*a%MOD;} a=a*a%MOD; p>>=1;} return res;}

const int N = 2e5+7;
const int M = 605;

int arr[N];
int f[M][N];  // 分块后
vector<pii> que;
map<int,bool> ans[N];
void solve() {
	int n, m;
    cin >> n >> m;
    fori(1, n) cin >> arr[i];
    fori(1, m) {
        int a,b;
        cin >> a >> b;
        que.push_back({a, b});
        ans[b][a];
    }

    int G = sqrt(n);  // 分块大小
    forj(1, G+1) {
        int lv = 1;
        int cnt = 0;
        fori(1, n) {  // 预处理出小于sqrt(n)的答案
            if (arr[i]<lv) {
                if (ans[j].find(i) != ans[j].end()) ans[j][i] = false;
            }
            else {
                cnt++;
                if (ans[j].find(i) != ans[j].end()) ans[j][i] = true;
                if (cnt>=j) {
                    cnt = 0;
                    lv++;
                }
            }
        }  // 预处理出级别<=sqrt(n)的怪物数量
        fori(1, n) f[j][i] = f[j][i-1]+(arr[i]>=j);
    }

    fork(G+1, n) {
        vector<int> pt;
        int lv = 1;
        int now = 0;
        while (1) {
            int nxt = lower_bound(f[lv]+1, f[lv]+n+1, f[lv][now]+k)-f[lv];  // 下一个升级点的位置
            if (nxt>n) break;
            pt.push_back(nxt);
            now = nxt;
            lv++;
        }

        for (auto pi: ans[k]) {
            int lv = lower_bound(pt.begin(), pt.end(), pi.first)-pt.begin()+1;  // 处理答案
            if (arr[pi.first]<lv) ans[k][pi.first] = false;
            else ans[k][pi.first] = true;
        }
    }

    // ans
    fori(0, m-1) {
        int a = que[i].first, b = que[i].second;
        if (ans[b][a]) YES;
        else NO;
    }
}

signed main() {
	IOS;
	int t = 1;
	while (t--) {
		solve();
	}
	return 0;
}

未完待续

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CAN长字节DM1报文是指在CAN总线上传输的长度超过8个字节的DM1报文。根据引用\[1\],当要传输的数据长度超过8个字节时,首先使用TPCM进行广播,广播内容包含即将传输报文的PGN、总的数据包长度等信息,然后使用TP.DT进行数据传输。相邻两个TP.DT之间的时间间隔是50ms到200ms。根据引用\[2\],当字节数大于8时,将会使用多帧传输参数组。根据引用\[3\],DM1报文是Diagnostic Message 1, Active Diagnostic Trouble Codes的缩写,用于点亮故障指示灯、红色停机灯等,并周期性播报控制器中处于激活状态的故障码。DM1报文的格式包括各个字节的定义,如故障指示灯、红色停机灯、琥珀色警告指示灯等。因此,CAN长字节DM1报文是指在CAN总线上传输的长度超过8个字节的DM1报文,用于传输更多的故障码信息。 #### 引用[.reference_title] - *1* [车载通信——J1939 DM1](https://blog.csdn.net/weixin_64064747/article/details/130193432)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [J1939广播DM1报文](https://blog.csdn.net/mengdeguodu_/article/details/108173263)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [J1939商用车在线诊断DM1报文](https://blog.csdn.net/traveller93/article/details/120735912)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值