这是我作为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,⋯lk 求 maxordp(li)\max \text{ord}_p(l_i)maxordp(li) ,然后加起来对 M−1M-1M−1 取模。结合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)∣S∣−1x≥1∑i∈S∏[ordp(li)≥x]
于是我们可以枚举 ppp 和 xxx ,剩下的计算相当于强制某些环的长度是 pxp^xpx 的倍数,令 s=pxs=p^xs=px ,则可以枚举是 sss 倍数的环的总长度 i⋅si\cdot si⋅s 。转移方程是:
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=1∑ifi−j(j⋅s−1i⋅s−1)(j⋅s−1)!
对于这个 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_i∑i=0⌊sn⌋(s⋅in)(n−s⋅i)!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}n2∑i=1ni−2≤6n2π2 ,也就是 O(n2)O(n^2)O(n2) 。可以注意到 fff 的计算大概是一个多项式 exp\expexp 的形式,所以能优化到O(n⋅poly(logn))O(n\cdot\text{poly}(\log n))O(n⋅poly(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-1k≥n−1 时可以发现答案是 k!k!k! ,所以以下假设 k≤n−2k\le n-2k≤n−2 ,且已把一个度超过 222 的点当成根。
可以选择钦定 kkk 个点是深度最大的 kkk 个点,这样的好处是可以把钦定的点表示成若干子树的并。对于一个被钦定的点 xxx ,不难发现它的所有孩子都是可以互相交换的。除掉这种情况只需要考虑 xxx 和它的孩子是否可交换,判断条件是 k+d(x)≤n−2k+d(x)\le n-2k+d(x)≤n−2 ,这里 d(x)d(x)d(x) 是距离 xxx 最近的、不在 xxx 子树内的、度超过 222 的点到 xxx 的距离。由于根的度数超过 222 ,d(x)d(x)d(x) 一定存在,可以用两遍dfs求出。简单理解一下就是找一个度超过 222 的点作为中转点,所以从 xxx 到这个点的路径都要空出来。
最后对于子树之间的情况,可以只考虑子树的根之间是否可交换,事实上当 k≤n−3k\le n-3k≤n−3 时任意两个根都是可交换的;对于 k=n−2k=n-2k=n−2 的情况深度相同的根都是可交换的,需要特判。
对于孩子之间、孩子和父亲之间的可交换关系都可以直接连边;对子树的根可以建立一个新点和它们连边。每一条这样的边都有存在的时间:孩子之间的边是只要端点都被钦定就存在;孩子和父亲之间的边是从父亲被钦定到k+d(x)≤n−2k+d(x)\le n-2k+d(x)≤n−2 不满足之前;子树的根和新点的边是这个点是根的一个时间段。我们需要对每个时间维护连通块的大小的阶乘的积,可以使用LCT维护最大生成树或者分治+可撤销并查集。最后对 k=n−2k=n-2k=n−2 特判一下即可,分治+可撤销并查集复杂度 O(nlog2n)O(n\log^2n)O(nlog2n) ,LCT复杂度 O(nlogn)O(n\log n)O(nlogn) 。
fizzydavid说可以证明钦定了 kkk 个点之后只需要考虑这样的 (u,v)(u,v)(u,v) 是否可交换:uuu 到 vvv 的路径上最多只有一个已经钦定的点,仔细想想也有点道理:假如可以交换 uuu 和 vvv ,那么中间所有点都要移开,如果移动了两个点 w,zw, zw,z 那么 uuu 和 zzz 、www 和 vvv 就可交换了。这样大概可以证明除了第一个结论之外的部分。
另外钦定 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;
}