A. 玩水
一直以为这是 \(dp\) ,但是只是一个性质/结论
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
return x;
}
const int maxn = 1005;
char mp[maxn][maxn];
bool f[maxn][maxn], p[maxn][maxn];
int n, m;
bool check(){
for(int i = 1; i < n; ++i)
for(int j = 1; j < m; ++j)
p[i][j] = f[i][j] = (mp[i + 1][j] == mp[i][j + 1]);
for(int i = n - 2; i > 0; --i)
for(int j = 1; j < m; ++j){
if(p[i][j] && p[i + 1][j])return true;
f[i][j] |= f[i + 1][j];
}
for(int i = n - 1; i > 0; --i){
for(int j = m - 2; j > 0; --j){
if(p[i][j] && p[i][j + 1])return true;
if(p[i][j] && f[i + 1][j + 1] && i + 1 < n && j + 1 < m)return true;
f[i][j] |= f[i][j + 1];
}
}
return false;
}
int main(){
freopen("water.in","r",stdin);
freopen("water.out","w",stdout);
int T = read();
for(int i = 1; i <= T; ++i){
n = read(), m = read();
for(int i = 1; i <= n; ++i)scanf("%s", mp[i] + 1);
if(check())printf("1\n");
else printf("0\n");
}
return 0;
}
B. AVL 树
奇妙
按照先序考虑每个点,如果能够加入就加入,不能加入则跳过该子树
可以预处理出来当深度为 \(i\) 时, \(AVL\) 最少的节点数 \(f_i = f_{i - 1} + f_{i - 2} + 1\)
顺次考虑每个点时,我们每次向上跳,如果当前点作为左子树,用 \(f\) 算出右子树至少需要加入多少点,如果加入当前点需要加入的点数小于等于剩余点数就加入
这样,我们还需要维护当前确定的 \(AVL\) 中每个点子树的最大深度, 以及每个子树至少有多少深度,这样我们在加入一个点时,就能够计算保证之前加入的点对应的那些至少加入的点数一定被计算了
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; bool f = 0;char c = getchar();
while(c < '0' || c > '9'){if(c == '-')f = 1;c = getchar();}
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
return f ? -x : x;
}
const int maxn = 500005;
int n, k, root, l[maxn], r[maxn], fa[maxn], mx[maxn], dep[maxn];
bool flag[maxn];
int mi[maxn], ndp[maxn], f[maxn], res;
void pre(int x){
mx[x] = dep[x] = dep[fa[x]] + 1;
if(l[x]){
pre(l[x]);
mx[x] = max(mx[x], mx[l[x]]);
}
if(r[x]){
pre(r[x]);
mx[x] = max(mx[x], mx[r[x]]);
}
}
int check(int x){
int ndep = max(dep[x], ndp[x]);
int ans = 1;
while(fa[x]){
bool left = (l[fa[x]] == x);
x = fa[x];
ndep = max(ndep, ndp[x]);
if(left && r[x])ans += f[max(ndep - 1, mi[r[x]]) - dep[x]];
}
return ans;
}
void upd(int x){
int ndep = ndp[x] = max(ndp[x], dep[x]);
while(x){
x = fa[x];
ndp[x] = ndep = max(ndp[x], ndep);
if(r[x])mi[r[x]] = max(mi[r[x]], ndep - 1);
}
}
void dfs(int x){
int cnt = check(x);
if(cnt <= res){
upd(x);
flag[x] = 1; --res;
if(l[x] && r[x]){
int dt = mx[l[x]] < mi[x];
mi[l[x]] = max(mi[l[x]], mi[x] - dt);
mi[r[x]] = max(mi[r[x]], mi[x] + dt - 1);
dfs(l[x]); dfs(r[x]);
return;
}
if(l[x]){
mi[l[x]] = max(mi[l[x]], mi[x]);
dfs(l[x]);
}
if(r[x]){
mi[r[x]] = max(mi[r[x]], mi[x]);
dfs(r[x]);
}
}
}
int main(){
freopen("avl.in","r",stdin);
freopen("avl.out","w",stdout);
n = read(); res = k = read();
for(int i = 1; i <= n; ++i){
int x = read();
if(x == -1)root = i;
else{
fa[i] = x;
if(i > x)r[x] = i;
else l[x] = i;
}
}
f[1] = 1; f[2] = 2;
for(int i = 3; i <= n; ++i)f[i] = (f[i - 1] + f[i - 2] + 1);
pre(root);
dfs(root);
for(int i = 1; i <= n; ++i)printf("%d", flag[i]);
return 0;
}
C. 暴雨
设 \(f_{i, j, k, 1 / 0}\) 表示前 \(i\) 个,铲平了 \(k\) 块, 最大高度为 \(j\), 要求后面存在 \(>= j\) 的 积水为 奇数/偶数的方案数
同理得出后缀,然后枚举最大值的位置合并答案
因为最多铲去 \(k\) 块, 那么最大值只有 \(k + 1\) 种选法, 通过映射可以转移
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
return x;
}
const int mod = 1e9 + 7;
const int maxn = 25005;
int n, k, h[maxn], pre[maxn][29], nxt[maxn][29], pn[maxn], nn[maxn];
int low[maxn][29], row[maxn][29];
multiset<int>s;
int f[maxn][27][2][27], g[maxn][27][2][27];
inline void add(int &x, int y){
x += y; x = x >= mod ? x - mod : x;
}
inline int mul(int x, int y){return 1ll * x * y % mod;}
int main(){
// freopen("rain.in","r",stdin);
// freopen("rain.out","w",stdout);
n = read(), k = read();
if(n == 1 && k == 0){
printf("1\n");
return 0;
}
for(register int i = 1; i <= n; ++i)h[i] = read();
s.insert(0);
for(int i = 1; i <= n; ++i){
s.insert(h[i]);
while(s.size() > k + 1)s.erase(s.begin());
for(int x : s)pre[i][++pre[i][0]] = x;
}
for(int i = 1; i < n; ++i){
pn[i + 1] = lower_bound(pre[i + 1] + 1, pre[i + 1] + pre[i + 1][0] + 1, h[i + 1]) - pre[i + 1];
for(int j = 1; j <= pre[i][0]; ++j)
low[i][j] = lower_bound(pre[i + 1] + 1, pre[i + 1] + 1 + pre[i + 1][0], pre[i][j]) - pre[i + 1];
}
f[1][0][0][2] = 1;
if(k)f[1][1][0][1] = 1;
for(register int i = 1; i < n; ++i){
int mk = min(i, k);
for(register int nk = 0; nk <= mk; ++nk){
for(register int op = 0; op <= 1; ++op)
for(int m = 1; m <= pre[i][0]; ++m){
if(f[i][nk][op][m]){
int hi = pre[i][m], ct = f[i][nk][op][m], po = low[i][m];
if(nk < k)add(f[i + 1][nk + 1][(op + hi) & 1][po], ct);
if(h[i + 1] < hi) add(f[i + 1][nk][(op + hi - h[i + 1]) & 1][po], ct);
else add(f[i + 1][nk][op][pn[i + 1]], ct);
}
}
}
}
s.clear();
s.insert(0);
for(int i = n; i >= 1; --i){
s.insert(h[i]);
while(s.size() > k + 1)s.erase(s.begin());
for(int x : s)nxt[i][++nxt[i][0]] = x;
}
for(int i = n; i > 1; --i){
nn[i - 1] = lower_bound(nxt[i - 1] + 1, nxt[i - 1] + nxt[i - 1][0] + 1, h[i - 1]) - nxt[i - 1];
for(int j = 1; j <= nxt[i][0]; ++j)
row[i][j] = lower_bound(nxt[i - 1] + 1, nxt[i - 1] + 1 + nxt[i - 1][0], nxt[i][j]) - nxt[i - 1];
}
if(k)g[n][1][0][1] = 1;
g[n][0][0][2] = 1;
for(register int i = n; i > 1; --i){
int mk = min(n - i + 1, k);
for(register int nk = 0; nk <= mk; ++nk){
for(register int op = 0; op <= 1; ++op)
for(int m = 1; m <= nxt[i][0]; ++m){
if(g[i][nk][op][m]){
int hi = nxt[i][m], ct = g[i][nk][op][m], po = row[i][m];
if(nk < k)add(g[i - 1][nk + 1][(op + hi) & 1][po], ct);
if(h[i - 1] < hi) add(g[i - 1][nk][(op + hi - h[i - 1]) & 1][po], ct);
else add(g[i - 1][nk][op][nn[i - 1]], ct);
}
}
}
}
ll ans = 0;
for(int i = 1; i <= pre[n - 1][0]; ++i)if(pre[n - 1][i] <= h[n]) ans += f[n - 1][k][0][i]; else break;
for(int i = 1; i <= nxt[2][0]; ++i)if(nxt[2][i] < h[1]) ans += g[2][k][0][i]; else break;
for(register int i = 2; i < n; ++i){
for(register int kl = 0; kl <= k; ++kl){
int kr = k - kl;
int sl0 = 0, sl1 = 0;
for(int j = 1; j <= pre[i - 1][0]; ++j)
if(pre[i - 1][j] <= h[i]){
add(sl0, f[i - 1][kl][0][j]);
add(sl1, f[i - 1][kl][1][j]);
}else break;
int sr0 = 0, sr1 = 0;
for(int j = 1; j <= nxt[i + 1][0]; ++j)
if(nxt[i + 1][j] < h[i]){
add(sr0, g[i + 1][kr][0][j]);
add(sr1, g[i + 1][kr][1][j]);
}else break;
ans += mul(sl0, sr0);
ans += mul(sl1, sr1);
}
}
printf("%lld\n",ans % mod);
return 0;
}
D. 置换
有个性质就是置换可以看做一些点连成环, \(f(p)\) 就是环长度的 \(lcm\), 那么环长之和为 $$
设 \(f_{i, j}\) 表示用了 \(i\) 个数, 此时 \(lcm\) 为 \(j\) 的贡献
有很多好写的 \(60pts\) 转移,但是为了引出正解,不得不用一种我认为很恶心的转移
枚举环长 \(l\) ,从大到小考虑 \(sum\), 然后枚举放 \(k\) 个环, 那么方案数为
选择 \(C_{n - sum}^{k \times l}\)
全排转组合 $fac_{k \times l} \times facinv_{l}^k $
各个环顺序无关 \(\times facinv_{k}\)
每个组合内园排列 \(\times fac_{l - 1}^k\)
最后为 \(fac_{k \times l} \times inv_{l}^k \times facinv_{k} \times C_{n - sum}^{k \times l}\)
然后此时的瓶颈在于第二维太多,考虑根号分治
上面的 \(dp\) 可以转化成直接算贡献的,这样转移每次乘上 \(lcm\) 变化倍数的平方即可
考虑优化
对环长按照最大质因子分类,
特别的$ <= \sqrt n $ 的算作一类,正常转移
然后对于每个 \(p> \sqrt n\), 他在最终 \(lcm\) 的幂次最大为 \(1\), 我们去掉 \(p\) 转移, 然后统一算上 \(p\) 的贡献
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
int x = 0; char c = getchar();
while(c < '0' || c > '9')c = getchar();
do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
return x;
}
const int maxn = 205;
int n, mod;
ll gcd(ll a, ll b){return b ? gcd(b, a % b) : a;}
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
ll lcm(ll x, ll y){
if(!x || !y)return x | y;
return x * y / gcd(x, y);
}
void add(int &x, int y){
x += y; x = x >= mod ? x - mod : x;
}
const int mx = 25000;
unordered_map<int, int>f[maxn], tmp[maxn], id;
vector<int>p[maxn];
int prime[maxn], cnt, tot, pr[maxn];
bool flag[maxn];
int fac[maxn], inv[maxn];
int C(int n, int m){return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;}
int main(){
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
n = read(), mod = read();
fac[0] = inv[0] = 1; for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
inv[n] = qpow(fac[n], mod - 2); for(int i = n - 1; i > 0; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
for(int i = 2; i <= n; ++i){
if(!flag[i])prime[++cnt] = i;
for(int j = 1; j <= cnt && i * prime[j] <= n; ++j){
flag[i * prime[j]] = 1;
if(i % prime[j] == 0)continue;
}
}
int gn = sqrt(n);
for(int i = 1; i <= cnt; ++i)if(prime[i] > gn)id[prime[i]] = ++tot, pr[tot] = prime[i];
for(int i = 1; i <= n; ++i){
bool f = 0;
for(int j = 1; j <= tot; ++j){
int x = pr[j];
if(i % x == 0){
p[j].push_back(i / x);
f = 1;
break;
}
}
if(!f)p[0].push_back(i);
}
f[0][1] = 1; pr[0] = 1;
for(int now = 0; now <= tot; ++now){
if(p[now].empty())continue;
for(int i = 0; i <= n; ++i)tmp[i] = f[i];
for(int len : p[now]){
int kl = len * pr[now];
for(int sum = n; sum >= 0; --sum){
for(int k = 1; k * kl + sum <= n; ++k){
int ds = k * kl;
int base = 1ll * fac[ds] * C(n - sum, ds) % mod * inv[k] % mod * qpow(qpow(kl, k), mod - 2) % mod;
for(auto x : tmp[sum]){
int lc = lcm(x.first, len), dt = (lc / x.first);
add(tmp[sum + ds][lc], 1ll * dt * dt % mod * x.second % mod * base % mod);
}
}
}
}
if(now)
for(int sum = 0; sum <= n; ++sum){
for(auto x : tmp[sum]){
x.second -= f[sum][x.first]; x.second = (x.second % mod + mod) % mod;
add(f[sum][x.first], 1ll * x.second * pr[now] % mod * pr[now] % mod);
}
tmp[sum].clear();
}
else for(int i = 0; i <= n; ++i)f[i] = tmp[i];
}
int ans = 0;
for(auto x : f[n]){
add(ans, x.second);
}
ans = 1ll * ans * inv[n] % mod;
ans = (ans % mod + mod) % mod;
printf("%d ",ans);
return 0;
}