zoukankan      html  css  js  c++  java
  • [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。

    输入输出样例

    输入样例#1:

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

    输出样例#1:

    6


    题解

    首先看到n<=17,第一反应就是撞鸭
    然后想了一会儿觉得是可行的,就是设(f[i][j][S])表示在树上的点i对应图上的点j,且i的子树选取图上的点的情况是(S)
    然后dp就直接无脑枚举这个点u对应图上的什么,然后再枚举点u的状态,然后再枚举v对应图上的什么,然后再枚举点v的状态,然后ta就(MLE+TLE)成70分了其实不开longlong卡一下空间可以到90

    #include<vector>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    # define LL long long
    const int M = 18 ;
    const int N = (1 << 17) + 1 ;
    using namespace std ;
    
    int n , m , size[M] ;
    vector < int > G[M] , vec[M] , sit[M] ;
    LL f[M][M][N] ;
    LL Ans ;
    inline int Gsz(int x) {
        int ret = 0 ;
        for(int i = 1 ; i <= n ; i ++) if(x & (1 << (i - 1))) ++ ret ;
        return ret ;
    }
    void dfs(int u , int father) {
        size[u] = 1 ; for(int i = 1 ; i <= n ; i ++) f[u][i][(1 << (i - 1))] = 1 ;
        for(int e = 0 , v ; e < vec[u].size() ; e ++) {
            v = vec[u][e] ; if(v == father) continue ; dfs(v , u) ; 
            for(int i = 0 , S ; i < sit[size[u]].size() ; i ++) {
                S = sit[size[u]][i] ;
                for(int id = 1 ; id <= n ; id ++) {
                    if(!(S & (1 << (id - 1)))) continue ;
                    for(int j = 0 , T ; j < sit[size[v]].size() ; j ++) {
                        T = sit[size[v]][j] ;
                        if(S & T) continue ;
                        for(int k = 0 , vid ; k < G[id].size() ; k ++) {
                            vid = G[id][k] ;
                            if(!(T & (1 << (vid - 1)))) continue ;
                            f[u][id][S | T] += f[u][id][S] * f[v][vid][T] ;
                        }					
                    }
                } 
            }
            size[u] += size[v] ;
        }
    }
    int main() {
        scanf("%d%d",&n,&m) ;
        for(int i = 1 , u , v ; i <= m ; i ++) {
            scanf("%d%d",&u,&v) ;
            G[u].push_back(v) ;
            G[v].push_back(u) ;
        }
        for(int i = 1 , u , v ; i < n ; i ++) {
            scanf("%d%d",&u,&v) ;
            vec[u].push_back(v) ;
            vec[v].push_back(u) ;
        }
        for(int i = 0 ; i < (1 << n) ; i ++) sit[Gsz(i)].push_back(i) ;
        dfs(1 , 1) ;
        for(int i = 1 ; i <= n ; i ++) Ans += f[1][i][(1 << n) - 1] ;
        printf("%lld
    ",Ans) ;
        return 0 ;
    }
    

    然后这么做复杂度显然不对,我们考虑一个正确的方法
    我们考虑这道题用撞鸭的目的是什么?
    判重!
    就是为了每个点映射图上的点不要重复
    所以我们可以考虑容斥
    先让每个点随便对应图上的点,只要能对应上就行(两点之间有边),然后减去有一个点被选重复的方案数,再加上有两个点被选重复的方案数-...+..,以此类推
    复杂度就是(2^n*n^3)

    #include<vector>
    #include<cstdio>
    # define LL long long
    const int M = 18 ;
    using namespace std ;
    int n , m , Sit ;
    vector < int > G[M] , vec[M] ;
    LL f[M][M] , Ans ;
    void dfs(int u , int father) {
    	for(int i = 1 ; i <= n ; i ++) f[u][i] = 1 ;
    	for(int i = 0 , v ; i < vec[u].size() ; i ++) {
    		v = vec[u][i] ; if(v == father) continue ; dfs(v , u) ;
    		for(int id = 1 ; id <= n ; id ++) {
    			if(Sit & (1 << (id - 1))) continue ; LL temp = 0 ;
    			for(int j = 0 , idv ; j < G[id].size() ; j ++) {
    				idv = G[id][j] ; if(Sit & (1 << (idv - 1))) continue ;
    			    temp += f[v][idv] ;
    			}
    			f[u][id] *= temp ;
    		}
    	}
    }
    inline void query(int ret) {
    	dfs(1 , 1) ; 
    	for(int i = 1 ; i <= n ; i ++) {
    		Ans += ret * f[1][i] ;
    		for(int j = 1 ; j <= n ; j ++) f[j][i] = 0 ;
    	}
    }
    int main() {
    	scanf("%d%d",&n,&m) ;
    	for(int i = 1 , u , v ; i <= m ; i ++) { 
    		scanf("%d%d",&u,&v) ; 
    		G[u].push_back(v) ; G[v].push_back(u) ; 
    	}
        for(int i = 1 , u , v ; i < n ; i ++) { 
    		scanf("%d%d",&u,&v) ; 
    		vec[u].push_back(v) ; vec[v].push_back(u) ; 
    	}
    	for(int i = 0 , cnt = 0 ; i < (1 << n) ; i ++) {
    		cnt = 0 ; Sit = i ;
    		for(int j = 1 ; j <= n ; j ++) if(i & (1 << (j - 1))) ++ cnt ;
    		cnt % 2 ? query(-1) : query(1) ;
    	}
    	printf("%lld
    ",Ans) ;
    	return 0 ;
    }
    
  • 相关阅读:
    DevExpress的TreeList实现节点上添加自定义右键菜单并实现删除节点功能
    C#中全局作用域的常量、字段、属性、方法的定义与使用
    DevExpress的TreeList的常用属性设置以及常用事件
    DevExpress的TreeList怎样设置数据源,从实例入手
    一、ABP框架框架摘要
    十、LINQ查询之延迟执行
    一、
    二、core abp 数据库迁移
    十一、ASP.NET Boilerplate
    十、ABP
  • 原文地址:https://www.cnblogs.com/beretty/p/10288311.html
Copyright © 2011-2022 走看看