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\n",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",n);
	FOR(i,0,n-1) printf("%d%c",a[i]," \n"[i==n-1]);
	printf("%d\n",m);
	FOR(i,0,m-1) printf("%d%c",b[i]," \n"[i==m-1]);
	return 0;
}

C. 组合数问题

卢卡斯定理拆成 \(p\) 进制下的问题,我们将 \(n,m\)\(p\) 进制下分解,如果 \(\bmod 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(q\log_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\n",dp(1,0,1,1,1));
	}
	return 0;
}
posted @ 2020-10-20 21:48  RainAir  阅读(55)  评论(0编辑  收藏  举报