【CodeForces】Codeforces Round #589 (Div. 2) 题解

CodeForces Round #589(Div. 2)题解

A.Distinct Digits

◇题目传送门◆

题目大意

找出在区间 [ L , R ] [L,R] [L,R]中的任意一个各位数都不相同的数。

分析

暴力枚举并判断即可。

参考代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

bool vis[15];
bool check(int a) {
	memset(vis, false, sizeof vis);
	while(a != 0) {
		if(vis[a % 10]) return false;
		vis[a % 10] = true;
		a /= 10;
	}
	return true;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	int l, r;
	scanf("%d %d", &l, &r);
	for(int i = l; i <= r; i++)
		if(check(i)) {
			printf("%d\n", i);
			return 0;
		}
	puts("-1");
	return 0;
}

B.Filling the Grid

◇题目传送门◆

题目大意

在一个 H × W H\times W H×W的矩形方格中,要求第 i i i行上从第一个开始有连续的 c i c_i ci个格子涂色,第 j j j列从第一个开始有连续的 r j r_j rj个连续的格子,在每行每列之后要求空一个格子,求总共有多少种涂色方案。

分析

考虑维护两种标记,一种是必须涂色的标记,另一种是必须空着的标记,如果一个位置上有两种不同的标记,那么这就不合法,否则记录上两种标记都没有的方格数,答案即是2的无标记的方格数次方。

参考代码

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 1000;
const ll Mod = 1e9 + 7;

int N, M;
int A[Maxn + 5], B[Maxn + 5];
bool f1[Maxn + 5][Maxn + 5], f2[Maxn + 5][Maxn + 5];

ll QuickPow(ll a, ll k) {
	ll ret = 1;
	while(k) {
		if(k & 1) ret = ret * a % Mod;
		a = a * a % Mod;
		k >>= 1;
	}
	return ret;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d %d", &N, &M);
	for(int i = 1; i <= N; i++) {
		scanf("%d", &A[i]);
		for(int j = 1; j <= A[i]; j++)
			f1[i][j] = true;
		f2[i][A[i] + 1] = true;
	}
	for(int i = 1; i <= M; i++) {
		scanf("%d", &B[i]);
		for(int j = 1; j <= B[i]; j++)
			f1[j][i] = true;
		f2[B[i] + 1][i] = true;
	}
	int tot = 0;
	for(int i = 1; i <= N; i++)
		for(int j = 1; j <= M; j++)
			if(!f1[i][j] && !f2[i][j])
				tot++;
			else if(f1[i][j] && f2[i][j]) {
				puts("0");
				return 0;
			}
	printf("%lld\n", QuickPow(2, tot));
	return 0;
}

C.Primes and Multiplication

◇题目传送门◆

题目大意

定义集合 p r i m e ( x ) prime(x) prime(x) x x x的质因数集合。

定义函数 g ( x , p ) g(x,p) g(x,p)为满足 p k ∣ x p^k|x pkx的最小的 p k p^k pk

定义函数 f ( x , y ) = ∏ i ∈ p r i m e ( x ) g ( y , i ) f(x,y)=\prod_{i\in prime(x)}{g(y,i)} f(x,y)=iprime(x)g(y,i)

∏ i = 1 N f ( x , i ) m o d    1 0 9 + 7 \prod_{i=1}^{N}f(x,i)\mod 10^9+7 i=1Nf(x,i)mod109+7

分析

考虑一个朴素的想法: ∏ i = 1 N f ( x , i ) = ∏ i = 1 N ∏ p ∈ p r i m e ( x ) g ( i , p ) = ∏ p ∈ p r i m e ( x ) ∏ i = 1 N g ( i , p ) \begin{aligned} \prod_{i=1}^{N}f(x,i)&=\prod_{i=1}^{N}\prod_{p\in prime(x)}g(i,p)\\&=\prod_{p\in prime(x)}\prod_{i=1}^{N}g(i,p) \end{aligned} i=1Nf(x,i)=i=1Npprime(x)g(i,p)=pprime(x)i=1Ng(i,p)

不难发现对于 p k p^k pk,有 ⌊ N p k ⌋ − t \lfloor\frac{N}{p^k}\rfloor-t pkNt个数对答案有贡献 p k ⌊ N p k ⌋ − t p^{k^{\lfloor\frac{N}{p^k}\rfloor-t}} pkpkNt,其中 t t t表示质因数 p p p的次数大于 k k k的数的个数。

其实这样就可以做了。但这样做很容易让精度爆炸,但我用了一个方法可以勉强卡过去,详见参考代码的版本2。

考虑另一个做法,定义函数 h ( i , p ) = log ⁡ p g ( i , p ) h(i,p)=\log_p g(i,p) h(i,p)=logpg(i,p),这样 h ( i , p ) h(i,p) h(i,p)就表示了这个函数的指数。

可以证明 h ( x y , p ) = h ( x , p ) + h ( y , p ) h(xy,p)=h(x,p)+h(y,p) h(xy,p)=h(x,p)+h(y,p)

接着上面的继续推式子: ∏ i = 1 N f ( x , i ) = ∏ p ∈ p r i m e ( x ) ∏ i = 1 N p h ( i , p ) = ∏ p ∈ p r i m e ( x ) p ∑ i = 1 N h ( i , p ) = ∏ p ∈ p r i m e ( x ) p h ( n ! , p ) \begin{aligned}\prod_{i=1}^{N}f(x,i)&=\prod_{p\in prime(x)}\prod_{i=1}^{N}p^{h(i,p)}\\&=\prod_{p\in prime(x)}p^{\sum_{i=1}^{N}h(i,p)}\\&=\prod_{p\in prime(x)}p^{h(n!,p)} \end{aligned} i=1Nf(x,i)=pprime(x)i=1Nph(i,p)=pprime(x)pi=1Nh(i,p)=pprime(x)ph(n!,p)

我们可以按照这种方式进行计算: h ( n ! , p ) = ∑ k = 0 ∞ ⌊ N p k ⌋ h(n!,p)=\sum_{k=0}^{\infty}\lfloor\frac{N}{p^k}\rfloor h(n!,p)=k=0pkN

参考代码

版本1
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ull;
const ull Mod = 1e9 + 7;

ull Calc(ull n, ull p) {
	if(n == 0) return 0;
	return (Calc(n / p, p) + (n / p)) % (Mod - 1);
}
ull QuickPow(ull a, ull k) {
	ull ret = 1;
	while(k) {
		if(k & 1) ret = ret * a % Mod;
		a = a * a % Mod;
		k >>= 1;
	}
	return ret;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	long long X, N;
	scanf("%lld %lld", &X, &N);
	ull ans = 1;
	for(ull i = 2; i * i <= X; i++)
		if(X % i == 0) {
			ans = ans * QuickPow(i, Calc(N, i)) % Mod;
			while(X % i == 0) X /= i;
		}
	if(X > 1) ans = ans * QuickPow(X, Calc(N, X)) % Mod;
	printf("%lld\n", ans);
	return 0;
}
版本2
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

typedef unsigned long long ll;
const ll Mod = 1e9 + 7;
const int Maxn = 1e6;

ll QuickPow(ll a, ll k) {
	a %= Mod;
	ll ret = 1;
	while(k != 0) {
		if(k % 2 == 1) ret = ret * a % Mod;
		a = a * a % Mod;
		k /= 2;
	}
	return ret;
}
ll Calc(ll a, ll n) {
	ll ret = 1, tmp = 1;
	while(tmp <= n / a) tmp *= a;
	ll tot = 0;
	while(tmp >= 1) {
		ret = ret * QuickPow(tmp, n / tmp - tot) % Mod;
		tot += (n / tmp - tot), tmp /= a;
	}
	return ret;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	ll X, N;
	cin >> X >> N;
	ll ans = 1;
	for(ll i = 2; i * i <= X; i++)
		if(X % i == 0) {
			while(X % i == 0) X /= i;
			ans = ans * Calc(i, N) % Mod;
		}
	if(X > 1) ans = ans * Calc(X, N) % Mod;
	cout << ans;
	return 0;
}

D.Complete Tripartite

◇题目传送门◆

题目大意

给定一个无向无环图,将这个图上的点分成三个点集,每个点集中任意两点之间没有边相连,且任意两个属于不同点集中的点有边相连,求这样的一个方案。

分析

因为是随便找一个方案出来,所以我们考虑贪心。

先随便找一个节点出来,将不与这个节点相连的所有点标上同一个号。对剩下的节点做同样的操作三次即可。

最后注意检验节点的合法性。

参考代码

#include <set>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

const int Maxn = 1e5;

int N, M;
set<int> G[Maxn + 5];
int col[Maxn + 5];
vector<int> t[5];

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d %d", &N, &M);
	for(int i = 1; i <= M; i++) {
		int u, v;
		scanf("%d %d", &u, &v);
		G[u].insert(v), G[v].insert(u);
	}
	for(int i = 1; i <= 3; i++) {
		int u = 1;
		for(u = 1; u <= N; u++)
			if(!col[u])
				break;
		if(u > N) {
			puts("-1");
			return 0;
		}
		col[u] = i;
		for(int v = 1; v <= N; v++)
			if(u != v && col[v] == 0 && G[u].count(v) == 0)
				col[v] = i;
	}
	for(int u = 1; u <= N; u++) {
		if(col[u] == 0) {
			puts("-1");
			return 0;
		}
		t[col[u]].push_back(u);
	}
	int cnt = 0;
	for(int t1 = 1; t1 <= 3; t1++)
		for(int t2 = t1 + 1; t2 <= 3; t2++)
			for(auto u : t[t1])
				for(auto v : t[t2])
					if(G[u].count(v) == 0) {
						puts("-1");
						return 0;
					} else cnt++;
	if(cnt != M) {
		puts("-1");
		return 0;
	}
	for(int i = 1; i <= N; i++)
		printf("%d ", col[i]);
	return 0;
}

E.Another Filling the Grid

◇题目传送门◆

题目大意

给定一个 N × N N\times N N×N的网格,要求往每个格子中填上 1 1 1 K K K中的整数,要求每行每列的最小值是 1 1 1,求有多少种方案。

分析

听说可以用DP做,但复杂度是 O ( N 3 ) O(N^3) O(N3)

若我们在行上放 i i i 1 1 1,在列上放 j j j 1 1 1,并在这些放了 1 1 1的行列上不放上 1 1 1,这时的方案数为 C N i ⋅ C N j ⋅ K N 2 − N × ( i + j ) + i × j ⋅ ( K − 1 ) N × ( i + j ) − i × j C_N^i\cdot C_N^j\cdot K^{N^2-N\times(i+j)+i\times j}\cdot (K-1)^{N\times(i+j)-i\times j} CNiCNjKN2N×(i+j)+i×j(K1)N×(i+j)i×j

容斥一下可以得到总答案为 ∑ i = 0 N ∑ j = 0 N ( − 1 ) i + j C N i ⋅ C N j ⋅ K N 2 − N × ( i + j ) + i × j ⋅ ( K − 1 ) N × ( i + j ) − i × j \sum_{i=0}^{N}\sum_{j=0}^{N}(-1)^{i+j}C_N^i\cdot C_N^j\cdot K^{N^2-N\times(i+j)+i\times j}\cdot (K-1)^{N\times(i+j)-i\times j} i=0Nj=0N(1)i+jCNiCNjKN2N×(i+j)+i×j(K1)N×(i+j)i×j

好像官方题解中还提供了一个 O ( N log ⁡ N ) O(N\log N) O(NlogN)的解法。。。

参考代码

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;
const ll Mod = 1e9 + 7;
const int Maxn = 250;

ll fac[Maxn + 5], inv_fac[Maxn + 5];
ll QuickPow(ll a, ll k) {
	ll ret = 1;
	while(k) {
		if(k & 1) ret = ret * a % Mod;
		a = a * a % Mod;
		k >>= 1;
	}
	return ret;
}
void Init() {
	fac[0] = inv_fac[0] = 1;
	for(int i = 1; i <= Maxn; i++)
		fac[i] = fac[i - 1] * i % Mod;
	inv_fac[Maxn] = QuickPow(fac[Maxn], Mod - 2);
	for(int i = Maxn - 1; i >= 1; i--)
		inv_fac[i] = inv_fac[i + 1] * (i + 1) % Mod;
}
ll C(ll n, ll m) {
	return fac[n] * inv_fac[n - m] % Mod * inv_fac[m] % Mod;
}

int N, K;

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	Init();
	scanf("%d %d", &N, &K);
	ll ans = 0;
	for(int i = 0; i <= N; i++)
		for(int j = 0; j <= N; j++) {
			ll dir = (i + j) & 1 ? -1 : 1;
			ll tmp = (C(N, i) * C(N, j)) % Mod;
			tmp = (tmp * QuickPow(K, N * N - (i + j) * N + i * j)) % Mod;
			tmp = (tmp * QuickPow(K - 1, N * (i + j) - i * j)) % Mod;
			ans = (ans + dir * tmp + Mod) % Mod;
		}
	printf("%lld\n", ans);
	return 0;
}

F.One Node is Gone

◇题目传送门◆

题目大意

给定一个只删掉了一个节点的二叉树,要求求出可能的被删的节点的父亲。

分析

我们将问题分成如下几种情况:

  • 删掉了根节点的儿子
  • 删掉了一个叶子
  • 删掉了除以上特殊节点外的节点。

考虑第一种情况,如图(红色节点是被删的节点):
考虑换种画法画它:

不难发现这种情况就是由两棵高度为 N − 1 N-1 N1的二叉树连起来构成的,不难发现这种情况的答案是两个点,也就是这两棵高度为 N − 1 N-1 N1的树的根。

对于剩下两个情况,我们先找出它的根。

对于第二种情况,我们发现这时有两个点的度数是2,一个是根节点,另一个就是删掉的节点的父亲。所以我们只需要找出除了根节点的另一个度数为2的节点。

对于第三种情况,我们发现只有一个节点的度数为4。我们找出这个度数为4的点,再讨论它的三个儿子的情况即可。

一种检验答案是否合法的方法是暴力加入删掉的节点再判断。

注意可能出现的无解的情况。

不难发现这就是一道分类讨论的好题。

参考代码

#include <cstdio>
#include <algorithm>
using namespace std;

const int Maxn = 17;
const int Maxnode = (1 << Maxn);

int N, node;
vector<int> G[Maxnode + 5];
int deg[Maxnode + 5], cnt_deg[5];

int fa[Maxnode + 5], dep[Maxnode + 5], siz[Maxnode + 5], max_dep[Maxnode + 5];
void DFS(int u, int pre) {
	fa[u] = pre, dep[u] = dep[pre] + 1, max_dep[u] = dep[u];
	for(int i = 0; i < (int)G[u].size(); i++) {
		int v = G[u][i];
		if(v == pre) continue;
		DFS(v, u);
		max_dep[u] = max(max_dep[u], max_dep[v]);
	}
}
int FindRoot() {
	DFS(1, 0);
	int u = 1;
	for(int i = 1; i <= node; i++)
		if(dep[u] < dep[i]) u = i;
	DFS(u, 0);
	int v = u;
	for(int i = 1; i <= node; i++)
		if(dep[v] < dep[i]) v = i;
	if(!(dep[v] & 1)) {
		puts("0");
		exit(0);
	}
	int ret = v;
	while(dep[v] - dep[ret] != dep[ret] - dep[u])
		ret = fa[ret];
	return ret;
}

bool check(int u, int pre) {
	siz[u] = 1;
	int tmp = -1;
	for(int i = 0; i < (int)G[u].size(); i++) {
		int v = G[u][i];
		if(v == pre || v == fa[u]) continue;
		if(!check(v, u)) return false;
		siz[u] += siz[v];
		if(tmp == -1) tmp = siz[v];
		else if(tmp != siz[v]) return false;
	}
	return true;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d", &N);
	node = (1 << N) - 2;
	for(int i = 1; i < node; i++) {
		int u, v;
		scanf("%d %d", &u, &v);
		G[u].push_back(v), G[v].push_back(u);
		deg[u]++, deg[v]++;
	}
	for(int i = 1; i <= node; i++) {
		if(deg[i] > 4) {
			puts("0");
			return 0;
		}
		cnt_deg[deg[i]]++;
	}
	if(cnt_deg[1] != (1 << (N - 1)) && cnt_deg[1] != (1 << (N - 1)) - 1) {
		puts("0");
		return 0;
	}
	if(cnt_deg[1] == (1 << (N - 1)) && cnt_deg[2] == 0 && cnt_deg[4] == 0) {
		DFS(1, 0);
		int u = 1;
		for(int i = 1; i <= node; i++)
			if(dep[u] < dep[i]) u = i;
		DFS(u, 0);
		int v = u;
		for(int i = 1; i <= node; i++)
			if(dep[v] < dep[i]) v = i;
		if(dep[v] & 1) {
			puts("0");
			return 0;
		}
		int p = v;
		while(dep[v] - dep[p] < dep[p] - dep[u] - 1)
			p = fa[p];
		int q = fa[p];
		dep[q] = 0, DFS(p, q), dep[p] = 0, DFS(q, p);
		if(check(p, q) && check(q, p) && max_dep[p] == max_dep[q])
			printf("2\n%d %d\n", min(p, q), max(p, q));
		else puts("0");
		return 0;
	}//根节点的子节点
	int rt = FindRoot();
	DFS(rt, 0);
	if(cnt_deg[1] == (1 << (N - 1))) {//普通节点
		if(cnt_deg[4] == 1 && cnt_deg[2] == 1 && deg[rt] == 2) {
			int k = -1;
			for(int i = 1; i <= node; i++)
				if(deg[i] == 4) k = i;
			vector<int> ch;
			for(int i = 0; i < (int)G[k].size(); i++)
				if(G[k][i] != fa[k]) ch.push_back(G[k][i]);
			G[k].clear();
			G[k].push_back(node + 1), G[k].push_back(fa[k]);
			if(max_dep[ch[0]] == max_dep[ch[1]] && max_dep[ch[2]] == max_dep[ch[0]] + 1)
				G[k].push_back(ch[2]), G[node + 1].push_back(ch[1]), G[node + 1].push_back(ch[0]);
			else if(max_dep[ch[1]] == max_dep[ch[2]] && max_dep[ch[0]] == max_dep[ch[1]] + 1)
				G[k].push_back(ch[0]), G[node + 1].push_back(ch[1]), G[node + 1].push_back(ch[2]);
			else if(max_dep[ch[0]] == max_dep[ch[2]] && max_dep[ch[1]] == max_dep[ch[0]] + 1)
				G[k].push_back(ch[1]), G[node + 1].push_back(ch[0]), G[node + 1].push_back(ch[2]);
			else {
				puts("0");
				return 0;
			}
			if(check(rt, 0)) printf("1\n%d\n", k);
			else puts("0");
			return 0;
		}
		puts("0");
		return 0;
	} else if(cnt_deg[1] == (1 << (N - 1)) - 1) {//叶节点
		if(cnt_deg[4] == 0 && cnt_deg[2] == 2 && deg[rt] == 2) {
			int t = -1;
			for(int u = 1; u <= node; u++)
				if(deg[u] == 2 && u != rt)
					t = u;
			G[t].push_back(node + 1), G[node + 1].push_back(t);
			if(check(rt, 0)) printf("1\n%d\n", t);
			else puts("0");
		} else puts("0");
		return 0;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值