P5607 [Ynoi2013] 无力回天 NOI2017 题解
题目描述
你需要维护编号为 \(1,\cdots,m\) 的 \(m\) 个集合,初始为空。
接下来 \(m\) 次操作:
1 x y:在编号为 \(x\) 的集合中插入 \(y\) ,保证插入前 \(y\) 不在该集合中。2 x y:询问编号为 \(x,y\) 的集合并的元素个数。
数据范围
- \(1\le m\le 10^6,1\le x,y\le m\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
题解区大多数都是哈希表卡常的做法,这里提供一种不用哈希表且无需卡常的做法。
处理集合并就只有两种常见方法: bitset 或从元素端统计贡献。
记元素 \(i\) 的出现次数为 \(cnt_i\) ,则 \(\sum cnt_i\le m\) 。
果断根号分治。将所有元素按 \(cnt\) 降序排序,前 \(B\) 个元素用 bitset 维护,剩余元素先转化为求集合交集大小,然后可以看成维护一张 \(m\times m\) 的表格,每个元素会给 \(cnt_i^2\) 个位置加上 \(1\) 的贡献。
bitset 需要 \(\mathcal O(m\frac Bw)\) 的时间和空间,但常数很小。为了把空间用满取 \(B=3000\) ,则剩余元素 \(cnt_i\) 至多为 \(302\) (方程 \(x^2+3000x=10^6\) 的正根下取整), 进而:
由于我们只关心至多 \(m\) 个位置的值,用哈希表判断每次 \(+1\) 的位置是否是关键位置,这样我们需要 \(302m\) 次哈希表上的操作(这也是多数题解的做法)。
但是这样并没有很好地利用产生贡献位置的对称性。
先特判 \(x=y\) 的询问,注意到每次产生贡献的位置是一行一列的部分位置且关于主对角线对称,而且查询 \((x,y)\) 和 \((y,x)\) 的值一定相等,那么如果我们只给所在行添加贡献,查询时把 \((x,y)\) 和 \((y,x)\) 位置的值加起来,同样可以得到正确答案。
这样不同行之间的贡献独立,离线分别处理每行涉及到的操作,时间复杂度 \(\mathcal O(302n)\) ,空间复杂度 \(\mathcal O(n)\) ,常数非常小。
也可以换一种理解方式:
将询问 \((x,y)\) 的拆成 "先在 \(x\) 中出现再在 \(y\) 中出现的元素个数" 和 "先在 \(y\) 中出现再在 \(x\) 中出现的元素个数" 之和。
在处理第 \(i\) 行时,对所有
1 i y的操作,将此时 \(y\) 出现过的集合贡献 \(+1\) ;对所有2 i x或2 x i的操作,统计集合 \(x\) 的贡献。
#include<bits/stdc++.h>
using namespace std;
const int B=3000,maxn=1e6+5;
int n;
int x[maxn],y[maxn],op[maxn];
int id[maxn],cnt[maxn],res[maxn];
bitset<B+5> b[maxn];
vector<int> h[maxn],q[maxn];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&op[i],&x[i],&y[i]);
if(op[i]==1) cnt[y[i]]++;
}
iota(id+1,id+n+1,1);
sort(id+1,id+n+1,[&](int x,int y){return cnt[x]>cnt[y];});
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
{
if(op[i]==1)
{
if((y[i]=id[y[i]])<=B) b[x[i]][y[i]]=1;
else cnt[x[i]]++,q[x[i]].push_back(i),h[y[i]].push_back(i);
}
if(op[i]==2)
{
res[i]+=(b[x[i]]|b[y[i]]).count();
if(x[i]==y[i]) res[i]+=cnt[x[i]];
else res[i]+=cnt[x[i]]+cnt[y[i]],q[x[i]].push_back(i),q[y[i]].push_back(i);
}
}
memset(cnt,0,sizeof(cnt));
for(int j=1;j<=n;j++)
{
for(auto i:q[j])
{
if(op[i]==1) for(auto k:h[y[i]]) {if(k>i) break;cnt[x[k]]++;}
if(op[i]==2) res[i]-=cnt[x[i]+y[i]-j];
}
for(auto i:q[j])
if(op[i]==1) for(auto k:h[y[i]]) {if(k>i) break;cnt[x[k]]=0;}
}
for(int i=1;i<=n;i++) if(op[i]==2) printf("%d\n",res[i]);
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/19089229
浙公网安备 33010602011771号