2020CCPC长春补题
好难,只过了两题
F - Strange Memory
比赛时我写了个点分治233
写完才发现是有根树,然后我就把点分治改了下,变成了一个O(n^2)的暴力,在第三个点T了
然后我就想这是不是要轻重链剖分,然后剖了一下,发现完全写不来。。
赛后看了下dsu on tree,就感觉dsu on tree就是这题的正解了
今天思考了下这个算法,终于理解了dsu on tree了
然后就试试自己写下F题
我做出来的第一道dsu on tree
思路:
先考虑暴力的O(n^2)做法,计算每个节点为LCA时的贡献,要把这个节点当子树的根节点时,把整颗子树遍历一遍,然后还要清空
复杂度 = Σsize[node] = O(n^2)
然后考虑优化的做法,计算一个节点的贡献,我们先计算完其儿子的贡献,然后再计算它的贡献
在计算第二个儿子的贡献时,要把它第一个儿子的记录清空。计算第三个儿子的贡献时,要把它第二个儿子的记录清空,以此类推,
但是,在计算最后一个儿子的贡献时,就不必把记录清空了,这个记录在计算其父节点的时候也要用到,
所以,我们把重儿子留到最后一个计算,并不对重儿子做清空处理
这就是dsu on tree了
复杂度分析:
对于计算每个节点,要遍历的点为除重儿子为根节点的子树以外的所有子树 ( 即所有轻儿子为根节点的子树)
复杂度 = Σsize[node] - size[son[node]]
这个复杂度怎么算呢,我们对于某个节点node,沿着一条重链计算一下,size[node] - siz[son[node]] + siz[son[node]] - siz[son[son[node]]] + siz[son[son[node]]] - ...
可以发现这是一个相消的式子,累加的结果为size[node]
我们先从根节点沿着重链计算一下,size[st] = n,复杂度 += n
然后对于那些没有计算过,但父亲计算过的节点vi,从这些节点沿重链计算一下,Σsize[vi] < n,复杂度 += n
再次这样计算几轮,直到所有节点都被计算过为止
可以发现轮数一定是<=log2 n的
所以复杂度为O(n log2 n)
对于本题,还有一个难点,就是在清除的时候该怎么做
显然不能在一个vector数组里指定清除哪个数
其实直接pop掉就行(想一想,为什么)
最后,为什么我写的dsu on tree的函数DSU_ON_TREEEEEEEEEEEEE,函数名这么长,还用了个very_heavyyyyyyyy?
因为我第一次理解了dsu on tree,比较兴奋(
我从5月份的时候就想学dsu on tree了,然而现在12月份了,才理解dsu on tree。。。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int MAXN = 1e5+7;
const int MAXP = 1e6+7;
int a[MAXN];
vector<int> dt[MAXP];
int n;
struct EDGE{
int to,next;
}edge[MAXN*2];
int head[MAXN],tot;
void add(int u,int v){
tot++;
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot;
}
int fa[MAXN],son[MAXN],siz[MAXN];
void dfs(int s,int f){//求重儿子
siz[s] = 1;
int maxsize = -1;
for(int i = head[s];i;i=edge[i].next ){
int po = edge[i].to;
if(po == f ) continue;
fa[po] = s;
dfs(po,s);
siz[s] += siz[po];
if(siz[po]>maxsize){
maxsize = siz[po];
son[s] = po;
}
}
}
long long ans = 0;
struct Pa{
int node,qq;
}tmp[MAXN];
int cnt = 0;
void cal(int s,int st){//记录整颗子树
int ff = a[s] ^ a[st];
if(ff<=1000000){
for(int i = 0;i < dt[ff].size();i++){
ans += (long long)(dt[ff][i] ^ s);
}
}
cnt++;
tmp[cnt].node = s;
tmp[cnt].qq = a[s];
for(int i = head[s];i;i=edge[i].next ){
int po = edge[i].to;
if(po == fa[s]) continue;
cal(po,st);
}
}
void clear(int st){//清空整颗子树的记录
dt[a[st]].pop_back();
for(int i = head[st];i;i=edge[i].next){
int po = edge[i].to;
if(po == fa[st]) continue;
clear(po);
}
}
void DSU_ON_TREEEEEEEEEEEEE(int st,bool very_heavyyyyyyyy){
for(int i = head[st]; i ;i = edge[i].next){
int po = edge[i].to;
if(po == fa[st] || po == son[st]) continue;
DSU_ON_TREEEEEEEEEEEEE(po,0);//先求轻儿子
}
if(son[st]) DSU_ON_TREEEEEEEEEEEEE(son[st],1); //求重儿子
for(int i = head[st]; i ;i = edge[i].next){//计录轻儿子,此时重儿子已经计录过了
int po = edge[i].to;
if(po == fa[st] || po == son[st]) continue;
cnt = 0;
cal(po,st);
for(int j = 1;j <= cnt;j++){
int node = tmp[j].node;
int qq = tmp[j].qq;
dt[qq].push_back(node);
}
}
for(int i = 0;i < dt[0].size();i++){//计算自己
ans += (long long)(dt[0][i]^st);
}
dt[a[st]].push_back(st);//记录子树根节点
if(!very_heavyyyyyyyy) clear(st);//子树根节点是轻儿子,清空整颗子树的记录
}
int main()
{
cin>>n;
tot = 0;
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
head[i] = 0;
}
int u,v;
for(int i = 1;i <= n-1;i++){
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
DSU_ON_TREEEEEEEEEEEEE(1,1);
cout<<ans<<endl;
return 0;
}
K - Ragdoll
这题看起来很难
思路:
先从x⊕y = gcd(x,y)下手
打了个表,发现所有符合条件的对,x和y互不为倍数,也就是gcd(x,y)!=x&&gcd(x,y)!=y
为什么?因为如果满足gcd(x,y)==x且x⊕y == x,那么y==0(因为x ^ y==x),显然这样gcd(x,y)!=x
受此启发,我们可以先假设gcd(x,y)为x的某个因数z,满足x⊕y == z,那么 y = x⊕z,最后再检验gcd(x,y)是否为z
这样就能把2e5内的所有对求出来了
然后是怎么处理合并时的计算
我们记录每棵树上各个权值的个数,这样合并的时候,遍历一遍其中一颗树就能计算了
先考虑这样做的时间复杂度
受上面F题的启发,我们每次合并的时候只搜索轻的树,把轻的树合并到重的树上,这样时间复杂度和上面那题应该是一样的
再考虑空间复杂度
最多有3e5棵树,每棵树开2e5的cnt数组是不行的,怎么办?
我想到了动态开点,但感觉这样好麻烦,后来突然想到了map,好!这样就能做了
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
const int MAXN = 3e5+7;
vector<int>graph[MAXN];
int gcd(int a,int b){
if(a<b)swap(a,b);
while(b){
a = a%b;
swap(a,b);
}
return a;
}
void pre(int n){
for(int i = 1;i <= n;i++){
for(int j = 1;j * j <= i;j++){
if(i % j == 0){
if(gcd(i^j, i) == j) graph[i].push_back(i^j);
if(i / j != j){
if(gcd((i^(i/j)),i ) == i / j) graph[i].push_back(i^(i/j));
}
}
}
}
}
int n,m;
long long ans = 0;
struct NODE{
int siz = 0,top, id, val;
map <int,int> cnt;
}node[MAXN];
int par[MAXN];
int find(int x){
if(par[x] == x) return x;
return par[x] = find(par[x]);
}
void merge(int a,int b){
par[b] = a;
for(map<int,int>::iterator it = node[b].cnt.begin();it!=node[b].cnt.end();++it){
if(!it->second) continue;
int x = it->first, nx = it->second;
for(int i = 0;i < graph[x].size();i++){
int y = graph[x][i];
ans += (long long)nx * (long long)node[a].cnt[y];
}
}
for(map<int,int>::iterator it = node[b].cnt.begin();it!=node[b].cnt.end();++it){
if(!it->second) continue;
int x = it->first;
node[a].cnt[x] += it->second;
}
node[a].siz += node[b].siz;
}
int main()
{
pre(2e5);
cin>>n>>m;
int vv;
for(int i = 1;i <= n;i++) {
node[i].id = i;
node[i].top = i;
scanf("%d",&vv);
node[i].cnt[vv]++;
node[i].val = vv;
node[i].siz++;
par[i] = i;
}
int t, x, y, v;
for(int i = 1;i <= m;i++){
scanf("%d",&t);
if(t==1){
scanf("%d%d",&x,&v);
node[x].id = x;
node[x].top = x;
node[x].cnt[v]++;
node[x].siz++;
node[x].val = v;
par[x] = x;
}
if(t==2){
scanf("%d%d",&x,&y);
x = find(x),y = find(y);
if(node[x].siz < node[y].siz) swap(x,y);
if(x!=y)merge(x,y);
}
if(t==3){
scanf("%d%d",&x,&v);
int fx = find(x);
for(int j = 0;j < graph[node[x].val].size();j++){
int y = graph[node[x].val][j];
ans -= (long long)node[fx].cnt[y];
}
node[fx].cnt[node[x].val]--;
node[x].val = v;
for(int j = 0;j < graph[v].size();j++){
int y = graph[v][j];
ans += (long long)node[fx].cnt[y];
}
node[fx].cnt[v]++;
}
printf("%lld\n",ans);
}
return 0;
}

浙公网安备 33010602011771号