• 741D.Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(树上启发式合并+状压)


    只有22种字符。

    一个串满足答案的条件是:

    每个字符出现次数都为偶数或者出现次数为奇数的字符仅有一个。

    因此我们可以考虑:

    用0表示这个字符出现次数为偶数,1表示出现次数为奇数。

    而对于一个路径上的字符,我们只需要考虑每个字符的出现次数,这样我们用一个整数就可以表示出所有符合条件的状态:0或者(1<<i),0<=i<=21,总共22种。

    之后我们可以考虑维护一个值(dist_i)表示从i到1的路径上字符的状态,其实利用异或的性质(dist_i)就是1到i路径里面所有字符表示的字符异或的答案。

    同时我们如果知道两个点,那么两个点路径上的状态就是(dis_u) ^(dis_v)

    如果两个节点之间形成的路径符合条件的话,那么(dis_u)^(dis_v=0|(1<<i),0 leq i leq 21)

    (f_x)表示从根节点触发路径异或之后的值为(x)的最大深度。

    然后开始DSU On Tree,先处理出每个点的重儿子,然后轻儿子和重儿子依次合并。

    时间复杂度(O(nlogn))

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=5e5+100;
    
    vector<pair<int,int> > g[maxn];
    int tot;
    int L[maxn];//每个点进栈的时间
    int R[maxn];//每个点出栈的时间
    int id[maxn];//每个DFS序对应的点
    int son[maxn];//重儿子
    int f[maxn*20];//f(i)表示以当前节点为根的子树中状态为i的最大深度
    int dis[maxn];//dis(i)表示以当前节点为根的子树中状态为i的最大深度
    int ans[maxn];//记录每个点的答案
    int size[maxn];//子树大小 
    int dep[maxn];//层数 
    
    
    void dfs1 (int x,int pre) {
    	//处理重儿子
    	dep[x]=dep[pre]+1;//处理出当前节点所在层数 
    	size[x]=1;//处理出子树大小 
    	L[x]=++tot;//DFS时入栈的时间点 
    	id[tot]=x;//DFS序上节点本来的编号 
    	for (pair<int,int> y:g[x]) {
    		dis[y.first]=dis[x]^y.second;//dis[y]记录根节点到节点外的边权异或和 
    		dfs1(y.first,x);//往下搜 
    		size[x]+=size[y.first];//更新子树 
    		if (size[son[x]]<size[y.first]) son[x]=y.first;//记录重儿子 
    	}
    	R[x]=tot;//记录出栈时间点 
    }
    void cal (int x) {
    	//这里f(dis(x))表示在以x为根的子树状态中状态为dis(x)的最大深度
    	if (f[dis[x]]) ans[x]=max(ans[x],f[dis[x]]-dep[x]);//如果该节点的子树内有路径异或和为dis[x]的点,那么就先更新长度 
    	for (int i=0;i<22;i++) {
    		if (f[dis[x]^(1<<i)]) {
    			ans[x]=max(ans[x],f[dis[x]^(1<<i)]-dep[x]);//同理,这里记录的是奇数长度回文串 
    		}
    	}
    	f[dis[x]]=max(f[dis[x]],dep[x]);//f[dis[x]]初始化为自己 
    	
    	for (pair<int,int> y:g[x]) {//遍历点x的每个儿子
    		if (y.first==son[x]) continue;//不遍历x的重儿子 
    		for (int j=L[y.first];j<=R[y.first];j++) {
    			int z=id[j];
    			//考虑以x为中间点的情况 
    			//遍历整个y子树
    			//如果f(dis(z))可以和别的子树里的路径产生答案,那么更新答案
    			if (f[dis[z]]) ans[x]=max(ans[x],f[dis[z]]+dep[z]-2*dep[x]);
    			
    			for (int k=0;k<22;k++) {
    				if (f[dis[z]^(1<<k)]) {
    					ans[x]=max(ans[x],f[dis[z]^(1<<k)]+dep[z]-2*dep[x]);
    				}
    			} 
    		}
    		for (int j=L[y.first];j<=R[y.first];j++) {
    			f[dis[id[j]]]=max(f[dis[id[j]]],dep[id[j]]); 
    			//同时把子树里的每个点的贡献统计 
    		}
    	} 
    }
    
    void dfs2 (int x,int kp) {
    	//kp为1表示处理并保留数据
    	//否则表示处理,否则表示处理但不保留数据
    	for (pair<int,int> y:g[x]) {
    		if (y.first==son[x]) continue;
    		dfs2(y.first,0);
    		ans[x]=max(ans[x],ans[y.first]);
    		//更新答案,因为有可能最大长度的链不以当前节点为根节点 
    	}  
    	
    	if (son[x]) {
    		dfs2(son[x],1);
    		ans[x]=max(ans[x],ans[son[x]]);
    	}
    	cal(x);//统计节点x的答案 
    	if (!kp) for (int i=L[x];i<=R[x];i++) f[dis[id[i]]]=0;
    	//如果是轻儿子,则清空数据
    	//如果是重儿子,就不清空,保留数据,后续把轻儿子往重儿子上合并,时间复杂度的优化主要在这里 
    }
    int main () {
    	int n;
    	scanf("%d",&n);
    	for (int i=2;i<=n;i++) {
    		int x;
    		char ch;
    		scanf("%d %c",&x,&ch);
    		g[x].push_back(make_pair(i,1ll<<(ch-'a')));
    	}
    	dfs1(1,0);
    	dfs2(1,1);
    	for (int i=1;i<=n;i++) printf("%d ",ans[i]);
    }
  • 相关阅读:
    python之函数用法id(),了解即可
    python之函数用法locals()
    python之函数用法vars()
    python之函数用法execfile()
    python之函数用法isinstance()
    python之函数用法any()
    python之函数用法iter()
    python之函数用法file()
    python之函数用法bin()
    python之函数用法basestring
  • 原文地址:https://www.cnblogs.com/zhanglichen/p/14608387.html
Copyright © 2020-2023  润新知