USACO 2020 US Open Platinum 题解

本文介绍了USACO比赛中的三道题目解题思路:Sprinklers 2采用O(n^2)复杂度的轮廓线DP解决;Exercise利用质因数分解和容斥原理求解;Circus问题通过置换群和连通块理论分析,复杂度为O(n log n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是我作为OIer打的最后一场USACO了,我也不知道为啥要在这个时候开blog…

Sprinklers 2: Return of the Alfalfa

可以发现只要轮廓线是递减的,并且角上被选中即可,其他的位置都可选可不选。可以dp这个轮廓线,从左上方每次向右或向下走,复杂度 O(n2)O(n^2)O(n2) 。也可以对每一列用一个高度来表示轮廓线,从左往右dp,复杂度不变。对于轮廓线贴到边界的情况要稍微特殊考虑一下。

#include <bits/stdc++.h>

namespace IO
{
char gc()
{
#ifdef FREAD
	static char buf[1<<21], *P1 = buf, *P2 = buf;
	if(P1 == P2)
	{
		P1 = buf;
		P2 = buf + fread(buf, 1, 1<<21, stdin);
		if(P1 == P2) return EOF;
	}
	return *(P1++);
#else
	return getchar();
#endif
}
template<typename Tp> bool get1(Tp &x)
{
	bool neg = 0;
	char c = gc();
	while( c != EOF && (c < '0' || c > '9') && c != '-' ) c = gc();
	if(c == '-') c = gc(), neg = 1;
	if(c == EOF) return false;
	x = 0;
	for(; c>='0' && c<='9'; c = gc()) x = x*10 + c - '0';
	if(neg) x = -x;
	return true;
}
template<typename Tp> void printendl(Tp x)
{
	if(x<0)putchar('-'),x=-x;
	static short a[40], sz;
	sz = 0;
	while(x>0)a[sz++]=x%10,x/=10;
	if(sz==0)putchar('0');
	for(int i=sz-1; i>=0; i--)putchar('0'+a[i]);
	puts("");
}
} // namespace IO
using IO::get1;
using IO::printendl;
#define get2(x,y) get1(x) && get1(y)
#define get3(x,y,z) get2(x,y) && get1(z)
#define get4(x,y,z,w) get3(x,y,z) && get1(w)
#define pb push_back
#define mp std::make_pair
#define ff first
#define ss second
typedef long long LL;
typedef unsigned long long uLL;
typedef std::pair<int,int> pii;
const int inf = 0x3f3f3f3f;
const LL Linf = 1ll<<61;

const int mod = 1e9 + 7;
const int inv[] = {1, (mod+1)/2};
int qpow(int x, int y)
{
	int ret = 1;
	while(y)
	{
		if(y & 1) ret = 1ll * ret * x % mod;
		x = 1ll * x * x % mod;
		y >>= 1;
	}
	return ret;
}

const int maxn = 2111;

int n, p2[maxn], cnt[maxn], dp[maxn], ndp[maxn];
char s[maxn][maxn];
int main()
{
#ifndef EEEEEericKKK
	freopen("sprinklers2.in", "r", stdin);
	freopen("sprinklers2.out", "w", stdout);
#endif
	p2[0] = 1; for(int i=1; i<maxn; i++) p2[i] = 2ll * p2[i-1] % mod;
	
	get1(n);
	for(int i=n; i>=1; i--) scanf("%s", s[i]+1);
	for(int i=0; i<=n+1; i++) for(int j=0; j<=n+1; j++) if(!i || !j || i>n || j>n) s[i][j] = '.';
	
	for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) if(s[i][j] == '.') cnt[i]++;
	
	dp[n] = 1;
	for(int i=1; i<=n; i++)
	{
		int sum = 0;
		for(int j=n; j>=0; j--)
		{
			ndp[j] = 1ll * dp[j] * p2[cnt[i]] % mod;
			if(s[i][j+1] == '.') ndp[j] = (ndp[j] + 1ll * sum * p2[cnt[i]] % mod * (mod+1)/2) % mod;
			if(s[i-1][j] == '.') sum = (sum + 1ll * dp[j] * (i == 1 ? 1 : (mod+1)/2)) % mod;
		}
		memcpy(dp, ndp, sizeof(dp));
	}
	int ans = 0;
	for(int j=0; j<=n; j++) if(s[n][j] == '.') ans = (ans + 1ll * dp[j] * inv[j>0]) % mod;
	printendl(ans);
	return 0;
}
Exercise

一个排列的答案是环长的LCM。由于是求乘积,可以把每个质因子 ppp 分开考虑,于是我们相当于要对所有排列对应的环长 l1,⋯lkl_1,\cdots l_kl1,lkmax⁡ordp(li)\max \text{ord}_p(l_i)maxordp(li) ,然后加起来对 M−1M-1M1 取模。结合min-max容斥和等于转大于等于的容斥可得我们要求的是:

∑S⊆{1,…,k},S≠∅(−1)∣S∣−1∑x≥1∏i∈S[ordp(li)≥x]\sum_{S\subseteq\{1,\dots,k\},S\neq\emptyset}(-1)^{|S|-1}\sum_{x\ge 1}\prod_{i\in S}[\text{ord}_p(l_i)\ge x]S{1,,k},S=(1)S1x1iS[ordp(li)x]

于是我们可以枚举 pppxxx ,剩下的计算相当于强制某些环的长度是 pxp^xpx 的倍数,令 s=pxs=p^xs=px ,则可以枚举是 sss 倍数的环的总长度 i⋅si\cdot sis 。转移方程是:

fi=−∑j=1ifi−j(i⋅s−1j⋅s−1)(j⋅s−1)!f_i=-\sum_{j=1}^if_{i-j}\binom{i\cdot s-1}{j\cdot s-1}(j\cdot s-1)!fi=j=1ifij(js1is1)(js1)!

对于这个 sss 的答案为 ∑i=0⌊ns⌋(ns⋅i)(n−s⋅i)!fi\sum_{i=0}^{\lfloor\frac ns\rfloor}\binom{n}{s\cdot i}(n-s\cdot i)!f_ii=0sn(sin)(nsi)!fi 。预处理组合数后,递推复杂度为 O(n2s2)O(\frac{n^2}{s^2})O(s2n2) ,对这个复杂度求和不会超过 n2∑i=1ni−2≤n2π26n^2\sum_{i=1}^ni^{-2}\le \frac{n^2\pi^2}{6}n2i=1ni26n2π2 ,也就是 O(n2)O(n^2)O(n2) 。可以注意到 fff 的计算大概是一个多项式 exp⁡\expexp 的形式,所以能优化到O(n⋅poly(log⁡n))O(n\cdot\text{poly}(\log n))O(npoly(logn)) 。群友也有不用多项式操作的poly(log⁡)\text{poly}(\log)poly(log) 做法,可能要对转移式往后推几步,不过 n2n^2n2 能过就没再管。

#include <bits/stdc++.h>

namespace IO
{
char gc()
{
#ifdef FREAD
	static char buf[1<<21], *P1 = buf, *P2 = buf;
	if(P1 == P2)
	{
		P1 = buf;
		P2 = buf + fread(buf, 1, 1<<21, stdin);
		if(P1 == P2) return EOF;
	}
	return *(P1++);
#else
	return getchar();
#endif
}
template<typename Tp> bool get1(Tp &x)
{
	bool neg = 0;
	char c = gc();
	while( c != EOF && (c < '0' || c > '9') && c != '-' ) c = gc();
	if(c == '-') c = gc(), neg = 1;
	if(c == EOF) return false;
	x = 0;
	for(; c>='0' && c<='9'; c = gc()) x = x*10 + c - '0';
	if(neg) x = -x;
	return true;
}
template<typename Tp> void printendl(Tp x)
{
	if(x<0)putchar('-'),x=-x;
	static short a[40], sz;
	sz = 0;
	while(x>0)a[sz++]=x%10,x/=10;
	if(sz==0)putchar('0');
	for(int i=sz-1; i>=0; i--)putchar('0'+a[i]);
	puts("");
}
} // namespace IO
using IO::get1;
using IO::printendl;
#define get2(x,y) get1(x) && get1(y)
#define get3(x,y,z) get2(x,y) && get1(z)
#define get4(x,y,z,w) get3(x,y,z) && get1(w)
#define pb push_back
#define mp std::make_pair
#define ff first
#define ss second
typedef long long LL;
typedef unsigned long long uLL;
typedef std::pair<int,int> pii;
const int inf = 0x3f3f3f3f;
const LL Linf = 1ll<<61;

const int maxn = 8111;

int n, mod;
int qpow(int x, int y)
{
	int ret = 1;
	while(y)
	{
		if(y & 1) ret = 1ll * ret * x % mod;
		x = 1ll * x * x % mod;
		y >>= 1;
	}
	return ret;
}

int c[maxn][maxn], fac[maxn], pr[maxn];
bool isp[maxn];
void prework()
{
	fac[0] = 1; for(int i=1; i<maxn; i++) fac[i] = 1ll * i * fac[i-1] % (mod-1);
	for(int i=0; i<maxn; i++)
	{
		c[i][0] = 1;
		for(int j=1; j<=i; j++)
		{
			c[i][j] = c[i-1][j] + c[i-1][j-1];
			if(c[i][j] >= (mod-1)) c[i][j] -= (mod-1);
		}
	}
	for(int i=2; i<=n; i++) isp[i] = 1;
	for(int i=2; i<=n; i++) if(isp[i]) for(int j=i+i; j<=n; j+=i) isp[j] = 0;
	for(int i=2; i<=n; i++) if(isp[i])
	{
		int now = i;
		while(now <= n)
		{
			pr[now] = i;
			now *= i;
		}
	}
}

int dp[maxn];
int calc(int x)
{
	dp[0] = mod - 2;
	int sz = n / x;
	for(int i=1; i<=sz; i++)
	{
		dp[i] = 0;
		for(int j=1; j<=i; j++) dp[i] = (dp[i] + 1ll * (mod-1-dp[i-j]) * c[i*x-1][j*x-1] % (mod-1) * fac[j*x-1]) % (mod-1);
	}
	int sum = 0;
	for(int i=1; i<=sz; i++) sum = (sum + 1ll * dp[i] * c[n][i*x] % (mod-1) * fac[n-i*x]) % (mod-1);
	return sum;
}

int main()
{
#ifndef EEEEEericKKK
	freopen("exercise.in", "r", stdin);
	freopen("exercise.out", "w", stdout);
#endif
	get2(n, mod);
	prework();
	
	int ans = 1;
	for(int i=2; i<=n; i++) if(pr[i]) ans = 1ll * ans * qpow(pr[i], calc(i)) % mod;
	printendl(ans);
	return 0;
}
Circus

(以下加粗显示的部分都是我比赛的时候猜的,没有证明,不过可以感性理解一下正确性)

先考虑怎么对于一个 kkk 算答案。由于每种放法都可以与某钦定的 kkk 个位置对应,所以可以直接考虑对于这 kkk 个位置的可能的置换。通过手算可以猜结论:对应的置换群可以由一些对换生成,因为产生移位的方法一定可以分解成若干次通过某个度超过 222 的点把两个点swap的操作。由于可交换性显然是一个等价关系,假设由“可交换”形成的连通块的大小分别是 sz1,⋯ ,szmsz_1,\cdots,sz_msz1,,szm ,那么方案数就是 k!∏i=1mszi!\frac{k!}{\prod_{i=1}^m sz_i!}i=1mszi!k! ,于是我们的目标就是求出这些连通块。

找一个度超过 222 的点作根,如果不存在就是一条链的情况,对于所有的 kkk 答案都是 k!k!k!。当 k≥n−1k\ge n-1kn1 时可以发现答案是 k!k!k! ,所以以下假设 k≤n−2k\le n-2kn2 ,且已把一个度超过 222 的点当成根。

可以选择钦定 kkk 个点是深度最大的 kkk 个点,这样的好处是可以把钦定的点表示成若干子树的并。对于一个被钦定的点 xxx ,不难发现它的所有孩子都是可以互相交换的。除掉这种情况只需要考虑 xxx 和它的孩子是否可交换,判断条件是 k+d(x)≤n−2k+d(x)\le n-2k+d(x)n2 ,这里 d(x)d(x)d(x) 是距离 xxx 最近的、不在 xxx 子树内的、度超过 222 的点到 xxx 的距离。由于根的度数超过 222d(x)d(x)d(x) 一定存在,可以用两遍dfs求出。简单理解一下就是找一个度超过 222 的点作为中转点,所以从 xxx 到这个点的路径都要空出来。

最后对于子树之间的情况,可以只考虑子树的根之间是否可交换,事实上当 k≤n−3k\le n-3kn3 时任意两个根都是可交换的;对于 k=n−2k=n-2k=n2 的情况深度相同的根都是可交换的,需要特判。

对于孩子之间、孩子和父亲之间的可交换关系都可以直接连边;对子树的根可以建立一个新点和它们连边。每一条这样的边都有存在的时间:孩子之间的边是只要端点都被钦定就存在;孩子和父亲之间的边是从父亲被钦定到k+d(x)≤n−2k+d(x)\le n-2k+d(x)n2 不满足之前;子树的根和新点的边是这个点是根的一个时间段。我们需要对每个时间维护连通块的大小的阶乘的积,可以使用LCT维护最大生成树或者分治+可撤销并查集。最后对 k=n−2k=n-2k=n2 特判一下即可,分治+可撤销并查集复杂度 O(nlog⁡2n)O(n\log^2n)O(nlog2n) ,LCT复杂度 O(nlog⁡n)O(n\log n)O(nlogn)

fizzydavid说可以证明钦定了 kkk 个点之后只需要考虑这样的 (u,v)(u,v)(u,v) 是否可交换:uuuvvv 的路径上最多只有一个已经钦定的点,仔细想想也有点道理:假如可以交换 uuuvvv ,那么中间所有点都要移开,如果移动了两个点 w,zw, zw,z 那么 uuuzzzwwwvvv 就可交换了。这样大概可以证明除了第一个结论之外的部分。

另外钦定 kkk 个点的方法不止笔者写的这一种,可能有别的方法考虑的情况少一点,或者不需要复杂数据结构维护,希望题解有高论(雾

P.S. CF上有人说的做法是选dfs序前 kkk 个点,据说可以避免动态图连通性;Benq给的std应该是选了深度最小的 kkk 个点,算答案的时候好像还有高论;William Lin选了拓扑序后 kkk 个,这个应该跟我的做法类似。真的是怎么做都行。具体细节等官方题解出来再更。

#include <bits/stdc++.h>

namespace IO
{
char gc()
{
#ifdef FREAD
	static char buf[1<<21], *P1 = buf, *P2 = buf;
	if(P1 == P2)
	{
		P1 = buf;
		P2 = buf + fread(buf, 1, 1<<21, stdin);
		if(P1 == P2) return EOF;
	}
	return *(P1++);
#else
	return getchar();
#endif
}
template<typename Tp> bool get1(Tp &x)
{
	bool neg = 0;
	char c = gc();
	while( c != EOF && (c < '0' || c > '9') && c != '-' ) c = gc();
	if(c == '-') c = gc(), neg = 1;
	if(c == EOF) return false;
	x = 0;
	for(; c>='0' && c<='9'; c = gc()) x = x*10 + c - '0';
	if(neg) x = -x;
	return true;
}
template<typename Tp> void printendl(Tp x)
{
	if(x<0)putchar('-'),x=-x;
	static short a[40], sz;
	sz = 0;
	while(x>0)a[sz++]=x%10,x/=10;
	if(sz==0)putchar('0');
	for(int i=sz-1; i>=0; i--)putchar('0'+a[i]);
	puts("");
}
} // namespace IO
using IO::get1;
using IO::printendl;
#define get2(x,y) get1(x) && get1(y)
#define get3(x,y,z) get2(x,y) && get1(z)
#define get4(x,y,z,w) get3(x,y,z) && get1(w)
#define pb push_back
#define mp std::make_pair
#define ff first
#define ss second
typedef long long LL;
typedef unsigned long long uLL;
typedef std::pair<int,int> pii;
const int inf = 0x3f3f3f3f;
const LL Linf = 1ll<<61;

const int mod = 1e9+7;
const int maxn = 100111;
int qpow(int x, int y)
{
	int ret = 1;
	while(y)
	{
		if(y & 1) ret = 1ll * ret * x % mod;
		x = 1ll * x * x % mod;
		y >>= 1;
	}
	return ret;
}
int fac[maxn], invf[maxn];

std::vector<int> g[maxn];
int n, eu[maxn], ev[maxn], dep[maxn], fa[maxn], sz[maxn], ord[maxn], rk[maxn];

int down[maxn], up[maxn], ans[maxn];
void dfs(int x, int f, int d)
{
	dep[x] = d;
	fa[x] = f;
	sz[x] = 1;
	for(auto v:g[x]) if(v!=f)
	{
		dfs(v,x,d+1);
		sz[x] += sz[v];
		down[x] = std::min(down[x], down[v]+1);
	}
	if((int)g[x].size() > 2) down[x] = 0;
}
void dfs2(int x)
{
	int now = up[x]+1;
	if((int)g[x].size() > 2) now = 1;
	for(int i=0; i<(int)g[x].size(); i++) if(g[x][i] != fa[x])
	{
		up[g[x][i]] = std::min(up[g[x][i]], now);
		now = std::min(now, down[g[x][i]]+2);
	}
	now = up[x]+1;
	if((int)g[x].size() > 2) now = 1;
	for(int i=g[x].size()-1; i>=0; i--) if(g[x][i] != fa[x])
	{
		up[g[x][i]] = std::min(up[g[x][i]], now);
		now = std::min(now, down[g[x][i]]+2);
		dfs2(g[x][i]);
	}
}

int f[maxn], szv[maxn], piv;
int gf(int x) { return x == f[x] ? x : f[x] = gf(f[x]); }
void un(int x, int y)
{
	x = gf(x);
	y = gf(y);
	if(x == y) return;
	f[x] = y;
	szv[y] += szv[x];
}

int calc(int q)
{
	for(int i=1; i<=n; i++)
	{
		f[i] = i;
		szv[i] = 1;
	}
	std::vector<int> vs;
	int cnt = 0;
	for(int t=1; t<=q; t++)
	{
		int x = ord[t], last = 0;
		if(up[x] + q <= n-2) last = x;
		for(int i=0; i<(int)g[x].size(); i++) if(g[x][i] != fa[x])
		{
			if(last) un(last, g[x][i]);
			last = g[x][i];
		}
		if(rk[fa[x]] > q)
		{
			vs.pb(x);
			if(fa[x] == piv) cnt++;
		}
	}
	
	if(dep[ord[q]] > 1 || cnt < (int)g[piv].size() - 1)
	{
		for(int i=1; i<(int)vs.size(); i++)
			un(vs[i-1], vs[i]);
	}
	else
	{
		std::vector<pii> vss;
		int last = 0;
		for(int i=0; i<(int)vs.size(); i++)
		{
			if(fa[vs[i]] == piv)
			{
				if(last) un(last, vs[i]);
				last = vs[i];
			}
			else vss.pb(mp(fa[vs[i]], vs[i]));
		}
		std::sort(vss.begin(), vss.end());
		for(int i=1; i<vss.size(); i++) if(vss[i].ff == vss[i-1].ff) un(vss[i].ss, vss[i-1].ss);
	}
	
	int ret = fac[q];
	for(int i=1; i<=q; i++) if(f[ord[i]] == ord[i]) ret = 1ll * ret * invf[szv[ord[i]]] % mod;
	return ret;
}

namespace solver
{
	int f[maxn], sz[maxn];
	int gf(int x) { return x == f[x] ? x : gf(f[x]); }
	
	struct edge{ int u, v, l, r; };
	std::vector<edge> es;
	void addedge(int u, int v, int l, int r) { es.pb(edge{u, v, l, r}); }
	
	struct change { int u, v, f, sz; };
	std::vector<change> changes;
	
	int cur;
	void un(int u, int v)
	{
		u = gf(u);
		v = gf(v);
		if(u == v) return;
		if(sz[u] < sz[v]) std::swap(u, v);
		changes.pb(change{u, v, f[v], sz[v]});
		cur = 1ll * cur * fac[sz[u]] % mod * fac[sz[v]] % mod;
		f[v] = u;
		sz[u] += sz[v];
		cur = 1ll * cur * invf[sz[u]] % mod;
	}
	void pop()
	{
		auto now = changes.back();
		cur = 1ll * cur * fac[sz[now.u]] % mod;
		sz[now.u] -= now.sz;
		f[now.v] = now.f;
		cur = 1ll * cur * invf[sz[now.u]] % mod * invf[sz[now.v]] % mod;
		changes.pop_back();
	}
	
	void solve(int l, int r, std::vector<edge> es)
	{
		int nn = 0, tmp = changes.size();
		for(int i=0; i<es.size(); i++)
		{
			if(es[i].l <= l && es[i].r >= r) un(es[i].u, es[i].v);
			else es[nn++] = es[i];
		}
		es.resize(nn);
		if(l == r) ans[l] = 1ll * cur * fac[l] % mod;
		else
		{
			std::vector<edge> ne;
			int mid = (l + r) / 2;
			for(int i=0; i<es.size(); i++) if(es[i].l <= mid) ne.pb(es[i]);
			solve(l, mid, ne);
			ne.clear();
			for(int i=0; i<es.size(); i++) if(es[i].r > mid) ne.pb(es[i]);
			solve(mid+1, r, ne);
		}
		while(changes.size() > tmp) pop();
	}
	
	void work()
	{
		for(int x=1; x<=n; x++) if(rk[x] <= n-2)
		{
			int last = 0;
			for(auto v:g[x]) if(v!=fa[x])
			{
				if(last) addedge(last, v, std::max(rk[last], rk[v]), n-2);
				last = v;
			}
			if(last) addedge(last, x, rk[x], n-2-up[x]);
			addedge(n+1, x, rk[x], rk[fa[x]]-1);
		}
		for(int i=1; i<=n+1; i++)
		{
			f[i] = i;
			sz[i] = (i <= n);
		}
		cur = 1;
		solve(1, n-2, es);
	}
} // namespace solver

int main()
{
#ifndef EEEEEericKKK
	freopen("circus.in", "r", stdin);
	freopen("circus.out", "w", stdout);
#endif
	fac[0] = 1; for(int i=1; i<maxn; i++) fac[i] = 1ll * i * fac[i-1] % mod;
	invf[maxn-1] = qpow(fac[maxn-1], mod-2); for(int i=maxn-1; i>=1; i--) invf[i-1] = 1ll * i * invf[i] % mod;
	
	get1(n);
	for(int i=1, u, v; i<n; i++)
	{
		get2(u, v);
		g[u].pb(v);
		g[v].pb(u);
	}
	for(int i=1; i<=n; i++) if((int)g[i].size() > 2)
	{
		piv = i;
		break;
	}
	if(piv == 0)
	{
		for(int i=1; i<=n; i++) printf("%d\n", fac[i]);
		return 0;
	}
	
	for(int i=1; i<=n; i++) down[i] = up[i] = inf;
	dfs(piv, 0, 0);
	dfs2(piv);
	for(int i=1; i<=n; i++) ord[i] = i;
	std::sort(ord+1, ord+n+1, [](int x, int y) { return mp(dep[x], -x) > mp(dep[y], -y); });
	for(int i=1; i<=n; i++) rk[ord[i]] = i;
	
	if(n > 3) solver::work();
	ans[n-2] = calc(n-2);
	ans[n-1] = fac[n-1];
	ans[n] = fac[n];
	for(int i=1; i<=n; i++) printf("%d\n", ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值