Typesetting math: 100%

【USACO 2020 US Open Platinum】题解

T1#

Sol#

简单题,发现实质上是用一条折线把举行分开,弯折处必须放,其余随便放不放,同时满足有些格子不能放。直接 dp 就好了。

不过当时脑子有抽风,所以写得复杂了,分成了四种情况讨论,然后统计的是所有方案的必须选的逆元和,最后再乘上一个 2tot2tot ……

Code#

Copy
#include <algorithm> #include <cstdio> #include <iostream> using namespace std; #define LL long long #define go(G, x, i, v) \ for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]]) #define inline __inline__ __attribute__((always_inline)) inline LL read() { LL x = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } return x * w; } const int Max_n = 2005, mod = 1e9 + 7, inv = (mod + 1) / 2; int n, ans, tot, t; int f[Max_n], q[Max_n], g[Max_n]; char s[Max_n][Max_n]; namespace Input { void main() { n = read(); for (int i = n; i >= 1; i--) { scanf("%s", s[i] + 1); for (int j = 1; j <= n; j++) tot += s[i][j] == '.'; reverse(s[i] + 1, s[i] + n + 1); } } } // namespace Input namespace Solve { void DP() { for (int i = 2; i <= n; i++) { for (int j = 0; j <= n; j++) { (q[j] = s[j + 1][i - 1] != 'W' ? f[j] : 0) %= mod; } for (int j = 1; j <= n; j++) (q[j] += q[j - 1]) %= mod; for (int j = 0; j <= n; j++) { g[j] = j > 0 && s[j][i] == '.' ? (LL)q[j - 1] * inv % mod * inv % mod : 0; (g[j] += f[j] % mod) %= mod; } swap(g, f); } for (int i = 0; i < n; i++) f[i] = s[i + 1][n] != 'W' ? f[i] : 0; for (int i = 1; i < n; i++) (f[i] += f[i - 1]) %= mod; } void main() { t = 1; while (tot--) t = 2 * t % mod; for (int i = 1; i <= n; i++) f[i] = s[i][1] == '.' ? (LL)inv * inv % mod : 0; DP(); (ans += (LL)f[n] * t % mod * 2 % mod) %= mod; (ans += (LL)f[n - 1] * t % mod) %= mod; f[0] = 1; for (int i = 1; i <= n; i++) f[i] = 0; DP(); (ans += (LL)f[n] * t % mod) %= mod; (ans += (LL)f[n - 1] * t % mod * inv % mod) %= mod; cout << ans << endl; } } // namespace Solve int main() { #ifndef ONLINE_JUDGE freopen("A.in", "r", stdin); freopen("A.out", "w", stdout); #endif Input::main(); Solve::main(); }

T2#

Sol#

题意是对于 n!n! 种置换,每种置换的所有环长的 lcmlcm 的乘积。

不难发现可以转化为求每个质因数的指数,最后乘起来就行了。

那么重点在于怎么求指数。

对于 pp ,其实并不需要一一处理恰好包含 pipi 的置换个数,事实上设 F(x)F(x)x|lcmx|lcm 的置换个数,那么 pp 的指数就是 F(pi)F(pi)

接下来进一步转化为怎么求 F(x)F(x) ,若 x|lcmx|lcm ,那条件应该是所有环长中,至少有一个环长 lenlen 满足 x|lenx|len 即可。

正难则反,转为求 f(t)f(t) 为构造的环长总合目前为 tt,没有任何一个环长满足 x|lenx|len 的置换数,则 F(x)=n!f(n)F(x)=n!f(n)

接着我们可以设函数 g(t)g(t) 为构造的环长总合目前为 tt,且所有环长都满足 x|lenx|len

则有如下转移:

f(t)=t!j>0g(j)f(tj)Cjtf(t)=t!j>0g(j)f(tj)Cjt

由定义可知,正确性显然。

然后问题又变成了怎么求 gg ,我们有如下转移式:

g(t)=j>0,x|jCj1t1(j1)!g(tj)g(t)=j>0,x|jCj1t1(j1)!g(tj)

其中 jj 为 1 所在的联通块大小,正确性显然。

故对于每一个 pp ,我们都分别求出 F(pi)F(pi) ,然后计算答案。

根据转移式可发现,若要求得 f(n)f(n)f,gf,g 所需使用的下标仅有 nxnx 个,故最终所有 dpdp 的总复杂度也就是 O(n2)O(n2)

Code#

Copy
#include <cstdio> #include <iostream> using namespace std; #define LL long long typedef unsigned long long uLL; #define go(G, x, i, v) \ for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]]) #define inline __inline__ __attribute__((always_inline)) inline LL read() { LL x = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } return x * w; } const int Max_n = 7.5e3 + 5; int n, mod, ans; int C[Max_n][Max_n], fac[Max_n]; namespace FastMod { uLL b, m; void init(int x) { b = x, m = uLL((__int128(1) << 64) / b); } uLL Mod(uLL a) { uLL q = (uLL)((__int128(m) * a) >> 64); uLL r = a - q * b; // can be proven that 0 <= r < 2*b return r >= b ? r - b : r; } } // namespace FastMod using FastMod::Mod; void M(int &x) { x = x >= mod - 1 ? x - mod + 1 : x; } namespace Input { void main() { n = read(), mod = read(); } } // namespace Input namespace Init { void main() { FastMod::init(mod - 1); for (int i = 0; i <= n; i++) { C[i][0] = 1; for (int j = 1; j <= i; j++) M(C[i][j] = C[i - 1][j - 1] + C[i - 1][j]); } fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = Mod((uLL)fac[i - 1] * i); } } // namespace Init namespace Solve { int f[Max_n], g[Max_n]; bool vis[Max_n]; int ksm(int a, int b) { int res = 1; for (; b; b >>= 1, a = (LL)a * a % mod) if (b & 1) res = (LL)res * a % mod; return res; } int DP(int x) { g[0] = f[0] = 1; for (int i = x; i <= n; i += x) { g[i] = 0; for (int j = x; j <= i; j += x) M(g[i] += Mod(Mod((uLL)C[i - 1][j - 1] * fac[j - 1]) * g[i - j])); } for (int i = n % x; i <= n; i += x) { f[i] = fac[i]; for (int j = x; j <= i; j += x) M(f[i] += mod - 1 - Mod(Mod((uLL)C[i][j] * g[j]) * f[i - j])); } return Mod(fac[n] - f[n] + mod - 1); } void main() { ans = 1; for (int i = 2; i <= n; i++) if (!vis[i]) { for (int j = i; j <= n; j += i) vis[j] = 1; for (int j = i; j <= n; j *= i) ans = (LL)ans * ksm(i, DP(j)) % mod; } cout << ans << endl; } } // namespace Solve int main() { #ifndef ONLINE_JUDGE freopen("B.in", "r", stdin); freopen("B.out", "w", stdout); #endif Input::main(); Init::main(); Solve::main(); }

T3#

Sol#

神题。

先想一下怎么算答案,我们发现,不管这 KK 个点最开始在哪里,我们都必定可以将它们移到任意 KK 个被指定的点上去,所以我们的方案数肯定最大为 K!K!

我们将所有极长的,中间点度数均为 2 的路径缩成一条边,将树变成一棵每个点度数均不为 2 的新树,对于一条边 ii ,我们有 AiAiuu 这边的子树大小,BiBivv 这边的子树大小,CiCi 为这条路径上的点数,则有 Ai1+Bi1+Ci=nAi1+Bi1+Ci=n

考虑对于一个 KK 和一条边 ii ,若有 KAi1+Bi1KAi1+Bi1 ,则 u,vu,v 两个子树内的点必然不能交换,且中间的链上始终有 K(Ai1)(Bi1)K(Ai1)(Bi1) 个元素,这些元素的顺序无法被改变。

反之若 K<Ai1+Bi1K<Ai1+Bi1 ,发现必然可以在不影响其他元素的情况下,交换 (u,v)(u,v) 链上的任意两个相邻元素,也就等价于可以交换链上任意两个元素。

对于一个 KK ,将所有满足 K<Ai1+Bi1K<Ai1+Bi1 的边连起来,发现对于每个联通块内都必然可以相互交换,同时对于所有能够“到达”这个联通块内的点,也可以做出交换。

对于一个联通块,如何计算能够到达它的点呢?我们发现如果联通块内连出去一条边,但是这条边并不满足 K<Ai1+Bi1K<Ai1+Bi1 ,设 AiAi 是相对于联通块来说在外面的子树,除去此子树及此链以外的点数自然为 n(Ai1)Cin(Ai1)Ci

由于不满足等式,则 n(Ai1)Ci<Kn(Ai1)Ci<K ,所以此子树最少也要剩下 K(n(Ai1)Ci)=Ai1+Ci+KnK(n(Ai1)Ci)=Ai1+Ci+Kn 个点不能到达联通块。

故对于当前 KK 所构成的所有联通块,对于其中一个联通块 TT,设其点数为 xx ,和联通块相连但不满足要求要求的边的编号集合 S={i|viT and uiT}S={i|viT and uiT} ,设此集合大小为 yy ,则联通块内可交换集合大小为:

f(T)=KiSAi1+Ci+Knf(T)=KiSAi1+Ci+Kn

将常数项拆出来,并且由于其意义,实际上 iSAi1+Ci=nx+yiSAi1+Ci=nx+y

那么可将式子进一步化为:

f(T)=K(nx)(Kn+1)yf(T)=K(nx)(Kn+1)y

由此,对于一个 KK 而言,其答案就是 K!f(T)!K!f(T)! ,意义在开头已经说过了,K!K! 是放在某 KK 个指定位置的方案数,然后除去所有可交换的位置集合方案数。

那么考虑从大到小枚举 KK ,就能使得边不断被加入,用并查集即可轻松维护 xxyy

对于每个 KK 来说,不用管那么多,直接枚举当前存在的所有联通块即可。

因为对于任意一条边来说,它不被连上当且仅当 K>(Ai1)+(Bi1)K>(Ai1)+(Bi1) ,也就是 K>nCiK>nCi ,也就是说它只会在前 CiCi 大的 KK 里作出贡献,也就是说所有 KK 的联通块数的和为 Ci=O(n)Ci=O(n) ,所以直接枚举的复杂度肯定是对的。

Code#

Copy
#include <algorithm> #include <cstdio> #include <iostream> using namespace std; #define LL long long #define go(G, x, i, v) \ for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]]) #define inline __inline__ __attribute__((always_inline)) inline LL read() { LL x = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } return x * w; } const int Max_n = 1e5 + 5, mod = 1e9 + 7; struct graph { int hd[Max_n]; int cntr, nx[Max_n << 1], to[Max_n << 1]; void addr(int u, int v) { cntr++; nx[cntr] = hd[u], to[cntr] = v; hd[u] = cntr; } }; struct path { int u, v, len; }; int n; int fac[Max_n], ifac[Max_n]; int deg[Max_n], bf[Max_n], nx[Max_n]; graph G; int cnt, f[Max_n], x[Max_n], y[Max_n]; path K[Max_n]; void remove(int x) { nx[bf[x]] = nx[x], bf[nx[x]] = bf[x]; } namespace Input { void main() { n = read(); for (int i = 1; i < n; i++) { int u = read(), v = read(); G.addr(u, v), G.addr(v, u); deg[u]++, deg[v]++; } } } // namespace Input namespace Init { int ksm(int a, int b = mod - 2) { int res = 1; for (; b; b >>= 1, a = (LL)a * a % mod) if (b & 1) res = (LL)res * a % mod; return res; } void build(int x, int fa, int Fa, int dep) { if (deg[x] != 2) { if (x != nx[0]) K[++cnt] = (path){Fa, x, dep + 1}; Fa = x, dep = 0; } go(G, x, i, v) if (v != fa) build(v, x, Fa, dep + 1); } void main() { fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = (LL)fac[i - 1] * i % mod; ifac[n] = ksm(fac[n]); for (int i = n; i ; i--) ifac[i - 1] = (LL)ifac[i] * i % mod; int L = 0; for (int i = 1; i <= n; i++) if (deg[i] != 2) { bf[i] = L, nx[L] = i, L = i; f[i] = i, x[i] = 1, y[i] = deg[i]; } build(nx[0], 0, 0, 0); } } // namespace Init namespace Solve { bool cmp(path a, path b) { return a.len < b.len; } int ans[Max_n]; int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } void merge(int u, int v, int len) { u = find(u), v = find(v); if (u == v) return; f[v] = f[u], x[u] += x[v] + len - 2, y[u] += y[v] - 2; remove(v); } void main() { sort(K + 1, K + cnt + 1, cmp); int pos = 1; ans[n] = fac[n]; for (int k = n - 1; k; k--) { ans[k] = fac[k]; while (pos <= n && n - K[pos].len > k) merge(K[pos].u, K[pos].v, K[pos].len), pos++; for (int i = nx[0]; i; i = nx[i]) { ans[k] = (LL)ans[k] * ifac[k - (n - x[find(i)]) - (k - n + 1) * y[find(i)]] % mod; } } for (int i = 1; i <= n; i++) printf("%d\n", ans[i]); } } // namespace Solve int main() { #ifndef ONLINE_JUDGE freopen("C.in", "r", stdin); freopen("C.out", "w", stdout); #endif Input::main(); Init::main(); Solve::main(); }
posted @   洛水·锦依卫  阅读(296)  评论(0编辑  收藏  举报
编辑推荐:
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
阅读排行:
· 趁着过年的时候手搓了一个低代码框架
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· 乌龟冬眠箱湿度监控系统和AI辅助建议功能的实现
点击右上角即可分享
微信分享提示

目录

目录

×