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 ;
    }
    
  • 相关阅读:
    mac 10.15.7 修改PATH
    oc 属性类型一般用法
    ubuntu解压zip文件名乱码
    telnet 退出
    docker 根据容器创建镜像
    mac android adb device 没有显示设备
    Yii2 查看所有的别名 alias
    Yii2 App Advanced 添加 .gitignore
    ubuntu 18.04 搜狗突然就提示乱码
    An error occured while deploying the file. This probably means that the app contains ARM native code and your Genymotion device cannot run ARM instructions. You should either build your native code to
  • 原文地址:https://www.cnblogs.com/beretty/p/10288311.html
Copyright © 2011-2022 走看看