zoukankan      html  css  js  c++  java
  • 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
    ",ans);
    	}
    	return 0;
    }
    

      

     

  • 相关阅读:
    泛型
    事件和委托
    参数和属性
    常量、字段、和方法
    LINQ
    LINQ- 子查询、组合策略、投影策略
    MySQL全局锁和表锁
    CLR基础
    LINQ
    LINQ
  • 原文地址:https://www.cnblogs.com/ruanbaitql/p/14122750.html
Copyright © 2011-2022 走看看