• [洛谷P3349] ZJOI2016 小星星


    问题描述

    小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。

    有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n-1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。

    只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。

    输入格式

    第一行包含个2正整数n,m,表示原来的饰品中小星星的个数和细线的条数。接下来m行,每行包含2个正整数u,v,表示原来的饰品中小星星u和v通过细线连了起来。这里的小星星从1开始标号。保证u≠v,且每对小星星之间最多只有一条细线相连。接下来n-1行,每行包含个2正整数u,v,表示现在的饰品中小星星u和v通过细线连了起来。保证这些小星星通过细线可以串在一起。n<=17,m<=n*(n-1)/2

    输出格式

    输出共1行,包含一个整数表示可能的对应方式的数量。如果不存在可行的对应方式则输出0。

    样例输入

    4 3
    1 2
    1 3
    1 4
    4 1
    4 2
    4 3

    样例输出

    6

    解析

    首先考虑暴力中的暴力:直接枚举树上的点对应的原图上点的序号,再验证是否可行。

    显然这个暴力没有多大的意义,但它可以作为正解的启发。设 (f_{i,j}) 表示考虑 (i) 的子树,且 (i) 对应原图上的 (j) 的方案数。那么 (i) 的所有子节点必须在原图上对应与 (j) 相连。我们可以得到如下转移方程:

    [f_{u,i}=prod_{vin u} sum_{j=1}^n f_{v,j} imes g_{i,j} ]

    其中 (g) 是原图的邻接矩阵。但是,这样做没有考虑不能重复映射的条件。最后映射集合中的点可能少于 (n) 个。那接下来我们可以强制只能映射到 (n-1) 个点,然后用总方案数减去这里计算得到的方案数。在这个过程中,我们又会遇到一样的问题,我们要把多减的加回来。因此通过容斥原理,我们枚举映射集合,每次从图上删除不在映射集合里的点,然后 (O(n^3)) DP即可。

    代码

    #include <iostream>
    #include <cstdio>
    #define N 22
    using namespace std;
    int head[N],ver[N*2],nxt[N*2],l;
    int n,m,i,j;
    long long f[N][N],ans;
    bool g[N][N],g1[N][N],vis[N];
    int read()
    {
    	char c=getchar();
    	int w=0;
    	while(c<'0'||c>'9') c=getchar();
    	while(c<='9'&&c>='0'){
    		w=w*10+c-'0';
    		c=getchar();
    	}
    	return w;
    }
    void insert(int x,int y)
    {
    	l++;
    	ver[l]=y;
    	nxt[l]=head[x];
    	head[x]=l;
    }
    void dp(int x,int pre)
    {
    	for(int i=1;i<=n;i++) f[x][i]=1;
    	for(int i=head[x];i;i=nxt[i]){
    		int y=ver[i];
    		if(y!=pre){
    			dp(y,x);
    			for(int j=1;j<=n;j++){
    				long long tmp=0;
    				for(int k=1;k<=n;k++){
    					if(g[j][k]) tmp+=f[y][k];
    				}
    				f[x][j]*=tmp;
    			}
    		}
    	}
    }
    void dfs(int x)
    {
    	if(x==n+1){
    		int cnt=0;
    		for(int i=1;i<=n;i++){
    			if(vis[i]){
    				for(int j=1;j<=n;j++) g[i][j]=g[j][i]=0;
    				cnt++;
    			}
    		}
    		dp(1,0);
    		if(cnt%2==0){
    			for(int i=1;i<=n;i++) ans+=f[1][i];
    		}
    		else{
    			for(int i=1;i<=n;i++) ans-=f[1][i];
    		}
    		for(int i=1;i<=n;i++){
    			for(int j=1;j<=n;j++) g[i][j]=g1[i][j];
    		}
    		return;
    	}
    	dfs(x+1);
    	vis[x]=1;dfs(x+1);vis[x]=0;
    }
    int main()
    {
    	n=read();m=read();
    	for(i=1;i<=m;i++){
    		int u=read(),v=read();
    		g[u][v]=g[v][u]=1;
    	}
    	for(i=1;i<n;i++){
    		int u=read(),v=read();
    		insert(u,v);insert(v,u);
    	}
    	for(i=1;i<=n;i++){
    		for(j=1;j<=n;j++) g1[i][j]=g[i][j];
    	}
    	dfs(1);
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    一个web程序员的年终总结
    编程要诀-心态
    初识vps,域名与购买,初步配置
    一个好的学习方法真的很重要——费曼学习法
    [译]C# 7系列,Part 8: in Parameters in参数
    Dalsa 8K彩色相机Camera link C#采图
    精简Command版SqlHelper
    ASP.NET MVC模块化开发——动态挂载外部项目
    net core WebApi——依赖注入Autofac
    .NET Core 3 WPF MVVM框架 Prism系列之命令
  • 原文地址:https://www.cnblogs.com/LSlzf/p/13388039.html
Copyright © 2020-2023  润新知