您的位置:首页 > 其它

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 了……

  举个例子就很明白了,增加一个数的影响也类似,时间复杂度还是根号的。但是在我改题的时候因为没有认真分析导致改了很久,问题就出在想当然上。

参考代码

#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 掉慢慢调试要强得多。

  提到元素个数,说不定就是分块了。因为这种东西难以合并,不能差分。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: