来自学长的告别
A. 进攻
\(f_{i,j}\) 表示 \(i\) 个结点的,最大深度为 \(j\) 的树的数量
因为 \(2\) 的父亲只可能是 \(1\) 那么我们可以通过枚举 \(2\) 所在子树和其他子树进行拼接
具体的,考虑最大深度是由哪棵子树贡献的,直接给出式子,耐心理解一下
\(\displaystyle \large f_{i, j} = \sum _{x = 1} ^ {i - 1}\sum_{y = 1}^{j - 2}C_{i - 2} ^{x - 1}f_{x, y}f_{i - x, j} + \sum _{x = 1} ^ {i - 1}\sum_{y = 1}^{j}C_{i - 2} ^{x - 1}f_{i - x, y}f_{ x, j - 1}\)
然后用分配率前缀和优化即可
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
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 >= '0' && c <= '9');
return x;
}
typedef long long ll;
const int maxn = 405;
int n, p, inv[maxn], fac[maxn];
ll f[maxn][maxn], s[maxn][maxn];
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % p)if(y & 1)ans = 1ll * ans * x % p;
return ans;
}
int c(int n, int m){return 1ll * fac[n] * inv[m] % p * inv[n - m] % p;}
int main(){
n = read(), p = read();
fac[0] = 1; for(int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % p;
inv[n] = qpow(fac[n], p - 2); for(int i = n - 1; i; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % p;
inv[0] = 1;
f[1][1] = s[1][1] = 1;
for(int i = 1; i <= n; ++i) s[1][i] = 1;
for(int i = 2; i <= n; ++i){
for(int j = 1; j <= i; ++j)
for(int x = 1; x < i ; ++x)
f[i][j] = (f[i][j] + c(i - 2, x - 1) * (f[i - x][j] * s[x][j - 2] % p + f[x][j - 1] * s[i - x][j] % p) % p ) % p;
for(int j = 1; j <= n; ++j) s[i][j] = (s[i][j - 1] + f[i][j]) % p;
}
int ans = 0;
for(int i = 1; i <= n; ++i)ans = (ans + f[n][i] * i % p) % p;
printf("%lld\n",1ll * inv[n - 1] * ans % p);
return 0;
}
B. Star Way To Heaven
上下边界通过某些星星相交,那么无解
所以可以二分答案判断是否可行
正解需要进一步思考
发现这个让上下边界联通的最小的距离就是上下边界与星星构成的最小生成树上路径最大值的一半
因为边数太多,所以用\(prim\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
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 >= '0' && c <= '9');
return x;
}
typedef long long ll;
const int maxn = 1000005;
const long double inf = 0x3f3f3f3f3f3f;
double m,x[maxn], y[maxn];
int now, n, k;
long double dis[maxn], minn, an[maxn];
bool vis[maxn];
long double DIS(int a, int b){return sqrt((long double)(a[x] - b[x]) * (a[x] - b[x]) + (a[y] - b[y]) * (a[y] - b[y]));}
int main(){
n = read(), m = read(), k = read();
int t = k + 1;
for(int i = 1; i <= k; ++i){
x[i] = read(), y[i] = read();
dis[i] = y[i];
}
dis[t] = m;
for(;1;){
minn = inf; now = 0;
for(int i = 1; i <= k + 1; ++i)
if(!vis[i] && dis[i] < minn){
minn = dis[i]; now = i;
}
if(now == t)break;
vis[now] = 1;
for(int i = 1; i <= k; ++i){
if(!vis[i]){
long double ds = DIS(now, i);
if(ds < dis[i]){
dis[i] = ds;
an[i] = max(an[now], ds);
}
}
}
long double ds = m - y[now];
if(ds < dis[t]){
dis[t] = ds;
an[t] = max(an[now], ds);
}
}
printf("%Lf\n",an[t] / 2);
return 0;
}
C. God Knows
问题可以转化为求极长上升子序列的最小权值
考虑插入一个新的数 \(i\) ,首先要找出\(p_j < p_i\)的 不存在后继的数的最小的\(f_j\)
如果不考虑其他数,那么单调栈可以简单维护,但是由于我们每次插入 \(p_i\)是乱序,那么现在弹栈的数将来有可能不弹栈而且更优
所以使用线段树维护
形式化的? 考虑以\(p\) 为横轴, \(i\) 为纵轴的坐标系, 我们要做的是找某个位置之前一个单调递减的序列的 \(f\) 的最小值
首先考虑处理整个区间,设新插入的数为 \(qmax\),右区间最大值为\(rmax\),分两种情况
-
\(qmax > rmax\)整个右区间没有贡献,直接递归左区间
-
\(qmax < rmax\)对于左区间来说,弹掉他们的不是\(qmax\),而是\(rmax\),相对来说是个比较固定的数,我们可以预处理 \(fmin\) 表示由 \(rmax\) 弹栈时最小的\(f\),然后只需要递归右子树取个\(min\)
这样我们保证每次只往一个子树走,那么复杂度为\(log\)
再来考虑一般的询问,我们需要从右往左进行查询, 在全局维护当前右侧最大值,进行弹栈
我们只有在递归到线段树整块区间时才会调用上面的特殊情况,这样的区间有 \(log\) 个,而调用整块区间又是\(log\),所以是 \(log^2\)那么我们的\(fmin\)就不能在查询时计算,只能在修改时计算
所以修改时调用一次上面的整块查询即可
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
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 >= '0' && c <= '9');
return x;
}
const int maxn = 300055;
const int inf = 0x3f3f3f3f;
int n, p[maxn], c[maxn], val[maxn];
bool flag[maxn];
struct tree{
struct node{
int mx, fm, fmin;
}t[maxn << 2 | 1];
void pre_work(int x, int l, int r){
t[x].fm = inf;
t[x].mx = -1;
if(l == r)return;
int mid = (l + r) >> 1;
pre_work(x << 1, l, mid);
pre_work(x << 1 | 1, mid + 1, r);
}
int solve(int x, int l, int r, int qmax){
if(l == r)return t[x].mx > qmax ? t[x].fm : inf;
int mid = (l + r) >> 1;
if(t[x << 1 | 1].mx > qmax)return min(t[x << 1].fmin, solve(x << 1 | 1, mid + 1, r, qmax));
else return solve(x << 1, l, mid, qmax);
}
int qmx;
int query(int x, int l, int r, int L, int R){
if(L <= l && r <= R){
int ans = solve(x, l, r, qmx);
qmx = max(qmx, t[x].mx);
return ans;
}
int ans = inf, mid = (l + r) >> 1;
if(R > mid)ans = min(ans, query(x << 1 | 1, mid + 1, r, L, R));
if(L <= mid)ans = min(ans, query(x << 1, l, mid, L, R));
return ans;
}
void modify(int x, int l, int r, int pos, int ival, int fval){
if(l == r){
t[x].fm = fval;
t[x].mx = ival;
return;
}
int mid = (l + r) >> 1, ls = x << 1, rs = x << 1 | 1;
if(pos <= mid)modify(ls, l, mid, pos, ival, fval);
else modify(rs, mid + 1, r, pos, ival, fval);
t[x].mx = max(t[ls].mx, t[rs].mx);
t[ls].fmin = solve(ls, l, mid, t[rs].mx);
t[x].fm = min(t[ls].fmin, t[rs].fm);
}
}t;
int main(){
n = read();
for(int i = 1; i <= n; ++i)p[i] = read();
for(int i = 1; i <= n; ++i)c[i] = read();
++n; p[n] = n;
t.pre_work(1, 0, n);
t.modify(1, 0, n, 0, 0, 0);
for(int i = 1; i <= n; ++i){
t.qmx = -1;
int k = t.query(1, 0, n, 0, p[i] - 1) + c[i];
t.modify(1, 0, n, p[i], i, k);
if(i == n)printf("%d\n",k);
}
return 0;
}
D. Lost My Music
通过打表发现决策点是个栈,于是用\(vector\)乱搞水到\(80\),然后大概被蒲公英(链套菊花)卡掉了
实际上维护的是一个下凸包(式子提出一个负号可以转化成斜率)
那么弹栈可以二分
此时发现瓶颈在于儿子继承父亲信息进行弹栈的操作太多,而我们不得不留存父亲的信息,因为不同儿子的弹栈可能不同
进一步发现,弹栈后实际上儿子只是修改了某个位置上的值,那我们每次递归时记录历史值,修改后处理子树,回溯时归还历史值,就能很快解决问题
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 500005;
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 >= '0' && c <= '9');
return x;
}
int n;
int head[maxn], tot, fa[maxn],c[maxn],dep[maxn],sta[maxn];
struct edge{int to,net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
double ans[maxn];
int mr(int x, int top){
int l = 2, r = top;
while(l <= r){
int mid = (l + r) >> 1;
double k1 = (double)(c[sta[mid]] - c[sta[mid - 1]]) / (dep[sta[mid - 1]] - dep[sta[mid]]);
double k2 = (double)(c[x] - c[sta[mid]]) / (dep[sta[mid]] - dep[x]);
if(k1 < k2)r = mid - 1;
else l = mid + 1;
}
return l;
}
void dfs(int x , int top){
int pos = mr(x, top), history = sta[pos];
int y = sta[pos - 1];
ans[x] = (double)(c[y] - c[x])/(dep[x] - dep[y]);
sta[pos] = x;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
dep[v] = dep[x] + 1;
dfs(v, pos);
}
sta[pos] = history;
}
int main(){
n = read();
for(int i = 1; i <= n; ++i)c[i] = read();
for(int i = 2; i <= n; ++i)add(fa[i] = read(), i);
dep[1] = 1;sta[1] = 1;
dfs(1, 1);
for(int i = 2; i <= n; ++i)printf("%lf\n",ans[i]);
return 0;
}