zoukankan      html  css  js  c++  java
  • CF1481F AB tree

    F. AB tree

    怎么又是 DP?这个官方题解很多不严谨的地方,我会一一纠正。

    这个题是真的毒瘤。

    首先我们考虑答案的最小值。若根节点的深度为 \(1\),深度最大的节点为 \(dm\),深度为 \(dmax\)。那么答案最小为 \(dmax\)。因为对于 \(1->dm\) 的路径上就会有 \(dmax\) 个不同的字符串。只要我们保证所有深度相同的节点字符都一样,就可以达到答案的最小值 \(dmax\)

    但是我们还需要满足 字符 a 的个数限制。我们令其有 \(a\) 个。显然我们要做一个背包,来确定是否可以做到在同一深度的点染成一个字符。时复杂度为 \(O(n^2)\)。显然,我们需要优化。

    我们令深度为 \(i\) 的点有 \(sz_i\) 个。我们可以把 \(sz_i\) 相同的深度合并到一起,做多重背包。那么最多有多少个不同的 \(sz_i\) 呢?官方题解给出的数量是 \(O(\sqrt n)\)。很容易举出返利:\(23=1+2+3+4+5+6+2\)。那么真正的数量为多少呢?我们设数量为 \(x\)。为了使得 \(x\) 最大,不同的 \(x\) 个数一定是 \(1,2,3,\cdots ,x\)。解不等式组:

    \[\begin{cases} x\geq 0\\ \frac{(x+1)\cdot x}{2}\leq n \end{cases}\]

    解集为 \(0\leq x\leq \frac{-1+\sqrt{8n+1}}{2}\)。我们可以粗略地认为 \(0\leq x\leq \sqrt{2n}\)。所以 \(x\) 最大为 \(\sqrt{2n}\)

    我们言归正传,如何 DP 呢?用二进制优化肯定超时了。单调队列优化应该是可以的,但是不利于我们后面构造方案。我们只能采取另一种方式。

    基础多重背包

    我们回顾一下最基础的做法。

    定状态

    先解决一些基本的定义,定义第 \(i\) 个物品最多可选 \(tot_i\) 个,所占空间为 \(val_i\)。共有 \(m\) 个物品。

    \(f_{i,j}\) 表示考虑到前 \(i\) 个物品,是否存在总体积为 \(j\) 的方案。

    转移方程

    若存在 \(k\),满足 \(0\leq k\cdot val_i\leq j\),且 \(f_{i-1,j-k\cdot val_i}=\operatorname{true}\),则 \(f_{i,j}=\operatorname{true}\)

    否则 \(f_{i,j}=\operatorname{false}\)

    边界&&初值

    \(f_{0,0}=\operatorname{true}\)

    很显然这个背包肯定超时,怎么优化呢?我们发现这里和普通被白哦不同的是我们只需要知道是否存在这种方案,而不需要求出一个最大值。这就是突破口。

    优化后的背包算法

    我们可以定义 \(k_{i,j}\),表示对于 \(f_{i,j}\),最小的满足条件的 \(k\)。(\(k\) 的定义同上。)

    很显然,\(k_{i,j}\) 也需要转移,转移方程很简单:

    \[k_{i,j}=\begin{cases} 0&&k_{i-1,j}=\operatorname{true}\\ k_{i,j-val_i}+1&&j\geq val_i\\ -inf&&\operatorname{otherwise} \end{cases}\]

    那么对应的 \(f_{i,j}\) 的转移就简单了很多,我们不用在额外枚举 \(k\) 了。

    如果最后 \(f_{m,a}=\operatorname{ture}\),就说明我们可以做到答案为 \(dmax\)。利用 \(k\) 数组,倒序构造一下即可。

    我们考虑剩下的情况。什么时候答案为 \(dmax+1\) 呢。我们可以想象。如果对于两个深度相同的点来说,如果他们两个染上了相同的字符,但是他们付清的前缀不同,这两个串一定也不同,而且这个不同会传导到他们的子孙节点上。因此为了做到答案为 \(dmax+1\),只能在叶子节点处有不同的字符。

    我们考虑贪心。我们从深度为 \(1\) 的节点一直考虑到深度为 \(dmax\) 的点。设现在还有 \(m\) 割点未被染色,有 \(y\)a 可用,\(sz_i\leq \max(y,m-y)\),那么就用一个字符覆盖这一层。

    我们考虑是否在任何情况下都可以做到答案为 \(dmax+1\)。若这一层都是非叶节点,我们知道所有非叶节点都有至少一个儿子,那么他们的数量必然小于等于 \(\frac{m}{2}\),且 \(\frac{m}{2}\leq \max(y,m-y)\)。因此永远可以做到答案为 \(dmax+1\)。我们只需要按上面的步骤贪心计算即可。

    接下来就是一些恶心人的地方了,正如你们所见,我一开被疯狂卡空间,原因是我开的 \(f,k\) 数组都开了 \(500*100000\)。后来我想到一个绝妙的解决方法。。。

    我们可以只开 \(500*100000\) 的空间。我们想,到底 \(a\) 代表 a 的数量还是 b 的熟练干其实不重要,所以如果 \(a>\frac{n}{2}\),就让 \(a=n-a\),只是标记一下,最后换回来即可。

    代码挺长,下标特别乱。

    //Don't act like a loser.
    //This code is written by huayucaiji
    //You can only use the code for studying or finding mistakes
    //Or,you'll be punished by Sakyamuni!!!
    #include<bits/stdc++.h>
    using namespace std;
    
    int read() {
    	char ch=getchar();
    	int f=1,x=0;
    	while(ch<'0'||ch>'9') {
    		if(ch=='-')
    			f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') {
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return f*x;
    }
    
    const int MAXN=1e5+10,inf=1e9;
    
    int n,cnt,m,a,dmax,b;
    int h[MAXN],sz[MAXN],k[500][MAXN>>1],f[500][MAXN>>1],tot[500],d[500],index[MAXN],size[MAXN];
    set<int> s; 
    char ans[MAXN];
    vector<int> dep[MAXN],vec[MAXN];
    bool rev;
    
    struct edge {
    	int v,nxt;
    }e[MAXN<<1];
    
    void addedge(int u,int v) {
    	e[++cnt].v=v;
    	e[cnt].nxt=h[u];
    	h[u]=cnt;
    }
    void insert(int u,int v) {
    	addedge(u,v);
    	addedge(v,u);
    }
    
    void dfs(int u,int fa,int d) {
    	dmax=max(d,dmax);
    	dep[d].push_back(u);
    	size[u]=1;
    	for(int i=h[u];i;i=e[i].nxt) {
    		int v=e[i].v;
    		
    		if(v!=fa) {
    			dfs(v,u,d+1);
    			size[u]+=size[v];
    			//子树大小
    		}
    	}
    }
    
    bool cmp(int x,int y) {
    	return size[x]<size[y];
    }
    
    signed main() {
    	cin>>n>>a;
    	b=n-a;
    	if(a>b) {
    		swap(a,b);
    		rev=1;
    	}
    	for(int i=2;i<=n;i++) {
    		insert(i,read());
    	}
    	dfs(1,0,1);
    	for(int i=1;i<=n;i++) {
    		sz[i]=dep[i].size();
    		//深度为 i 的深度集合 
    		if(!sz[i]) {
    			continue;
    		}
    		sort(dep[i].begin(),dep[i].end(),cmp);
    		vec[sz[i]].push_back(i);
    		//大小为 sz_i 的深度集合 
    		if(s.find(sz[i])==s.end()) {
    			d[++m]=sz[i];
    			index[sz[i]]=m;
    			tot[m]++;
    			s.insert(sz[i]);
    		} 
    		else {
    			tot[index[sz[i]]]++;
    		}
    	}
    	
    	f[0][0]=1;
    	for(int i=1;i<=m;i++) {
    		fill(k[i],k[i]+n+1,-inf);
    		for(int j=0;j<=a;j++) {
    			if(f[i-1][j]) {
    				f[i][j]=1;
    				k[i][j]=0;
    			}
    			else {
    				if(j>=d[i])
    					k[i][j]=k[i][j-d[i]]+1;
    				if(k[i][j]>=0&&j>=k[i][j]*d[i]&&k[i][j]<=tot[i]) {
    					f[i][j]=f[i-1][j-k[i][j]*d[i]];
    				}
    				else {
    					f[i][j]=0;
    					k[i][j]=-inf;
    				}
    			}
    		}
    	}
    	
    	if(f[m][a]) {
    		printf("%d\n",dmax);
    		for(int i=m;i;i--) {
    			for(int j=0;j<k[i][a];j++) {
    				for(int l=sz[vec[d[i]][j]]-1;l>=0;l--) {
    					ans[dep[vec[d[i]][j]][l]]='a';
    				}
    			}
    			a-=d[i]*k[i][a];
    		}
    		for(int i=1;i<=n;i++) {
    			if(ans[i]!='a') {
    				ans[i]='b';
    			}
    		}
    		if(!rev)
    			printf("%s\n",ans+1);
    		else {
    			for(int i=1;i<=n;i++) {
    				printf("%c",'a'+'b'-ans[i]);
    			}
    		}
    	}
    	else {
    		/*if(b==489) {
    			printf("henhenhenaAAAAAAA\n");
    			return 0;
    		}*/
    		printf("%d\n",dmax+1);
    		char x='a',y='b';
    		if(rev) {
    			swap(a,b);
    		}
    		for(int i=1;i<=n;i++) {
    			if(!sz[i]) {
    				break;
    			}
    			if(b>a) {
    				swap(x,y);
    				swap(a,b);
    			}
    			while(sz[i]&&a) {
    				ans[dep[i][sz[i]-1]]=x;
    				sz[i]--;
    				a--;
    			}
    			while(sz[i]&&b) {
    				ans[dep[i][sz[i]-1]]=y;
    				sz[i]--;
    				b--;
    			}
    		} 
    		printf("%s\n",ans+1);
    	}
    
    	//fclose(stdin);
    	//fclose(stdout);
    	return 0;
    }
    /*
    9 6
    1 2 2 4 4 4 3 1
    */ 
    
    
  • 相关阅读:
    windows 2008 server下载地址
    C#多线程强制退出程序
    MyBatis insert 返回主键的方法(oracle和mysql)
    JVM内存管理:深入Java内存区域与OOM、深入垃圾收集器与内存分配策略
    xsocket源码解读
    JVM调优总结
    Oracle Exp/Imp 调优
    Oracle create tablespace 创建表空间语法详解
    深入研究java.lang.ThreadLocal类
    oracle 启动关闭以及监听启动关闭命令
  • 原文地址:https://www.cnblogs.com/huayucaiji/p/CF1481F.html
Copyright © 2011-2022 走看看