• DFS对树的遍历及一些优化


    树的遍历

    树的深度优先遍历

    调用根结点 d f s ( r o o t ) dfs(root) dfs(root)

    void dfs(int x)
    {
    	vis[x]=1;
    	for(int i=head[x];i;i=nex[i])
    	{
    		int y=ver[i];
    		if(vis[y]) continue;
    		dfs(y);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    树的DFS序

    对树进行深度优先遍历时,对于每个结点,在刚进入递归后以及即将回溯时各记录一次结点编号,每个结点x在序列中恰好出现两次。

    void dfs(int x)
    {
    	a[++m]=x;
    	vis[x]=1;
    	for(int i=head[x];i;i=nex[i])
    	{
    		int y=ver[i];
    		if(vis[y]) continue;
    		dfs(y);
    	}
    	a[++m]=x;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    树的深度

    void dfs(int x)
    {
    	vis[x]=1;
    	for(int i=head[x];i;i=nex[i])
    	{
    		int y=ver[i];
    		if(vis[y]) continue;
    		depth[y]=depth[x]+1;
    		dfs(y);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    树的重心

    有的时候题目不给明哪个是根结点(所有节点都可以是根结点),可以通过一次dfs找到树的重心来尽量避免“树大根深”的情况。
    树的重心:设max_part(x)表示在删除结点x后产生的子树中,最大的一棵的大小。使得max_part取最小值的结点p即为树的重心。

    void dfs(int x)
    {
    	vis[x]=1;size[x]=1;//子树x的大小 
    	int max_part=0;
    	for(int i=head[x];i;i=nex[i])
    	{
    		int y=ver[i]; 
    		if(vis[y]) continue;
    		dfs(y);
    		size[x]+=size[y];
    		max_part=max(max_part,size[y]);//找最大子树 
    	}
    	max_part=max(max_part,n-size[x]);
    	if(max_part<ans)
    	{
    		ans=max_part;//记录重心对应的最大子树规模 
    		pos=x;//记录重心 
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2018HBCPC的一道题:
    在这里插入图片描述
    找到一个点x,使得 F ( x ) = ∑ w ( i ) ∗ d ( x , i ) F(x)=\sum{w(i)}*d(x,i) F(x)=w(i)d(x,i)最小,w(i)是结点i的权值,d(x,i)是d到i的距离。

    #include
    #define llg long long
    #define inf 0x3ffffff
    
    using namespace std;
    
    const int N=1e6+10;
    int head[N],ver[N],nex[N],val[N],tot,n,dis[N],vis[N];
    int size[N],pos,max_size,ans=inf;
    long long minn=1e15;
    
    inline int read()
    {
    	int x=0,f=1;char ch=getchar();
    	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    	return x*f;
    }
    
    void add(int x,int y,int z)
    {
    	ver[++tot]=y,dis[tot]=z,nex[tot]=head[x],head[x]=tot;
    }
    
    void dfs1(int x)
    {
    	vis[x]=1,size[x]=1;
    	int max_size=0;
    	for(int i=head[x];i;i=nex[i])
    	{
    		if(vis[ver[i]]) continue;
    		vis[ver[i]]=1;
    		dfs1(ver[i]);
    		size[x]+=size[ver[i]];
    		max_size=max(max_size,size[ver[i]]);
    	}
    	max_size=max(max_size,n-size[x]);
    	if(max_size<ans)
    	{
    		ans=max_size;
    		pos=x;
    	}
    }
    long long dfs(int x,int d)
    {
    	//cout<<"x:"<
    	vis[x]=1;
    	long long sum=0;
    	for(int i=head[x];i;i=nex[i])
    	{
    		if(vis[ver[i]]) continue;
    		vis[ver[i]]=1;
    		sum+=val[ver[i]]*(dis[i]+d);
    		sum+=dfs(ver[i],dis[i]+d);
    	}
    	return sum;
    }
    
    int main()
    {
    	n=read();
    	for(int i=1;i<n;i++)
    	{
    		int x=read(),y=read(),z=read();
    		add(x,y,z);
    		add(y,x,z);
    	}
    	for(int i=1;i<=n;i++)
    		val[i]=read();
    	dfs1(1);//找重心 
    	//cout<<"pos:"<
    	memset(vis,0,sizeof(vis));
    	cout<<dfs(pos,0);
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    连通块划分

    void dfs(int x)
    {
    	v[x]=cnt; 
    	int max_part=0;
    	for(int i=head[x];i;i=nex[i])
    	{
    		int y=ver[i]; 
    		if(vis[y]) continue;
    		dfs(y);
    	}
    }
    int main()
    {
    	for(int i=1;i<=n;i++)
    	{
    		if(!v[i])
    		{
    			cnt++;
    			dfs(i);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    剪枝

    1. 优化搜索顺序
    2. 排除冗余信息
    3. 可行性剪枝(照当前分支这样走后面不可能达成)
    4. 最优性剪枝(照当前分支这样走的最优解都比当前解差)
    5. 记忆化搜索

    165. 小猫爬山

    #include
    using namespace std;
    
    int n,w,c[20],a[20],cnt,ans;
    
    void dfs(int num,int cnt)
    {
    	if(cnt>=ans) return;//最优性剪枝 
    	if(num==n+1)//n只喵都塞进去了 
    	{
    		ans=min(ans,cnt);
    		return;
    	}
    	for(int i=1;i<=cnt;i++)
    	{
    		if(w-a[i]>=c[num])
    		{
    			a[i]+=c[num];
    			dfs(num+1,cnt);
    			a[i]-=c[num];
    		}
    	}
    	a[cnt+1]=c[num];
    	dfs(num+1,cnt+1);
    	a[cnt+1]=0;
    }
    int main()
    {
    	cin>>n>>w;
    	ans=n;
    	for(int i=1;i<=n;i++)
    		cin>>c[i];
    	sort(c+1,c+n+1);
    	reverse(c+1,c+n+1);//优化搜索顺序,重的喵放在前面
    	dfs(1,0);
    	cout<<ans;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    166. 数独

    主要考虑优化搜索顺序,从可选的数少的空格开始试探。所以我们在每次搜索时都判断一下哪个格子的可选到数最小。

    #include 
    using namespace std;
    const int N=9;
    string str;
    int mp[1<<N],nums[1<<N],row[N],col[N],cell[N][N];
    
    inline int lowbit(int x)
    {
    	return x&(-x);
    }
    
    int get(int x,int y)
    {
    	return row[x]&col[y]&cell[x/3][y/3];//求出这一个位置可能会被填哪些数字 
    }
    
    void init()//初始化:所有位所有数字都可以填 
    {
    	for(int i=0;i<N;i++) 
    		row[i]=col[i]=(1<<N)-1;
    	for(int i=0;i<3;i++)
    		for(int j=0;j<3;j++)
    			cell[i][j]=(1<<N)-1;
    }
    
    bool dfs(int cnt)
    {
    	if(cnt==0) return true;//所有待填数字都已经被填上 
    	int minn=10,x,y;
    	for(int i=0;i<N;i++)
    		for(int j=0;j<N;j++)
    		{
    			if(str[i*9+j]=='.')
    			{
    				int temp=nums[get(i,j)];
    				if(temp<minn)
    				{
    					minn=temp;
    					x=i,y=j;
    				}
    			}
    		}//找到所有剩下的待填格中数字集合最小的一个,是一个剪枝过程 
    	for(int i=get(x,y);i;i-=lowbit(i))//枚举这一格所有可能填的数字 
    	{
    		int t=mp[lowbit(i)];
    		row[x]-=(1<<t);
    		col[y]-=(1<<t);
    		cell[x/3][y/3]-=(1<<t);
    		str[x*9+y]='1'+t;
    		if(dfs(cnt-1)) return true;
    		row[x]+=(1<<t);
    		col[y]+=(1<<t);
    		cell[x/3][y/3]+=(1<<t);
    		str[x*9+y]='.';//复原 
    	}
    	return false;
    }
    
    
    int main()
    {
    	for(int i=0;i<N;i++) mp[1<<i]=i;//预先存下二进制1所在的位置 
    	for(int i=1;i<(1<<N);i++)
    		for(int j=i;j;j-=lowbit(j)) nums[i]++;
    		//预先存下每个之后可能会枚举到的二进制数内有几个1 
    	while(cin>>str&&str[0]!='e')
    	{
    		init();
    		int cnt=0;
    		for(int i=0,k=0;i<9;i++)
    			for(int j=0;j<9;j++,k++)
    			{
    				if(str[k]!='.')
    				{
    					int t=str[k]-'1';//1~9映射到0~8 
    					row[i]-=(1<<t);
    					col[j]-=(1<<t);
    					cell[i/3][j/3]-=(1<<t);
    				}//这一位数字是题给的,把它存进对应位置 
    				else cnt++;//待填数字+1 
    			}
    		dfs(cnt);
    		cout<<str<<endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    P1120 小木棍

    #include
    using namespace std;
    int n,a[70],sum,maxn,len,cnt,v[70];
    
    bool dfs(int stick,int cab,int last)
    //正在拼第stick根木棒(stick-1)根已拼好
    //第stick根木棒的当前长度为cab
    //拼接到第stivk根木棒中的上一根小木棍为last
    {
    	if(stick>cnt) return true;//所有木棒都拼好
    	if(cab==len) return dfs(stick+1,0,1);//第stick个拼好
    	int fail=0;
    	for(int i=last;i<=n;i++)//木棍长度递减(从last开始枚举)
    	{
    		if(!v[i]&&cab+a[i]<=len&&fail!=a[i])
    		//排除冗余:之前搜过这么长的木棍,已经fail过了,就不要一fail再fail了
    		{
    			v[i]=1;
    			if(dfs(stick,cab+a[i],i+1)) return true;
    			fail=a[i];
    			v[i]=0;
    			if(cab==0||cab+a[i]==len)
    				return false;
    			//cab==0,尝试拼入第一根木棍就失败,没必要往后继续了,因为长度为0的木棍都是等效的,怎么拼都是失败
    			//can+a[i]==len这一整根能拼好,但是后面还是不能完全拼好,没必要在这里往更短的试了
    		}
    	}
    	return false;
    }
    
    int main()
    {
    	cin>>n;
    	for(int i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		sum+=a[i];
    		maxn=max(maxn,a[i]);
    	}
    	sort(a+1,a+n+1);
    	reverse(a+1,a+n+1);//优化搜索顺序,优先尝试较长的木棍
    	for(len=maxn;len<=sum;len++)
    	{
    		if(sum%len) continue;
    		cnt=sum/len;//原始木棒长度为len,共有cnt根
    		//cout<<"len:"<
    		memset(v,0,sizeof(v));
    		if(dfs(1,0,1))
    		{
    			cout<<len;
    			break;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    168. 生日蛋糕

    建议直接看视频,躺

    #include
    using namespace std;
    #define inf 1e9
    int n,m,minv[25],mins[25];
    int R[25],H[25],ans=inf;
    
    void dfs(int u,int v,int s)
    // 当前层数,已用体积,已用面积 
    {
    	if(v+minv[u]>n) return;//可行性剪枝
    	if(s+mins[u]>=ans) return;//最优性剪枝
    	if(s+2*(n-v)/R[u+1]>=ans) return;//最优性剪枝
    	if(!u)
    	{
    		if(v==n) ans=s;
    		return;
    	}
    	for(int i=min((int)sqrt(n-v),R[u+1]-1);i>=u;i--)//上下界剪枝
    	{
    		for(int j=min((n-v)/(i*i),H[u+1]-1);j>=u;j--)//上下界剪枝
    		{
    			H[u]=j,R[u]=i;
    			int t=(u==m?i*i:0);
    			dfs(u-1,v+i*i*j,s+2*i*j+t);
    		}
    	}
    }
    
    int main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=m;i++)
    	{
    		minv[i]=minv[i-1]+i*i*i;
    		mins[i]=mins[i-1]*2*i*i;
    	}
    	R[m+1]=H[m+1]=inf;
    	dfs(m,0,0);
    	if(ans==inf) ans=0;
    	cout<<ans;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    P1434 [SHOI2002] 滑雪

    记忆化搜索

    #include 
    using namespace std;
    int a[105][105],mem[105][105],ans;
    int dx[4]={1,0,-1,0};
    int dy[4]={0,1,0,-1};
    int R,C;
    int dfs(int x,int y)
    {
    	if(mem[x][y]) return mem[x][y];
    	mem[x][y]=1;
    	for(int i=0;i<4;i++)
    	{
    		int xx=x+dx[i];
    		int yy=y+dy[i];
    		if(a[xx][yy]<a[x][y]&&xx>0&&yy>0&&xx<=R&&yy<=C)
    		{
    			dfs(xx,yy);
    			mem[x][y]=max(mem[x][y],1+mem[xx][yy]);
    		}
    	}
    	return mem[x][y];
    }
    int main()
    {
    	cin>>R>>C;
    	for(int i=1;i<=R;i++)
    	 	for(int j=1;j<=C;j++)
    	 		scanf("%d",&a[i][j]);
    	for(int i=1;i<=R;i++)
    	 	for(int j=1;j<=C;j++)
    	 		ans=max(ans,dfs(i,j));
    	cout<<ans;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    迭代加深

    搜索树规模随着层次的深入增长很快,并且能够确保答案在一个较浅层的结点内——>限制搜索深度,每次在该深度内找不到答案就返回,增加一单位深度后再搜索。这会导致重复搜索浅层,但这比起庞大的子树来微不足道。比如是二叉树, 1 + 2 + 2 2 + 2 3 + . . . + 2 d e p t h − 1 < 2 d e p t h 1+2+2^{2}+2^{3}+...+2^{depth-1}<2^{depth} 1+2+22+23+...+2depth1<2depth

    170. 加成序列

    #include
    using namespace std;
    const int N=10010;
    int path[N],st[N],n;
    
    bool dfs(int u,int depth)
    {
    	if(u==depth) return path[u-1]==n;//最后一个数是n即找到答案 
    	bool st[N]={0};
    	for(int i=u-1;i>=0;i--)
    		for(int j=i;j>=0;j--)//优化搜索顺序,i和j从大到小枚举
    		{
    			int temp=path[i]+path[j];
    			if(!st[temp]&&temp<=n&&path[u-1]<temp)
    			//!st[temp]剪枝,因为有多种可能的 temp=path[i]+path[j],但是只需要搜索一次
    			//这个数一定<=n且比前一层的数大 
    			{
    				path[u]=temp;
    				st[temp]=1;
    				if(dfs(u+1,depth)) return true;
    			}
    		}
    		
    	return false;
    }
    int main()
    {
    	path[0]=1;
    	while(cin>>n,n)
    	{
    		int depth=1;
    		while(!dfs(1,depth)) depth++;//迭代加深的过程
    		for(int i=0;i<depth;i++)
    			cout<<path[i]<<' ';
    		cout<<endl;
    	}
    	return 0; 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    双向搜索

    从初态和终态出发各搜索一半状态,产生两棵深度减半的搜索树,在中间交会、组合成最终的答案。

    171. 送礼物

    把礼物分成前一半,后一半,对于前一半,就暴搜,记录每次搜索完成后的总重量;对于后一半,也暴搜,每次搜索完成后与前一半合并。怎么合并?在前一半二分出一个重量,这个重量和后一半暴搜完的重量加起来不超过w而且尽可能大。用ans记录最大的一个合并总重。

    #include
    #define llg long long
    #define inf 0x3ffffff
    
    using namespace std;
    
    const int N=46;
    llg weight[1<<24],ans,g[N],w,n,k,cnt,tot;
    
    void dfs1(int v,int s)
    {
    	if(v==k)
    	{
    		weight[cnt++]=s;
    		return;
    	}
    	if((llg)s+g[v]<=w) dfs1(v+1,s+g[v]);//选 
    	dfs1(v+1,s);//不选 
    }
    
    void dfs2(int v,int s)
    {
    	if(v==n)
    	{
    		llg l=0,r=tot-1;
    		while(l<r)
    		{
    			llg mid=(l+r+1)>>1;
    			if(weight[mid]+(llg)s<=w) l=mid;
    			else r=mid-1;
    		}
    		ans=max(ans,s+weight[l]);
    		return;
    	}
    	if((llg)s+g[v]<=w) dfs2(v+1,s+g[v]);//选 
    	dfs2(v+1,s);//不选 
    }
    int main()
    {
    	cin>>w>>n;
    	for(int i=0;i<n;i++)
    		scanf("%lld",&g[i]);
    	sort(g,g+n);
    	reverse(g,g+n);
    	k=n/2;
    	dfs1(0,0);
    	sort(weight,weight+cnt);
    	tot=unique(weight,weight+cnt)-weight;
    	dfs2(k,0);
    	cout<<ans;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
  • 相关阅读:
    【SA8295P 源码分析 (四)】35 - QNX侧 Marvell 88Q5152 Phy_Switch 导通实录(硬核)
    什么是子网掩码?
    【HDFS】Hadoop-RPC:客户端侧通过Client.Connection#sendRpcRequest方法发送RPC序列化数据
    排序算法详解(java代码实现)
    数据结构 - 链表
    (论文翻译)UFO: Unified Feature Optimization——UFO:统一特性优化
    Node.js 入门教程 20 查看 npm 包安装的版本 & 21 安装 npm 包的旧版本
    拨开发展迷雾,将“智慧”嵌入全业务场景【2022戴尔科技峰会预告】
    解决驱动开发中并发和竞争中的问题----------自旋锁
    【lesson2】数据库的库操作
  • 原文地址:https://blog.csdn.net/m0_60630928/article/details/126063229