Public NOIP Round #1 Div.1 C&D
Public NOIP Round #1 Div.1
Problem C. 波特分组
记 \(S=\sum k_i\)。
\(O(n\sqrt{S}+S)\) Solution
将硬币正面记为 \(1\),反面记为 \(0\)。
首先概率转计数:求有多少个 01 序列,能够使得这 \(k\) 个波特分到一个组里。
为了方便,我们可以钦定这些波特都要分到 A 组中,最终答案再乘以 \(2\)。
我们设最后一个被分到 A 组中的波特为 \(pa\),最后一个被分到 B 组中的波特为 \(pb\)。设 \(p=\min(pa,pb)\),那么 \(p\) 之后的位置,无论是 \(0\) 还是 \(1\),都会被分到另一个组里面。
那么我们对 \(p\) 进行分类讨论:
-
\(p=pa\),\([p+1,n]\) 中的波特都会被分到 B 组。显然需要满足 \(p\geq b_k\)。
进一步分类:
- \(p=b_k\)。由于 \(k\) 个指定的波特必须分到 A 组,那么还需要将剩下的 \(n-k\) 个 \(1\) 填入 \(b_k-k\) 个位置里面。方案数为 \(\binom{b_k-k}{n-k}\times 2^{2n-p}\)。
- \(p>b_k\)。\(p\) 这个位置必须填 \(1\),那么还要将剩下的 \(n-k-1\) 个 \(1\) 填入 \(p-k-1\) 个位置里面。方案数为 \(\binom{p-k-1}{n-k-1}\times 2^{2n-p}\)。
-
\(p=pb\),\([p+1,n]\) 中的波特都会被分到 A 组。需要满足 \(p\ne b_i\)。
我们可以增加两个点 \(b_0=0,b_{k+1}=2n\)。
如果一个 \(p\) 满足 \(b_i<p<b_{i+1}\),那么要将剩下的 \(n-1\) 个 \(0\) 填入 \(p-i-1\) 个位置里面,方案数为 \(\binom{p-i-1}{n-1}\times 2^{2n-p}\)。
注意 \(p\ne 2n\),因为 \(\min(pa,pb)\) 一定小于 \(2n\)。
对于情况 2,我们可以将式子化为 \(\binom{p-i-1}{n-1}\times 2^{2n-(p-i)}\times 2^{-i}\),处理出 \(\binom{x-i}{n-1}\times 2^{2n-x}\) 的前缀和即可。
对于情况 1,不同的 \(k\) 不会超过 \(O(\sqrt{S})\) 量级,所以我们对每个 \(k\) 分别处理前缀和即可。
int n,Q;
const ll mod=998244353,inv2=(mod+1)>>1;
inline ll Mod(ll x){return (x>=mod)?(x-mod):(x);}
inline void Add(ll &x,ll y){x=Mod(x+y);}
ll fac[N],caf[N],pw[N],wp[N];
ll QuickPow(ll x,ll y){
ll res=1;
while(y){
if(y&1) res=res*x%mod;
x=x*x%mod; y>>=1;
}
return res;
}
void Init(){
fac[0]=caf[0]=1;
for(int i=1;i<=(n<<1);i++) fac[i]=fac[i-1]*i%mod;
caf[n<<1]=QuickPow(fac[n<<1],mod-2);
for(int i=(n<<1)-1;i;i--) caf[i]=caf[i+1]*(i+1)%mod;
pw[0]=wp[0]=1;
for(int i=1;i<=(n<<1);i++) pw[i]=Mod(pw[i-1]<<1),wp[i]=wp[i-1]*inv2%mod;
}
inline ll C(int x,int y){
if(x<0||y<0||x<y) return 0;
return fac[x]*caf[y]%mod*caf[x-y]%mod;
}
namespace Subtask1{
int k[N],c[N],m;
vector<int> q[N];
ll f[850][N],g[N];
void Solve(){
Init();
for(int i=1;i<=Q;i++){
read(k[i]); c[i]=k[i];
q[i].push_back(0);
for(int j=1;j<=k[i];j++){
int x; read(x);
q[i].push_back(x);
}
q[i].push_back(n<<1);
}
sort(c+1,c+Q+1);
m=unique(c+1,c+Q+1)-c-1;
for(int i=1;i<=m;i++){
for(int j=n;j<(n<<1);j++)
f[i][j]=Mod(f[i][j-1]+C(j-c[i]-1,n-c[i]-1)*pw[2*n-j]%mod);
}
for(int i=n;i<(n<<1);i++)
g[i]=Mod(g[i-1]+C(i-1,n-1)*pw[2*n-i]%mod);
for(int i=1;i<=Q;i++){
int tk=lower_bound(c+1,c+m+1,k[i])-c;
ll ans=0;
for(int j=0;j<(signed)q[i].size()-1;j++){
if(q[i][j]==2*n) continue;
int L=q[i][j]+1-j,R=q[i][j+1]-1-j;
ll res=Mod(g[R]-g[L-1]+mod)*wp[j]%mod;
Add(ans,res);
}
if(q[i][k[i]]!=n*2){
Add(ans,C(q[i][k[i]]-k[i],n-k[i])*pw[2*n-q[i][k[i]]]%mod);
Add(ans,Mod(f[tk][(n<<1)-1]-f[tk][q[i][k[i]]]+mod));
}
ans=Mod(ans<<1)*QuickPow(pw[n<<1],mod-2)%mod;
printf("%lld\n",ans);
}
}
}
signed main(){
read(n),read(Q); Init();
Subtask1::Solve();
return 0;
}
Problem D. 别急
首先发现两个图都是内向基环树森林。
令 \(bl_x^F,bl_x^G\) 表示 \(x\) 这个点在 \(F,G\) 中的连通块编号,\(id^F_x,id^G_x\) 表示 \(x\) 这个点在 \(F,G\) 环上的次序,\(len^F_x,len^G_x\) 表示 \(x\) 这个点所在环的长度,\(dep_x^F,dep_x^G\) 表示 \(x\) 这个点到环上的距离。
两点相遇时,分为三种情况讨论:
\(X,Y\) 都是树点
\(x^F\),\(y^G\) 两个点能碰面的条件:
- 存在一个点 \(u\),满足:
- \(dep_x^F-dep_u^F=dep_y^G-dep_u^G\),也就是 \(dep_u^F-dep_u^G=dep_x^F-dep_y^G\);
- \(x^F\in Son(u^F)\);
- \(x^G\in Son(u^G)\)。
我们把询问 \((X_i,Y_i)\) 挂在 \(Y_i^G\) 上,离线处理。
我们对于每一个 \(dep_u^F-dep_u^G\) 可能的取值 \(i\) 都建立一个区间的集合 \(S_i\)。
遍历 \(G\) 的树部分,每遍历到一个点 \(u\),就在 \(S_{dep_u^F-dep_u^G}\) 中加入区间 \([L_u^F,R_u^F]\),其中 \(L_u^F,R_u^F\) 表示 \(u^F\) 子树内 dfn 序的最小值、最大值。退出节点时撤销。
对于一个询问 \((X_i,Y_i)\),查询 \(S_{dep_{X_i}^F-dep_{Y_i}^G}\) 中 \(dfn_{X_i}^F\) 有没有被区间覆盖即可。
对于每一个 \(S_i\),分别用一颗动态开点线段树维护即可。
一种实现方式是维护差分,需要支持单点修改、区间查询。
\(X\) 是树点,\(Y\) 是环点
\(x^F\),\(y^G\) 两个点能碰面的条件:
- 存在一个点 \(u\),满足:
- \(dep_x^F-dep_u^F \equiv id_u^G-id_y^G \pmod{len_u^G}\),也就是 \(dep_u^F+id_u^G \equiv dep_x^F+id_y^G \pmod{len_u^G}\);
- \(x^F\in Son(u_F)\);
- \(bl_u^G=bl_y^G\)。
仍然把询问 \((X_i,Y_i)\) 挂在 \(X_i^F\) 上,离线处理。
对于 \(G\) 的每一个连通块,都建立一个数集 \(T_i\)。
遍历 \(F\) 的树部分,每遍历到一个点 \(u\),如果 \(u^G\) 在环内,就在 \(S_{bl_u^G}\) 中加入 \(dep_u^F+id_u^G\),退出节点时撤销。询问时在相应集合内查询即可。
\(X\) 是环点,\(Y\) 是树点
与上面的做法是对称的,在 \(F\) 上遍历一遍即可。
$X,Y $ 都是环点
\(x^F\),\(y^G\) 两个点能碰面的条件:
- 存在一个点 \(u\) ,满足:
- 存在整数 \(k_x,k_y\),使得 \(id^F_u-id^F_x+k_x\times len^F_u=id_u^G-id_y^G+k_y \times len^G_u\)。
- \(bl_u^F=bl_x^F\);
- \(bl_u^G=bl_y^G\)。
化简一下得到 \(k_x\times len^F_u-k_y \times len^G_u=(id_u^G-id_y^G)-(id^F_u-id^F_x)\)。
方程有解的充要条件是 \(id_u^G-id_y^G\equiv id^F_u-id^F_x \pmod{\gcd(len_u^F,len_u^G)}\),也就是 \(id_u^F-id^G_u\equiv id_x^F-id^G_y \pmod{\gcd(len_u^F,len_u^G)}\)
我们对于每一个既在 \(F\) 中的环也在 \(G\) 中的环的点 \(u\),将三元组 \((bl_u^F,bl_u^G,id_u^F-id^G_u)\) 加入一个 set 中。对于每个询问,在 set 中查询即可。
综合以上三个算法分别处理即可。代码死难写,细节死多。
int n,Q;
int lg[N];
struct Graph{
int P[N],head[N],tot=1,dep[N],bl[N],rt[N];
int dfn[N],num,tim,L[N],R[N],id[N],len[N];
int f[N][22];
int path[N],top;
vector<int> ring[N];
bool inr[N];int vis[N];
struct Edge{
int to,nxt;
}edge[N<<1];
void Add(int u,int v){
edge[++tot]={v,head[u]};
head[u]=tot;
}
void dfs1(int x,int c,int in){
bl[x]=c;
if(vis[x]){
if(inr[x]) return;
if(P[path[top]]==x){
for(int i=vis[x];i<=top;i++){
ring[c].push_back(path[i]);
id[path[i]]=ring[c].size()-1;
inr[path[i]]=1;
}
}
else{
for(int i=top;i>=vis[x];i--){
ring[c].push_back(path[i]);
id[path[i]]=ring[c].size()-1;
inr[path[i]]=1;
}
}
len[c]=ring[c].size();
return;
}
path[++top]=x; vis[x]=top;
for(int i=head[x];i;i=edge[i].nxt){
if(i==(in^1)) continue;
int t=edge[i].to;
dfs1(t,c,i);
}
top--;
}
void dfs2(int x,int s,int pr){
rt[x]=s; dep[x]=dep[pr]+1;
f[x][0]=pr;
for(int i=1;i<=lg[dep[x]];i++)
f[x][i]=f[f[x][i-1]][i-1];
dfn[x]=L[x]=++tim;
for(int i=head[x];i;i=edge[i].nxt){
int t=edge[i].to;
if(t==pr||inr[t]) continue;
dfs2(t,s,x);
}
R[x]=tim;
}
int Jump(int x,int d){
for(int i=lg[d];i>=0;i--)
if(d>>i&1) x=f[x][i];
return x;
}
void Init(){
for(int i=1;i<=n;i++){
if(bl[i]) continue;
dfs1(i,++num,0);
for(int j:ring[num])
dfs2(j,j,0);
}
}
}F,G;
int X[N],Y[N];
bool ans[N];
struct Query{
int x,id;
};
vector<Query> q[N];
namespace TreeAndTree{
int root[N<<1],tot;
struct SegTree{
struct SegNode{
int lc,rc;
int val;
}tr[N*35];
void Pushup(int p){
tr[p].val=tr[tr[p].lc].val+tr[tr[p].rc].val;
}
void Update(int &p,int l,int r,int x,int v){
if(x>n||x<0) return;
if(!p) p=++tot;
if(l==r) return tr[p].val+=v,void();
int mid=(l+r)>>1;
if(x<=mid) Update(tr[p].lc,l,mid,x,v);
else Update(tr[p].rc,mid+1,r,x,v);
Pushup(p);
}
int Ask(int p,int l,int r,int L,int R){
if(!p) return 0;
if(L<=l&&r<=R) return tr[p].val;
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=Ask(tr[p].lc,l,mid,L,R);
if(R>mid) res+=Ask(tr[p].rc,mid+1,r,L,R);
return res;
}
}Seg;
void dfs(int x,int pr){
int dx=F.dep[x]-G.dep[x]+n;
Seg.Update(root[dx],1,n,F.L[x],1);
Seg.Update(root[dx],1,n,F.R[x]+1,-1);
for(Query i:q[x]){
int y=i.x,id=i.id;
int dy=F.dep[y]-G.dep[x]+n;
ans[id]|=Seg.Ask(root[dy],1,n,1,F.dfn[y]);
}
for(int i=G.head[x];i;i=G.edge[i].nxt){
int t=G.edge[i].to;
if(G.inr[t]||t==pr) continue;
dfs(t,x);
}
Seg.Update(root[dx],1,n,F.L[x],-1);
Seg.Update(root[dx],1,n,F.R[x]+1,1);
}
void Solve(){
for(int i=1;i<=Q;i++)
q[Y[i]].push_back(Query{X[i],i});
for(int i=1;i<=n;i++){
if(G.rt[i]==i)
dfs(i,0);
}
}
}
namespace TreeAndRing{
multiset<int> s[N];
void dfs1(int x,int pr){
int lx=F.len[F.bl[x]],v=(F.id[x]+G.dep[x])%lx;
if(F.inr[x]) s[F.bl[x]].insert(v);
for(Query i:q[x]){
int y=i.x,id=i.id,ly=F.len[F.bl[y]];
int val=(F.id[y]+G.dep[x])%ly;
if(s[F.bl[y]].count(val)) ans[id]=1;
}
for(int i=G.head[x];i;i=G.edge[i].nxt){
int t=G.edge[i].to;
if(G.inr[t]||t==pr) continue;
dfs1(t,x);
}
if(F.inr[x]) s[F.bl[x]].erase(s[F.bl[x]].find(v));
}
void dfs2(int x,int pr){
int lx=G.len[G.bl[x]],v=(G.id[x]+F.dep[x])%lx;
if(G.inr[x]) s[G.bl[x]].insert(v);
for(Query i:q[x]){
int y=i.x,id=i.id,ly=G.len[G.bl[y]];
int val=(G.id[y]+F.dep[x])%ly;
if(s[G.bl[y]].count(val)) ans[id]=1;
}
for(int i=F.head[x];i;i=F.edge[i].nxt){
int t=F.edge[i].to;
if(F.inr[t]||t==pr) continue;
dfs2(t,x);
}
if(G.inr[x]) s[G.bl[x]].erase(s[G.bl[x]].find(v));
}
void Solve(){
for(int i=1;i<=n;i++) q[i].clear();
for(int i=1;i<=Q;i++){
int x=X[i],y=Y[i];
if(F.dep[x]<=G.dep[y]){
x=F.rt[x],y=G.Jump(y,F.dep[X[i]]-1);
q[y].push_back(Query{x,i});
}
}
for(int i=1;i<=n;i++)
if(G.rt[i]==i) dfs1(i,0);
for(int i=1;i<=n;i++) q[i].clear();
for(int i=1;i<=Q;i++){
int x=X[i],y=Y[i];
if(F.dep[x]>G.dep[y]){
y=G.rt[y],x=F.Jump(x,G.dep[Y[i]]-1);
q[x].push_back(Query{y,i});
}
}
for(int i=1;i<=n;i++)
if(F.rt[i]==i) dfs2(i,0);
}
}
namespace RingAndRing{
struct Tuple{
int x,y,z;
bool operator<(const Tuple& tmp)const{
if(x!=tmp.x) return x<tmp.x;
else if(y!=tmp.y) return y<tmp.y;
else return z<tmp.z;
}
};
multiset<Tuple> S;
void Solve(){
for(int i=1;i<=n;i++){
if(F.inr[i]&&G.inr[i]){
int g=__gcd(F.len[F.bl[i]],G.len[G.bl[i]]);
int v=((F.id[i]-G.id[i])%g+g)%g;
S.insert(Tuple{F.bl[i],G.bl[i],v});
}
}
for(int i=1;i<=Q;i++){
int x=X[i],y=Y[i];
int ly=G.len[G.bl[y]],lx=F.len[F.bl[x]];
if(F.dep[x]>=G.dep[y]){
int id=((G.id[G.rt[y]]+F.dep[x]-G.dep[y])%ly+ly)%ly;
y=G.ring[G.bl[y]][id],x=F.rt[x];
int g=__gcd(lx,ly);
int v=((F.id[x]-G.id[y])%g+g)%g;
if(S.count({F.bl[x],G.bl[y],v})) ans[i]=1;
}
else{
int id=((F.id[F.rt[x]]+G.dep[y]-F.dep[x])%lx+lx)%lx;
x=F.ring[F.bl[x]][id],y=G.rt[y];
int g=__gcd(lx,ly);
int v=((F.id[x]-G.id[y])%g+g)%g;
if(S.count({F.bl[x],G.bl[y],v})) ans[i]=1;
}
}
}
}
signed main(){
read(n),read(Q);
for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;i++) read(F.P[i]),F.Add(i,F.P[i]),F.Add(F.P[i],i);
for(int i=1;i<=n;i++) read(G.P[i]),G.Add(i,G.P[i]),G.Add(G.P[i],i);
F.Init(); G.Init();
for(int i=1;i<=Q;i++) read(X[i]),read(Y[i]);
TreeAndTree::Solve();
TreeAndRing::Solve();
RingAndRing::Solve();
for(int i=1;i<=Q;i++){
if(ans[i]) puts("YES");
else puts("NO");
}
return 0;
}

浙公网安备 33010602011771号