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 pk∣x的最小的 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)=∏i∈prime(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=1∏Nf(x,i)=i=1∏Np∈prime(x)∏g(i,p)=p∈prime(x)∏i=1∏Ng(i,p)
不难发现对于 p k p^k pk,有 ⌊ N p k ⌋ − t \lfloor\frac{N}{p^k}\rfloor-t ⌊pkN⌋−t个数对答案有贡献 p k ⌊ N p k ⌋ − t p^{k^{\lfloor\frac{N}{p^k}\rfloor-t}} pk⌊pkN⌋−t,其中 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=1∏Nf(x,i)=p∈prime(x)∏i=1∏Nph(i,p)=p∈prime(x)∏p∑i=1Nh(i,p)=p∈prime(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=0∑∞⌊pkN⌋
参考代码
版本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} CNi⋅CNj⋅KN2−N×(i+j)+i×j⋅(K−1)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=0∑Nj=0∑N(−1)i+jCNi⋅CNj⋅KN2−N×(i+j)+i×j⋅(K−1)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 N−1的二叉树连起来构成的,不难发现这种情况的答案是两个点,也就是这两棵高度为 N − 1 N-1 N−1的树的根。
对于剩下两个情况,我们先找出它的根。
对于第二种情况,我们发现这时有两个点的度数是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;
}