Codeforces Round 221 (Div. 1) D和hdu的C题 树上启发式合并

题目链接

hdu的

小总结:

        hdu的题是点对,这和树上路径相似,也和点分治思想一样,都是考虑三种情况不经过根,一端是根,跨根,第一种情况又可以归到子树的二三情况,所以一般都是只需要考虑后面两种情况。

        在dfs的时候,第一个for循环就是处理轻儿子子树的答案,由于是轻儿子,最后都会被keep=0的条件把内容清除,也就是res置零和线段树删点,所以在处理完轻儿子后,所有东西都是空的,等于还没进入dfs一样。

        接下来是处理重儿子,这时候重儿子的信息是被保留的,因为keep=1,也就是说res存了重儿子的答案,线段树里存了重儿子的节点。记录了重儿子的贡献,我们还需要去记录轻儿子的贡献,因为之前都删光了。

        在处理轻儿子的时候,我们会发现已经有重儿子的信息了。而重儿子子树是和轻儿子不一样的,也就是说其实这时候是处理跨树也就是跨根的情况。这时候轻儿子子树的信息是被保留的,也就是把信息都保存到了重儿子上。遍历一颗颗轻儿子子树,然后每次处理当前轻儿子和以前遍历过的所有子树的信息,也就是从这颗子树到别的所以子树去找跨子树点对了。

        处理完跨根,还剩第二种情况。直接就让当前根节点和所有子树点算贡献,然后再将根节点add进去就好了。

        有好多好多坑....

        一开始一直debug...然后瞎改,弄了个add和calc合并起来的代码,ac了...后来好好看了下,发现合并起来的代码其实就是将第一种情况(不跨根)和跨根的情况一起算了,所以就不用加上所有子树的答案了。

        

        cf那题和hdu我都用线段树维护信息了,作为我一发就ac的题应该没啥好说的,和上题差不多吧...也就是用一个线段树来维护各个数的个数(虽然数组就可以了)。离线处理一下,然后把超过k的答案直接求k到N(比给的数据大就行)的个数。这个和上面那题不一样,不需要分跨不跨根什么的,直接统计所有点就好了,所以直接都add,然后再查询一下就好了。

        一开始的hdu代码:

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=1e6+10;
const int mod=1e9+7;
#define fi first
#define se second

int n,val[N];
vector<int> ve[N];
int son[N],siz[N];
int dep[N];
int ans[N];
int res,flag;
struct tree{
	int tr[N*4];
	void modify(int p,int l,int r,int x,ll value){
		if(l==r){
			tr[p]+=value;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) modify(p<<1,l,mid,x,value);
		else modify(p<<1|1,mid+1,r,x,value);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	int query(int p,int l,int r,int x,int y){
		if(x<=l&&y>=r){
			return tr[p];
		}
		int res=0;
		int mid=(l+r)>>1;
		if(x<=mid) res+=query(p<<1,l,mid,x,y);
		if(y>mid) res+=query(p<<1|1,mid+1,r,x,y);
		return res;
	}
}cnt,sum,pfsum;

void dfs1(int x,int f){
	siz[x]=1;
	dep[x]=dep[f]+1;
	for(auto y:ve[x]){
		if(y==f) 
			continue;	
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])
			son[x]=y;
	}
}

//max*max-min*max
int croot,rt;
void calc(int u,int f,ll value){
	
	int cntmin;
		cntmin=cnt.query(1,1,N,1,val[u]);
	int summ;
		summ=sum.query(1,1,N,1,N);
	int pfsummax;
		pfsummax=pfsum.query(1,1,N,val[u]+1,N);
	res+=value*val[u]*val[u]*cntmin;
	res+=value*pfsummax;
	res-=value*summ*val[u];
	
	cnt.modify(1,1,N,val[u],value);//add操作在calc操作前后都可以,因为对自己对自己的贡献是0 
	sum.modify(1,1,N,val[u],value*val[u]);
	pfsum.modify(1,1,N,val[u],value*(val[u]*val[u]));
	if(croot) return;
	for(auto v:ve[u]){
		if(v==f||v==flag)
			continue;
		calc(v,u,value);
	}
}

void dfs2(int u,int f,bool keep){
	for(auto v:ve[u]){
		if(v==f||v==son[u])
			continue;
		dfs2(v,u,0);
	}
	if(son[u]){
		dfs2(son[u],u,1);
		flag=son[u];
	}
	croot=0;
	for(auto v:ve[u]){
		if(v==f||v==flag)
			continue;		
		calc(v,u,1);//只操作 
	}
	flag=0; 
	croot=1;//表示只修改根节点 
	calc(u,f,1);
	croot=0;
	ans[u]+=res*2;
	if(!keep){
		calc(u,f,-1);
	}
}
void solve(){
	cin>>n;
	for(int i=2;i<=n;i++){
		int a,b;
		cin>>a>>b;
		ve[a].push_back(b);
		ve[b].push_back(a);
	}
	for(int i=1;i<=n;i++)
		cin>>val[i];
	dfs1(1,0);
	dfs2(1,0,1);
	int t=0;
	for(int i=1;i<=n;i++)
		t^=ans[i];
	cout<<t;
}
signed  main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
	return 0;
}

        hdu改:后来的代码改了一下,也把我的所有理解写在注释里了(别小看这几句....都是血的教训)

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=1e6+10;
const int mod=1e9+7;
#define fi first
#define se second

int n,val[N];
vector<int> ve[N];
int son[N],siz[N];
int dep[N];
int ans[N];
int res,flag;
struct tree{
	int tr[N*4];
	void modify(int p,int l,int r,int x,ll value){
		if(l==r){
			tr[p]+=value;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) modify(p<<1,l,mid,x,value);
		else modify(p<<1|1,mid+1,r,x,value);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	int query(int p,int l,int r,int x,int y){
		if(x<=l&&y>=r){
			return tr[p];
		}
		int res=0;
		int mid=(l+r)>>1;
		if(x<=mid) res+=query(p<<1,l,mid,x,y);
		if(y>mid) res+=query(p<<1|1,mid+1,r,x,y);
		return res;
	}
}cnt,sum,pfsum;

void dfs1(int x,int f){
	siz[x]=1;
	dep[x]=dep[f]+1;
	for(auto y:ve[x]){
		if(y==f) 
			continue;	
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])
			son[x]=y;
	}
}

//max*max-min*max
int croot;

void calc(int u,int f,ll value){
	
	int cntmin;
		cntmin=cnt.query(1,1,N,1,val[u]);
	int summ;
		summ=sum.query(1,1,N,1,N);
	int pfsummax;
		pfsummax=pfsum.query(1,1,N,val[u]+1,N);
	res+=value*val[u]*val[u]*cntmin;
	res+=value*pfsummax;
	res-=value*summ*val[u];

	if(croot) return;
	for(auto v:ve[u]){
		if(v==f||v==flag)
			continue;
		calc(v,u,value);
	}
}
void add(int u,int f,ll value){
	
	cnt.modify(1,1,N,val[u],value);
	sum.modify(1,1,N,val[u],value*val[u]);
	pfsum.modify(1,1,N,val[u],value*(val[u]*val[u]));
	
	if(croot) return;
	for(auto v:ve[u]){
		if(v==f||v==flag)
			continue;
		add(v,u,value);
	}
}
void dfs2(int u,int f,bool keep){
	for(auto v:ve[u]){
		if(v==f||v==son[u])
			continue;
		dfs2(v,u,0);
		ans[u]+=ans[v];//不能res,因为会影响后面的轻儿子 
	}
	
	if(son[u]){
		dfs2(son[u],u,1);
		flag=son[u];
		//res=0;//res和ans[flag]重复,且res*2不等于ans[son],因为ans是+=res*2,可能有不经过根子树的答案 
		ans[u]+=ans[flag];//res存的应该是重儿子第一种情况,不经过根的贡献 
	}
	res=0;//res一定要赋值为0,因为这里的res存的只有跨根贡献,没有重儿子里的其他贡献 
	croot=0;//表示修改整个子树 
	for(auto v:ve[u]){
		if(v==f||v==flag)
			continue;		
		calc(v,u,1);//先算贡献再add 
		add(v,u,1);
	}
	flag=0;//注意置0,让后面的keep可以删整颗子树 
	croot=1;//表示只修改根节点 
	calc(u,f,1);
	add(u,f,1);
	croot=0;//同flag置0作用 
	ans[u]+=res*2;//在子树不经过根的ans[v]不用*2,因为在子树乘过了,新算的res要*2 
	if(!keep){
		//calc(u,f,-1);//算贡献的时候是正着for循环前面所有子树和当前子树算贡献 
		add(u,f,-1);//删的时候是正着for循环与后面所有子树和当前子树删贡献,一样的总贡献 
		res=0;//再calc一次tle了....果然线段树太慢了,反正都是让res变0,直接置0 
	}
}
void solve(){
	cin>>n;
	for(int i=2;i<=n;i++){
		int a,b;
		cin>>a>>b;
		ve[a].push_back(b);
		ve[b].push_back(a);
	}
	for(int i=1;i<=n;i++)
		cin>>val[i];
	dfs1(1,0);
	dfs2(1,0,1);
	int t=0;
	for(int i=1;i<=n;i++)
		t^=ans[i];
	cout<<t;
}
signed  main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
	return 0;
}

        cf的...       

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
const int mod=1e9+7;
#define fi first
#define se second
 

struct tree{
	int tr[N*4];
	void modify(int p,int l,int r,int x,int value){
		if(l==r){
			tr[p]+=value;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) modify(p<<1,l,mid,x,value);
		else modify(p<<1|1,mid+1,r,x,value);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	int query(int p,int l,int r,int x,int y){
		if(x<=l&&y>=r){
			return tr[p];
		}
		int mid=(l+r)>>1;
		int res=0;
		if(x<=mid) res+=query(p<<1,l,mid,x,y);
		if(y>mid) res+=query(p<<1|1,mid+1,r,x,y);
		return res;
	}
}sum,cnt;

int n,m;
struct node{
	int id,k;
};
int c[N],ans[N];
vector<node> ask[N];
vector<int> e[N];
int siz[N],son[N];
int flag,croot;

void df(int x,int f){
	siz[x]=1;
	for(auto y:e[x]){
		if(y==f) 
			continue;
		df(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])
			son[x]=y;
	}
}

void add(int x,int f,int value){
	sum.modify(1,0,N,c[x],value);
	int num=sum.query(1,0,N,c[x],c[x]);
	cnt.modify(1,0,N,num,1);
	cnt.modify(1,0,N,num-value,-1);
	if(croot)
		return;
	for(auto y:e[x]){
		if(y==f||y==flag)
			continue;
		add(y,x,value);
	}
}

void dfs(int x,int f,bool keep){
	for(auto y:e[x]){
		if(y==f||y==son[x])
			continue;
		dfs(y,x,0);
	}
	if(son[x])
		dfs(son[x],x,1),flag=son[x];
	croot=0;
	for(auto y:e[x]){
		if(y==f||y==son[x])
			continue;
		add(y,x,1);
	}
	croot=1;
	add(x,f,1);
	croot=0;
	for(auto [id,k]:ask[x]){
		ans[id]=cnt.query(1,0,N,k,N);
	}
	flag=0;
	if(!keep){
		add(x,f,-1);
	}
}
void solve(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>c[i];
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	for(int i=1;i<=m;i++){
		int u,k;
		cin>>u>>k;
		ask[u].push_back({i,k});
	}
	df(1,0);
	dfs(1,0,1);
	for(int i=1;i<=m;i++)
		cout<<ans[i]<<endl;
}
signed  main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
	return 0;
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值