NOIP2020 模拟 - 学军 30
解题报告
expect2004
棒棒糖
题解
观察到题中所给的后半部分伪代码,实际是倍增求 (operatorname{LCA}) 。
观察给出的有 bug 的 dfs 代码,实际上只会影响每个点的 dep。
倍增求 ( exttt{LCA}) 的部分中,后半部分,即 (x,y) 两个点一起往上跳的部分是正确的,那么只有将 (x,y) 提平的部分可能导致答案错误——如果不能提到同一高度,答案一定错误。
那么考虑什么情况才能让两个结点提平:(operatorname{dep}(x) - operatorname{dep}(y)) 的值是正确的,这种情况发生且仅发生在从 (operatorname{LCA}) 到 (x,y) 路径上,(operatorname{RANDOM}) 函数值为 (0) 的次数一样多。
令 (X = operatorname{dep}(x) - operatorname{dep}(operatorname{LCA}), Y = operatorname{dep}(y) - operatorname{dep}(operatorname{LCA}))
得到答案为 (dfrac{sum_{i=0}^{min{X,Y}}{C_X^i imes C_Y^i}}{sum_{i=0}^X{C_X^i} imessum_{i=0}^Y{C_Y^i}})
得到上式,即可 (O(n^2)) 预处理,可以获得 65 分
分子分母分开讨论,分母显然等于 (2^{X+Y})
通过打表或者考虑组合意义,分子等于 (C_{X+Y}^{min {X, Y}})
可以 (O(n)) 预处理组合数,(O(Q log n)) 回答询问。
至此,我们以 (O(Q log n)) 的复杂度解决了本题。
代码
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define dbg(x) cerr << #x << " = " << x << endl;
template < typename Tp >
inline void rd(Tp &x) {
x = 0; int fh = 1; char ch = 1;
while(ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if(ch == '-') fh = -1, ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= fh;
}
const int mod = 998244353;
template < typename Tp > inline void smax(Tp &x, Tp y) {if(x < y) x = y;}
template < typename Tp > inline void smin(Tp &x, Tp y) {if(x > y) x = y;}
template < typename Tp > inline void chkadd(Tp &x, Tp y) {x = (x + y) % mod;}
template < typename Tp > inline Tp abs(Tp x) {return (x < 0) ? -x : x;}
const int maxn = 200000 + 7;
const int maxm = 400000 + 7;
int n, Q;
int Head[maxn], to[maxm], Next[maxm], tot;
int dep[maxn], fa[maxn], top[maxn], size[maxn], son[maxn];
void add(int x, int y) {
to[++tot] = y, Next[tot] = Head[x], Head[x] = tot;
}
void dfs1(int x, int f) {
dep[x] = dep[f] + 1, size[x] = 1, fa[x] = f;
int mx = -1;
for(int i = Head[x]; i; i = Next[i]) {
int y = to[i];
if(y == f) continue;
dfs1(y, x); size[x] += size[y];
if(size[y] > mx) mx = size[y], son[x] = y;
}
}
void dfs2(int x, int tp) {
top[x] = tp;
if(!son[x]) return ;
dfs2(son[x], tp);
for(int i = Head[x]; i; i = Next[i]) {
int y = to[i];
if(y == son[x] || y == fa[x]) continue;
dfs2(y, y);
}
}
inline int LCA(int x, int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(dep[x] < dep[y]) return x;
return y;
}
long long fpow(long long x, int p) {
long long res = 1ll;
while(p) {
if(p & 1) res = res * x % mod; p >>= 1;
x = x * x % mod;
}
return res;
}
int frac[maxn * 2], inv[maxn * 2];
inline void Preprocess(void) {
frac[0] = 1;
for(int i = 1; i <= 400000; i++) frac[i] = 1ll * frac[i - 1] * i % mod;
inv[400000] = fpow(frac[400000], mod - 2);
for(int i = 399999; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
long long C(int x, int y) {
if(x < y) return 0;
return 1ll * frac[x] * inv[y] % mod * inv[x - y] % mod;
}
inline void query(int x, int y) {
int lca = LCA(x, y);
int dpx = dep[x] - dep[lca], dpy = dep[y] -dep[lca];
int Mn = min(dpx, dpy);
long long ans = C(dpx + dpy, Mn);
ans = 1ll * ans * fpow(fpow(2, dpx), mod - 2) % mod;
ans = 1ll * ans * fpow(fpow(2, dpy), mod - 2) % mod;
printf("%lld
", ans);
}
inline void Init(void) {
rd(n);
for(int i = 1, x, y; i < n; i++) {
rd(x); rd(y);
add(x, y); add(y, x);
}
}
inline void Work(void) {
Preprocess();
dfs1(1, 0);
dfs2(1, 1);
rd(Q);
while(Q--) {
int x, y; rd(x); rd(y);
query(x, y);
}
}
signed main(void) {
Init();
Work();
return 0;
}
彩虹糖
题解
注意到第三条限制,即是保证 (forall i in [1,m]) ,命题 (i in A) 与命题 (i in B) 不同时成立,因此,这是一个公平游戏。
显然,由于两人都绝顶聪明,都要最大化两人的可操作次数差,由于上述限制条件,操作顺序并不影响答案。
设 (f(i)) 表示利用大小为 (i) 的堆先手能与后手拉开的最大可操作次数差,(-g(i)) 表示利用大小为 (i) 的堆后手能与先手拉开的最大可操作次数差。
由于上述限制,在处理时可合并处理为 (f(i)) 表示利用大小为 (i) 的堆先手能与后手拉开的最大可操作次数差 或 后手能与先手拉开的最小可操作次数差,这取决于 (i) 属于哪一个区间。
由于 (forall x in A cup B,x in [1,m]),任何大于 (m) 的堆数无用。
排除掉无用堆后,令 (S = sum_{i=1}^n f(P_i)),当 (S>0) 时,答案为 Pomegranate,否则答案为 Orange。
值得指出的是,Subtask3 满足特殊限制 (tot_b = 0),但这并不表示先手必胜,因为在 (n) 堆中可以没有任何一堆先手能够操作的。我在制作测试数据时,特别手工构造了这样一组数据,并发现有选手因此失分。
时间复杂度貌似是 (O(dfrac{1}{2}m^2+n)),对于这个数据范围显得有些卡常。
代码
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define dbg(x) cerr << #x << " = " << x << endl;
template < typename Tp >
inline void rd(Tp &x) {
x = 0; int fh = 1; char ch = 1;
while(ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if(ch == '-') fh = -1, ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= fh;
}
const int mod = 998244353;
template < typename Tp > inline void smax(Tp &x, Tp y) {if(x < y) x = y;}
template < typename Tp > inline void smin(Tp &x, Tp y) {if(x > y) x = y;}
template < typename Tp > inline void chkadd(Tp &x, Tp y) {x = (x + y) % mod;}
template < typename Tp > inline Tp abs(Tp x) {return (x < 0) ? -x : x;}
const int maxn = 1000000 + 7;
const int maxm = 10000 + 7;
int n, m, A, B, dp[maxn], cnt;
bool a[maxm], b[maxm];
int p[maxn];
inline void Init(void) {
rd(n); rd(m);
rd(A);
for(int i = 1, x; i <= A; i++) {
rd(x); a[x] = true;
}
rd(B);
for(int i = 1, x; i <= B; i++) {
rd(x); b[x] = true;
}
for(int i = 1; i <= n; i++) {
rd(p[i]);
if(p[i] == 1 || p[i] > m) continue;
p[++cnt] = p[i];
}
n = cnt;
}
inline void Work(void) {
for(int i = 2; i <= m; i++) {
if(a[i]) {
for(int j = 1; j <= i / 2; j++) smax(dp[i], dp[j] + dp[i - j] + 1);
}
if(b[i]) {
for(int j = 1; j <= i / 2; j++) smin(dp[i], dp[j] + dp[i - j] - 1);
}
}
int ans = 0;
for(int i = 1; i <= n; i++) {
ans += dp[p[i]];
}
if(ans > 0) puts("Pomegranate");
else puts("Orange");
}
signed main(void) {
Init();
Work();
return 0;
}
泡泡糖
毒瘤题,不会
字典序
题解
将 ((a_i,b_i)) 视作有向图中的一条边,容易发现,在有解的时候,会构成一个 DAG。如果构不成 DAG,即无解。
容易发现,这个 DAG 的一个拓扑序即为一组合法的答案。
但要求字典序最小,考虑我们拓扑排序的过程,使用了一个队列,只需要每次贪心地从这个队列中取出最小的继续排序就行了。
显然用优先队列替换队列进行维护,时间复杂度 (O(n log n))。
代码
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define dbg(x) cerr << #x << " = " << x << endl;
template < typename Tp >
inline void rd(Tp &x) {
x = 0; int fh = 1; char ch = 1;
while(ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if(ch == '-') fh = -1, ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= fh;
}
const int mod = 998244353;
template < typename Tp > inline void smax(Tp &x, Tp y) {if(x < y) x = y;}
template < typename Tp > inline void smin(Tp &x, Tp y) {if(x > y) x = y;}
template < typename Tp > inline void chkadd(Tp &x, Tp y) {x = (x + y) % mod;}
template < typename Tp > inline Tp abs(Tp x) {return (x < 0) ? -x : x;}
const int maxn = 100000 + 7;
const int maxm = 100000 + 7;
int n, m;
priority_queue <int, vector <int>, greater <int> > Q;
int Head[maxn], to[maxm], Next[maxm], tot;
void add(int x, int y) {
to[++tot] = y, Next[tot] = Head[x], Head[x] = tot;
}
int deg[maxn], ans[maxn], cas;
int p[maxn];
inline void Init(void) {
rd(n); rd(m);
for(int i = 1, x, y; i <= m; i++) {
rd(x); rd(y);
add(x, y);
deg[y]++;
}
}
bool comp(int i, int j) {
return ans[i] < ans[j];
}
inline void Work(void) {
for(int i = 1; i <= n; i++) {
if(deg[i] == 0) {
Q.push(i);
}
p[i] = i;
}
while(Q.size()) {
int x = Q.top(); Q.pop();
ans[x] = ++cas;
for(int i = Head[x]; i; i = Next[i]) {
int y = to[i]; --deg[y];
if(deg[y] == 0) Q.push(y);
}
}
for(int i = 1; i <= n; i++) {
if(deg[i]) {
puts("-1"); return ;
}
}
sort(p + 1, p + n + 1, comp);
for(int i = 1; i < n; i++) printf("%d ", p[i]);
printf("%d
", p[n]);
}
signed main(void) {
Init();
Work();
return 0;
}