JZOJ 5556 Password 分块
2018-03-02 18:44
274 查看
传送门
思路
参考代码
总结
传送门
思路
首先不难发现可以用暴力解决子问题。分析可得,暴力的时间复杂度为 O(mnx)O(mnx)。由于不知道 xx 的最大值,所以需要离线。
暴力写完后,我就打了个表,生成了一下随机数据,发现每两行都会发生重复。经过分析,得出了这样一个结论:只需要保存前三行,就能得到整个表,因为从第二行开始,每隔一行就会发生重复。
这个结论在打表后很容易发现,证明也很容易:只需要考虑一个位置经过两行后会变成什么就可以了。所以暴力的时间复杂度为 O(mn)O(mn),可以得到 60 分。
然后我就走上了思考如何用 CDQ 分治解决它的不归路……
正解其实很简单,就是分块。仔细想一想,第一行是原序列,第二行表示的是从 1 到 n aiai 出现了多少次。这个东西明显用分块才好解决。
然而分块调了一下午才调出来……
为什么 CDQ 分治不好弄呢?我觉得应该是左边对右边的影响不好差分。更改一个数后,你是不能知道左边对右边的贡献的,因为右边内部还有可能发生更改。
这里我就直接说分块思路了。
首先设 fi,jfi,j 表示第 ii 块中 jj 的出现次数,这很容易计算,单次修改是常数时间,单次询问是根号时间。由于我们还需要计算第三行的内容,所以要思考下需要什么信息才能得到第三行。如果我们知道了每一块中出现次数为 jj 的元素的个数,那么我们便能在根号时间内得到前面的块的答案。但是当前块的答案该怎么得到?我们必须知道当前块中元素在前面一共出现了多少次,所以之前的做法需要修改下:
设 fi,jfi,j 表示前 ii 块中 jj 的出现次数,只需要对之前的设法求一个前缀和即可。初始化时间为 O(n−−√v)O(nv),单次修改是根号时间,单次查询也是根号时间。设 gi,jgi,j 表示第 ii 块中出现次数为 jj 的元素个数,结合 ff 进行查询,时间复杂度还是根号的。但是修改需要仔细思考下。
当一个数改变时,它将会对出现次数的前缀和的后部分造成影响,这很容易。但是它是如何影响 gg 的呢?假设当前块出现了 4 个 aa,它们分别是整个序列的第 3 个,第 4 个,第 5 个和第 6 个 aa,随便删去(修改相当于先删再加)一个 aa 后,这个块剩下的一定是第 3 个,第 4 个,第 5 个 aa;假设后面相邻的块本来有第 7∼107∼10 个 aa,现在还剩下第 6∼96∼9 个 aa 了……
举个例子就很明白了,增加一个数的影响也类似,时间复杂度还是根号的。但是在我改题的时候因为没有认真分析导致改了很久,问题就出在想当然上。
参考代码
总结
我建议分块前,把所有要用的数据的定义写下来,手推一遍看看能不能在根号时间内求出答案,再写。这比写好了后 WA 掉慢慢调试要强得多。
提到元素个数,说不定就是分块了。因为这种东西难以合并,不能差分。
思路
参考代码
总结
传送门
思路
首先不难发现可以用暴力解决子问题。分析可得,暴力的时间复杂度为 O(mnx)O(mnx)。由于不知道 xx 的最大值,所以需要离线。
暴力写完后,我就打了个表,生成了一下随机数据,发现每两行都会发生重复。经过分析,得出了这样一个结论:只需要保存前三行,就能得到整个表,因为从第二行开始,每隔一行就会发生重复。
这个结论在打表后很容易发现,证明也很容易:只需要考虑一个位置经过两行后会变成什么就可以了。所以暴力的时间复杂度为 O(mn)O(mn),可以得到 60 分。
然后我就走上了思考如何用 CDQ 分治解决它的不归路……
正解其实很简单,就是分块。仔细想一想,第一行是原序列,第二行表示的是从 1 到 n aiai 出现了多少次。这个东西明显用分块才好解决。
然而分块调了一下午才调出来……
为什么 CDQ 分治不好弄呢?我觉得应该是左边对右边的影响不好差分。更改一个数后,你是不能知道左边对右边的贡献的,因为右边内部还有可能发生更改。
这里我就直接说分块思路了。
首先设 fi,jfi,j 表示第 ii 块中 jj 的出现次数,这很容易计算,单次修改是常数时间,单次询问是根号时间。由于我们还需要计算第三行的内容,所以要思考下需要什么信息才能得到第三行。如果我们知道了每一块中出现次数为 jj 的元素的个数,那么我们便能在根号时间内得到前面的块的答案。但是当前块的答案该怎么得到?我们必须知道当前块中元素在前面一共出现了多少次,所以之前的做法需要修改下:
设 fi,jfi,j 表示前 ii 块中 jj 的出现次数,只需要对之前的设法求一个前缀和即可。初始化时间为 O(n−−√v)O(nv),单次修改是根号时间,单次查询也是根号时间。设 gi,jgi,j 表示第 ii 块中出现次数为 jj 的元素个数,结合 ff 进行查询,时间复杂度还是根号的。但是修改需要仔细思考下。
当一个数改变时,它将会对出现次数的前缀和的后部分造成影响,这很容易。但是它是如何影响 gg 的呢?假设当前块出现了 4 个 aa,它们分别是整个序列的第 3 个,第 4 个,第 5 个和第 6 个 aa,随便删去(修改相当于先删再加)一个 aa 后,这个块剩下的一定是第 3 个,第 4 个,第 5 个 aa;假设后面相邻的块本来有第 7∼107∼10 个 aa,现在还剩下第 6∼96∼9 个 aa 了……
举个例子就很明白了,增加一个数的影响也类似,时间复杂度还是根号的。但是在我改题的时候因为没有认真分析导致改了很久,问题就出在想当然上。
参考代码
#include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <iostream> #include <algorithm> #include <vector> #include <string> #include <stack> #include <queue> #include <deque> #include <map> #include <set> #include <bitset> #include <functional> #include <cassert> typedef long long LL; typedef unsigned long long ULL; using std::cin; using std::cout; using std::endl; LL readIn() { LL a = 0; bool minus = false; char ch = getchar(); while (!(ch == '-' || (ch >= '0' && ch <= '9'))) ch = getchar(); if (ch == '-') { minus = true; ch = getchar(); } while (ch >= '0' && ch <= '9') { a = a * 10 + (ch - '0'); ch = getchar(); } if (minus) a = -a; return a; } void printOut(LL x) { int length = 0; char buffer[20]; if (x < 0) { putchar('-'); x = -x; } do { buffer[length++] = x % 10 + '0'; x /= 10; } while (x); do { putchar(buffer[--length]); } while (length); putchar('\n'); } const int maxn = int(1e5) + 5; int n, m; int nModify, maxx = 3; int a[maxn]; struct Origin { int type; int param1; int param2; } origins[maxn]; #define RunInstance(x) delete new x struct work { static const int maxs = 320; int sqrtN; int N; int inBlocks[maxn]; int lBegin[maxs]; int rEnd[maxs]; int buf[maxs][10005]; int buf2[maxs][10005]; void initBlocks() { sqrtN = std::sqrt(n); N = (n + sqrtN - 1) / sqrtN; for (int i = 1, v = 0; i <= n; i++, v++) { int t = inBlocks[i] = (i - 1) / sqrtN; if (!lBegin[t]) lBegin[t] = i; rEnd[t] = i; } for (int i = 1; i <= n; i++) buf[inBlocks[i]][a[i]]++; for (int i = 1; i < N; i++) for (int j = 0; j <= int(1e4); j++) buf[i][j] += buf[i - 1][j]; static int times[int(1e4) + 5]; for (int i = 1; i <= n; i++) buf2[inBlocks[i]][++times[a[i]]]++; } work() : buf(), buf2(), lBegin(), rEnd(), inBlocks() { initBlocks(); for (int o = 1; o <= m; o++) { const Origin& ins = origins[o]; if (ins.type == 1) { int& num = a[ins.param2]; if (num == ins.param1) continue; int ib = inBlocks[ins.param2]; buf2[ib][buf[ib][num]]--; for (int i = ib + 1; i < N; i++) { buf2[i][buf[i - 1][num]]++; buf[i - 1][num]--; buf2[i][buf[i][num]]--; } buf[N - 1][num]--; num = ins.param1; buf[ib][num]++; buf2[ib][buf[ib][num]]++; for (int i = ib + 1; i < N; i++) { buf2[i][buf[i - 1][num]]--; buf[i][num]++; buf2[i][buf[i][num]]++; } } else if (ins.type == 2) { if (ins.param1 == 1) printOut(a[ins.param2]); else { int to = ins.param2; int ib = inBlocks[to]; int num = a[to]; int times; times = ib ? buf[ib - 1][num] : 0; for (int i = lBegin[ib]; i <= to; i++) if (a[i] == a[to]) times++; if (ins.param1 % 2 == 0) printOut(times); else { int ans = 0; for (int i = 0; i < ib; i++) ans += buf2[i][times]; static int appear[maxs * 2]; static int vis[int(1e4) + 5]; static int ttimes[int(1e4) + 5]; appear[0] = 0; for (int i = lBegin[ib]; i <= to; i++) { if (!vis[a[i]]) { appear[++appear[0]] = a[i]; vis[a[i]] = appear[0]; ttimes[appear[0]] = (ib ? buf[ib - 1][a[i]] : 0); } } for (int i = lBegin[ib]; i <= to; i++) { if ((++ttimes[vis[a[i]]]) == times) ans++; } while (appear[0]) { vis[appear[appear[0]--]] = 0; } printOut(ans); } } } } } }; void run() { n = readIn(); for (int i = 1; i <= n; i++) a[i] = readIn(); m = readIn(); for (int i = 1; i <= m; i++) { Origin& o = origins[i]; o.type = readIn(); o.param1 = readIn(); o.param2 = readIn(); if (o.type == 1) nModify++; else maxx = std::max(maxx, o.param1); } RunInstance(work); } int main() { #ifndef LOCAL freopen("password.in", "r", stdin); freopen("password.out", "w", stdout); #endif run(); return 0; }
总结
我建议分块前,把所有要用的数据的定义写下来,手推一遍看看能不能在根号时间内求出答案,再写。这比写好了后 WA 掉慢慢调试要强得多。
提到元素个数,说不定就是分块了。因为这种东西难以合并,不能差分。
相关文章推荐
- Jzoj4699 Password
- [JZOJ5555]Password
- 【Codeforces 583C】【JZOJ 4699】Password
- [JZOJ 4699][CF583C]【NOIP2016提高A组模拟8.15】Password
- JZOJ 4699 Password
- 【JZOJ4699】Password
- 【JZOJ 4699】 Password
- JZOJ 4699 Password【NOIP2016提高A组模拟8.15】
- 【JZOJ4699】Password
- 【开源项目】花密(Flower Password)VB版之XP样式模块
- 登录phpmyadmin时遇见的using password no问题
- 【开源项目】花密(Flower Password)VB版之INI文件读写模块
- 【JZOJ3738】理想城市(city)
- enable password 7与enable secret的区别
- 【jzoj3740】【TJOI2014】【电源插排】【线段树】
- Bug 331299 - Eclipse Virgo failed to shutdown after admin password change
- ERROR 1045 (28000): Access denied for user 'hive'@'localhost' (using password: YES)
- ORA-01017 invalid username/password;logon denied" (密码丢失解决方案)
- 【JZOJ5034】B 题解