A. 星际穿越
感觉也很神。。
先将询问拆成前缀和的形式,我们要对于一组询问 ((l,r)) 求出 ([l,r]) 内所有点到 (r) 的最短距离的和。
考虑 (r) 往左走的最短路径是怎么走的:
引理一:往右走之前一定不会往左走
反证法。如果先往左走在往右走,那么这个右边的点一定能 cover 到起点,所以可以第一步就先往右走,答案不会变劣。
引理二:不会存在连续的两次往右走
反证法。如果存在两次连续的往右走,那么之后一定有一次往左走,并且跨越起点的,我们可以一开始就走到这个点,答案不会变劣。
所以我们的路径只有可能是先往右最多走一次,再往左走。
我们观察一下一个点 (v) 到达左边的点 (x) 的最短距离的分布是怎么样的:
如果 (dis(v,x)=1),那么有 (l_v leq x)。
如果 (dis(v,x) leq 2),那么我们就会先走一步,用这个点中转,也就是问在区间 ([l_v,n]) 内是否存在点 (y),使得 (dis(x,y) leq 1)。注意这里这个区间包含了一些根本不跨过 (v) 的点,但这是显然不影响答案的。
归纳可以得到:如果 (dis(v,x) leq k),当且仅当存在一个点 (y in [l_v,n]),满足 (dis(x,y) leq k-1)。
所以一个暴力的想法是每次先特判了距离为 (1) 的点,然后每次求出最往左的距离为 (2) 的点跳过去,以此类推。。
对于跳这种东西,我们肯定想到要用倍增优化。我们想找到 (leq k) 的最往左的点,就要找到 (dis(x,y) leq k-1) 的最往左的点。。。所以我们定义在找 (dis(v,x) leq k) 的一次跳跃是从 (x) 点找到最小的一个点 (z),满足 (exists y in [l_v,n],dis(z,y) leq k-1),所以设 (f_{i,j}) 表示从 (i) 号点,跳了 (2^j) 次(也就是处理了距离为 ([1,2^j+1]) 的点),为了统计答案,设 (g_{i,j}) 表示从 (i) 号点跳了 (2^j) 的所有答案,初始化 (f_{i,0} = min_{j geq i}{l_j}),(f_{1,0} = 0,g_{i,0} = i-f_{i,0})。转移大概写一下 :
查询的时候跳一下,记一下已经跳了多少长度即可。最后有一些零散的边界要判掉。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 3e5 + 5;
const int MAXM = 18;
int n,l[MAXN];
// 处理[i,n]跳2^j步的最远位置 和这一块的答案
int f[MAXN][MAXM+1];
LL g[MAXN][MAXM+1];
int pw[MAXM+1];
inline LL calc(int l,int p){
if(l > p) return 0;
if(l >= ::l[p]) return p-l;
LL res = p-::l[p],now = 1;p = ::l[p];
ROF(i,MAXM,0){
if(f[p][i] < l) continue;
res += g[p][i] + now*(p-f[p][i]);
now += (1<<i);
p = f[p][i];
}
if(l <= p) res += (now+1)*(p-l);
return res;
}
inline LL gcd(LL x,LL y){
return !y ? x : gcd(y,x%y);
}
int main(){
pw[0] = 1;FOR(i,1,MAXM) pw[i] = pw[i-1]<<1;
scanf("%d",&n);
FOR(i,2,n) scanf("%d",l+i),f[i][0] = l[i];
ROF(i,n-1,2) f[i][0] = std::min(f[i][0],f[i+1][0]),g[i][0] = i-f[i][0];
f[1][0] = 0;
FOR(j,1,MAXM){
FOR(i,1,n){
f[i][j] = f[f[i][j-1]][j-1];
g[i][j] = g[i][j-1]+g[f[i][j-1]][j-1]+(f[i][j-1]-f[i][j])*pw[j-1];
}
}
int q;scanf("%d",&q);
// DEBUG(calc(3,6));
// exit(0);
while(q--){
int l,r,x;scanf("%d%d%d",&l,&r,&x);
LL res = calc(l,x)-calc(r+1,x);
LL g = gcd(r-l+1,res);
printf("%lld/%lld
",res/g,(r-l+1)/g);
}
return 0;
}
B. Minimax
设 (f_{v,i}) 表示 (v) 号点权值为 (i) 的概率,考虑如何合并两个子树 (x,y):
这种 dp 维护可以搞个线段树合并,每次在区间 ([l,r]) 的时候考虑同时维护一个对 (x,y) 分别应该打什么标记,类似cdq分治那样处理左边对右边的贡献,和右边对左边的贡献即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 3e5 + 5;
const int ha = 998244353;
const int inv = 796898467;
int n;
std::vector<int> G[MAXN];
int a[MAXN];
int rt[MAXN],sm[MAXN*25],tag[MAXN*25],lc[MAXN*25],rc[MAXN*25],tot;
inline void add(int &x,int y){
x += y-ha;x += x>>31&ha;
}
inline void cover(int x,int d){
// add(tag[x],d);
tag[x] = 1ll*tag[x]*d%ha;
sm[x] = 1ll*sm[x]*d%ha;
}
inline void pushdown(int x){
if(tag[x] != 1){
if(lc[x]) cover(lc[x],tag[x]);
if(rc[x]) cover(rc[x],tag[x]);
tag[x] = 1;
}
}
int p,M;
inline void update(int &x,int l,int r,int p){
x = ++tot;sm[x] = tag[x] = 1;
if(l == r) return;
int mid = (l + r) >> 1;
if(p <= mid) update(lc[x],l,mid,p);
else update(rc[x],mid+1,r,p);
}
inline int merge(int x,int y,int dx,int dy){
if(!x && !y) return 0;
if(!x || !y){
cover(x|y,x?dx:dy);
return x|y;
}
pushdown(x);pushdown(y);
int lsx = sm[lc[x]],rsx = sm[rc[x]],lsy = sm[lc[y]],rsy = sm[rc[y]];
lc[x] = merge(lc[x],lc[y],(dx+1ll*(1+ha-p)%ha*rsy%ha)%ha,(dy+1ll*(1+ha-p)%ha*rsx%ha)%ha);
rc[x] = merge(rc[x],rc[y],(dx+1ll*p*lsy%ha)%ha,(dy+1ll*p*lsx%ha)%ha);
sm[x] = (sm[lc[x]]+sm[rc[x]])%ha;
return x;
}
inline int query(int x,int l,int r,int p){
if(l == r) return sm[x];
int mid = (l + r) >> 1;pushdown(x);
if(p <= mid) return query(lc[x],l,mid,p);
return query(rc[x],mid+1,r,p);
}
inline void dfs(int v){
if(G[v].empty()){
update(rt[v],1,M,a[v]);
return;
}
for(auto x:G[v]) dfs(x);
if(G[v].size() == 1){
rt[v] = rt[G[v][0]];
return;
}
p = a[v];
rt[v] = merge(rt[G[v][0]],rt[G[v][1]],0,0);
}
int main(){
scanf("%d",&n);
FOR(i,1,n){
int f;scanf("%d",&f);
if(f) G[f].pb(i);
}
std::vector<int> S;
FOR(i,1,n){
scanf("%d",a+i);
if(G[i].empty()) S.pb(a[i]);
else a[i] = 1ll*a[i]*inv%ha;
}
std::sort(all(S));M = S.size();
FOR(i,1,n) if(G[i].empty()) a[i] = std::lower_bound(all(S),a[i])-S.begin()+1;
dfs(1);
int ans = 0;
FOR(i,1,M){
int t = query(1,1,M,i);
// DEBUG(t);
add(ans,1ll*i*S[i-1]%ha*t%ha*t%ha);
}
printf("%d
",ans);
return 0;
}
C. 随机算法
考虑给定最终选出的一个独立集序列 (p_1,p_2,ldots,p_m),如何求有多大的概率生成的排列能得到这个序列。
首先第一个数一定是 (p_1),接下来和 (p_1) 相连的点就可以乱放了,在剩下的点中第一个要放的一定是 (p_2),然后和 (p_2) 相连,还没有被放完的点也可以乱放了。所以我们处理出 (sz_S) 表示集合 (S) 放完之后,有多少个点可以乱放(包含集合 (S) 内的点),并且这个只对独立集有定义。
所以概率就是:
我们考虑对这个dp:设 (f_S) 表示最终独立集序列为 (S) ,所有 (|S|!) 种方案的概率的和,答案显然就是每个最大独立集的 (f_S)。转移只需要枚举一个点,如果可以加入这个独立集就把对应的概率乘上就行了。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 20;
const int ha = 998244353;
#define lowbit(x) ((x)&(-(x)))
int n,m,e[MAXN];
int ee[(1<<MAXN)+3],sz[(1<<MAXN)+3];// 选了独立集 S 后 位置可以自由选择的位置的数量
int f[(1<<MAXN)+3];// S 内 任意顺序排列概率的和
bool vis[(1<<MAXN)+3];
int inv[MAXN+1];
inline void add(int &x,int y){
x += y-ha;x += (x>>31&ha);
}
int main(){
inv[1] = 1;FOR(i,2,MAXN) inv[i] = (ha-1ll*(ha/i)*inv[ha%i]%ha)%ha;
scanf("%d%d",&n,&m);
FOR(i,1,m){
int u,v;scanf("%d%d",&u,&v);--u;--v;
e[u] |= (1<<v);e[v] |= (1<<u);
}
FOR(i,0,n-1) ee[1<<i] = e[i];
FOR(S,1,(1<<n)-1) ee[S] = ee[S-lowbit(S)]|ee[lowbit(S)];
int mx = 0;
FOR(S,0,(1<<n)-1) if(!(ee[S]&S)) vis[S] = 1,mx = std::max(mx,(int)__builtin_popcount(S));
FOR(S,0,(1<<n)-1) sz[S] = __builtin_popcount(ee[S]|S);
f[0] = 1;
FOR(S,0,(1<<n)-1){
if(!f[S]) continue;
if(!vis[S]) continue;
FOR(i,0,n-1){
if((S>>i)&1) continue;
if(!vis[S|(1<<i)]) continue;
add(f[S|(1<<i)],1ll*f[S]*inv[n-sz[S]]%ha);
}
}
int ans = 0;
FOR(S,0,(1<<n)-1) if(vis[S] && __builtin_popcount(S) == mx) add(ans,f[S]);
printf("%d
",ans);
return 0;
}