• contest3 题解


    A. 小星星

    考虑一个大家都会的暴力 dp:设 (f_{v,i,S}) 表示确定好了 (v) 的子树内的点映射到集合 (S) 上,并且 (v) 对应 (i) 的方案数。合并子树的时候要保证 (S1)(S2) 不交,然后根据图是否有边来判断。

    复杂度瓶颈显然在每次转移时的枚举子集。我们考虑我们这个第三维的作用是什么:是为了防止多个点映射到同一个点上。我们如果限定这个数的映射只能用 (S) 然后跑上面去掉第三维后的 dp ,设得到的方案数为 (f(S)),那么 (f(S)) 表示 (n) 个点,映射到的点的集合 (subseteq S) 的方案数,而我们要求 (g(S)) 表示 (n) 个点,映射到的点的集合 (=S) 的个数。

    那么我们有

    [f(S) = sum_{T subseteq S} g(T) ]

    根据子集容斥,可以得到

    [g(S) = sum_{T subseteq S} (-1)^{|S|-|T|}f(T) ]

    所以我们只需要枚举子集,(O(2^n n^3)) 才能过。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 17 + 1;
    
    int n,m;
    int G[MAXN][MAXN];
    std::vector<int> T[MAXN];
    LL f[MAXN][MAXN];
    std::vector<int> lim;
    
    inline void merge(int x,int y){
    	for(auto i:lim){
    		LL s = 0;
    		for(auto j:lim){
    			if(!f[y][j]) continue;
    			if(!G[i+1][j+1]) continue;
    			s += f[y][j];
    		}
    		f[x][i] *= s;
    	}
    }
    
    inline void dfs(int v,int fa=0){
    	for(auto i:lim) f[v][i] = 1;
    	for(auto x:T[v]){
    		if(x == fa) continue;
    		dfs(x,v);
    		merge(v,x);
    	}
    }
    
    int main(){
    	scanf("%d%d",&n,&m);
    	FOR(i,1,m){
    		int u,v;scanf("%d%d",&u,&v);
    		G[u][v] = G[v][u] = 1;
    	}
    	FOR(i,1,n-1){
    		int u,v;scanf("%d%d",&u,&v);
    		T[u].pb(v);T[v].pb(u);
    	}
    	LL res = 0;
    	FOR(S,1,(1<<n)-1){
    		if(__builtin_popcount(S) == 1) continue;
    		LL gx = 0;lim.clear();CLR(f,0);
    		FOR(i,0,n-1) if((S>>i)&1) lim.pb(i);
    		dfs(1);
    		FOR(i,0,n-1) if((S>>i)&1) gx += f[1][i];
    		if((n-__builtin_popcount(S))&1) gx = -gx;
    		res += gx;
    	}
    	printf("%lld
    ",res);
    	return 0;
    }
    

    B. 魔法小程序

    首先可以把 (a) 的长度缩短的 (O(log n)) 级别。注意需要把 (1) 去掉。

    然后我们看一下给出的程序在做什么:相当于用那个递归程序写成若干个字符串后,求高维前缀和。

    那么我们考虑高维前缀和如何做:在第一层的时候我们设 (x=a_0),按照 (x) 分组,每组内都是第一位递增的,所以每组内部都 (b_i += b_{i-1}),然后我们到下一层,那么原来的每 (x) 组在下一层中的最低位都是一样的,设 (y=a_0a_1),按照 (y) 分组,也是在每一组内 (b_i += b_{i-x}),剩下的也是类似做。。

    那么高维前缀和的逆就是倒着做,每次倒着减掉就行了。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 2e6 + 5;
    int a[MAXN],n,b[MAXN],m;
    std::vector<int> basis;
    
    int main(){
    	scanf("%d",&n);
    	FOR(i,0,n-1) scanf("%d",a+i);
    	scanf("%d",&m);
    	FOR(i,0,m-1) scanf("%d",b+i);
    	basis.pb(1);
    	FOR(i,0,n-1){
    		if(a[i] == 1) continue;
    		if(basis.back()*a[i] > m-1) break;
    		basis.pb(basis.back()*a[i]);
    	}
    	basis.pb(m);
    	ROF(cc,(int)basis.size()-1,1){
    		for(int i = 0;i < m;i += basis[cc]){
    			int j = std::min(m-1,i+basis[cc]-1);
    			while(j >= i+basis[cc-1]) b[j] -= b[j-basis[cc-1]],--j;
    		}
    	}
    	printf("%d
    ",n);
    	FOR(i,0,n-1) printf("%d%c",a[i]," 
    "[i==n-1]);
    	printf("%d
    ",m);
    	FOR(i,0,m-1) printf("%d%c",b[i]," 
    "[i==m-1]);
    	return 0;
    }
    

    C. 组合数问题

    卢卡斯定理拆成 (p) 进制下的问题,我们将 (n,m)(p) 进制下分解,如果 (mod p=0) 那么就说明存在一位 (a_i < b_i)

    所以可以设计数位dp:设 (f_{i,0/1,0/1,0/1,0/1}) 表示考虑前 (i) 位,是否已经有 (a_i<b_i)(j) 是否卡 (i) 上界,(i) 是否卡 (n) 上界,(j) 是否卡 (m) 上界,枚举转移即可。复杂度 (O(qlog_p^3))

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 60+5;
    const int ha = 1e9 + 7;
    int a[MAXN],b[MAXN],len;
    
    inline void add(int &x,int y){
    	x += y;if(x >= ha) x -= ha;
    }
    
    /*
    C(i,j)
    f[考虑到哪一位][是否有i<j][j是否卡i上界][i是否卡n上界][j是否卡m上界]
    */
    
    int f[MAXN][2][2][2][2],k;
    
    inline int dp(int i,int ok,int b1,int b2,int b3){
    	// DEBUG(i);
    	if(i > len) return ok;
    	if(f[i][ok][b1][b2][b3] != -1) return f[i][ok][b1][b2][b3];
    	int &res = f[i][ok][b1][b2][b3];res = 0;
    	int lim1 = b2?a[i]:(k-1);
    	FOR(x,0,lim1){
    		int lim2 = std::min(b1?x:(k-1),b3?b[i]:(k-1));
    		FOR(y,0,lim2){
    			add(res,dp(i+1,ok|(x<y),b1&(y==x),b2&(x==a[i]),b3&(y==b[i])));
    		}
    	}
    	return res;
    }
    
    int main(){
    	int T;scanf("%d%d",&T,&k);
    	while(T--){
    		LL n,m;scanf("%lld%lld",&n,&m);
    		m = std::min(n,m);CLR(a,0);CLR(b,0);
    		CLR(f,-1);
    		int lena = 0,lenb = 0;
    		while(n) a[++lena] = n%k,n /= k;
    		while(m) b[++lenb] = m%k,m /= k;
    		len = std::max(lena,lenb);
    		std::reverse(a+1,a+len+1);std::reverse(b+1,b+len+1);
    		printf("%d
    ",dp(1,0,1,1,1));
    	}
    	return 0;
    }
    
  • 相关阅读:
    ....
    CodeForces 375A(同余)
    POJ 2377 Bad Cowtractors (最小生成树)
    POJ 1258 AgriNet (最小生成树)
    HDU 1016 Prime Ring Problem(全排列)
    HDU 4460 Friend Chains(bfs)
    POJ 2236 Wireless Network(并查集)
    POJ 2100 Graveyard Design(尺取)
    POJ 2110 Mountain Walking(二分/bfs)
    CodeForces 1059B Forgery(模拟)
  • 原文地址:https://www.cnblogs.com/rainair/p/14305237.html
Copyright © 2020-2023  润新知