0%

11801202-AGC008

E

给定长度为 $n$ 的序列 $a$ ,求有多少长度为 $n$ 的排列 $p$ 满足 $\forall i : p_i=a_i 或 p_{p_i}=a_i$

对于排列 $p$ ,将 $i$ 向 $p_i$ 连边,显然每个点入度出度都为 $1$ ,即,形成了若干个环

https://cdn.luogu.com.cn/upload/image_hosting/z22bnrwt.png

对于其中的一个环:删除所有边并将 $i\rightarrow a_i$ ,由题得 $a_i$ 是其前面的点( $p_i=a_i$ )或前面的前面的点( $p_{p_i}=a_i$ )

分成 $3$ ( $4$ )种:

  • 所有 $i$ 的 $a_i$ 都是其前面的点,则环不变
  • 所有 $i$ 的 $a_i$ 都是其前面的前面的点

    • 环是奇环,则环变成同构的另一环
      https://cdn.luogu.com.cn/upload/image_hosting/bw5esa22.png
      -环是偶环,则环被平均拆成两大小相同的环
      https://cdn.luogu.com.cn/upload/image_hosting/fd4lyhjw.png
  • 有的是前面的点,有的是前面的前面,则构成基环内向树
    https://cdn.luogu.com.cn/upload/image_hosting/zjpns0w4.png

现在有 $a$ 构成的图,反向考虑

  • 对于环

    记录每个大小的环的个数,单独考虑每种大小, $\text{dp}$ 决策第 $k$ 个环合并还是单独组成,最后乘法原理

  • 对基环内向树

    考虑两条相邻的链,试图将链塞回环里,该链可以塞到树里的位置就是到下一个链的边,设 $l_2$ 条这样的边, $l_1$ 是该链长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const int N=1e5+100;
const int mod=1e9+7;
int n,ans;
int a[N],du[N],cir[N],vis[N],chain_len[N],sum[N],f[N];

inline void fix(int &x){ x>=mod ? x-=mod : 0;}

void deal_cirtree(int x){
int now=0,fir_ch=0,la_ch=0,fir_len=0;
//从x走环到的点,第一个链,上一个链,链长度
while(cir[x]){
++now,cir[x]=0;
if(chain_len[x]){
if(!fir_ch) la_ch=fir_ch=now,fir_len=chain_len[x];
else{//尝试把链塞回环里
int kl=(chain_len[x]<now-la_ch)+(chain_len[x]<=now-la_ch);
ans=(ll) ans*kl%mod,la_ch=now;
}
}
x=a[x];
}
if(!fir_ch) ++sum[now];//简单环
else{//第一个链
int kl=(fir_len<now-la_ch+fir_ch)+(fir_len<=now-la_ch+fir_ch);
ans=(ll) ans*kl%mod;
}
}

void work(){
for(rei i=1;i<=n;++i){
if(du[i]) continue;
int x=i,len=0; while(!cir[x]) x=a[x],++len;
chain_len[x]=len;
}
ans=1;
for(rei i=1;i<=n;++i) if(cir[i]) deal_cirtree(i);
for(rei i=1;i<=n;++i){//对每一种长度的简单环DP
if(!sum[i]) continue;
f[0]=1;
for(rei j=1;j<=sum[i];++j){
if(i>1 && (i&1)) fix(f[j]=f[j-1]+f[j-1]);//情况1,2
else f[j]=f[j-1];//情况1
if(j>1) fix(f[j]+=(ll) f[j-2]*(j-1)%mod*i%mod);//情况3
}
ans=(ll) ans*f[sum[i]]%mod;
}
}

int main(){
scanf("%d",&n);
for(rei i=1;i<=n;++i) scanf("%d",&a[i]),++du[ a[i] ];
for(rei i=1;i<=n;++i){
if(vis[i]) continue;
rei x=i; while(!vis[x]) vis[x]=i,x=a[x];
if(vis[x]!=i) continue;//说明i在链上
while(!cir[x]) cir[x]=1,x=a[x];
}
for(rei i=1;i<=n;++i) if((cir[i] && du[i]>2) || (!cir[i] && du[i]>1)) return puts("0"),0;
work();
printf("%d\n",ans);
getchar(),getchar();
return 0;
}

F

给定 $n$ 个点的树,每条边长度为 $1$ 给定 $S$ 表示喜欢的顶点集合,起初所有点为白色,可以做一次操作,其中选择顶v点 $\in S$ 和整数 $d$ 将所有满足 $dis(v,u)\leq d$ 的点 $u$ 染成黑色,求最终能得到多少形态不同的树

先考虑 $S$ 为全集,即每个点都能以其自身为中心操作,那么最终黑色的点形成一个连通块

注意到不同操作方案可能得出同一种连通块,即,这并不是一一对应的

考虑到操作有一定距离且连通块是树,考虑取直径中点,即,对于每个连通块,看成以其中心为操作中心的操作,同时得到尽可能小的 $d$

当中心是边时,显然一侧为子树,规定以该边靠近叶子的点作为操作中心

而对于整棵树并不计入,仅在最后将答案 $+1$

对于每个顶点 $v$ ,考虑以其为操作中心的操作个数,对于全集 $S$ ,一定有 $d\in [0,sup]$

而对 $d$ 的约束有:

  • $d<f_v$ ,即, $d$ 小于整棵树的深度,以防整棵树被染黑
  • $d\leq g_v-1$ ,即,$d$ 小于以 $child(v)$ 中点为根的所有子树中第二大深度为 $g_v$ 的 : 即考虑 $v$ 不是中心的情况,若中心在某子树中,朝根方向走,黑色点的最大深度严格递减,即 $\forall c\in child(v)$ 必须有 $\{u\mid dis(c,u)\leq d-1\}\not =\{u\mid (v,u)\leq d\}$ ,即,至少需要两颗子树存在深度 $\geq d-1$ 的顶点

即,$d\leq \min(f_v-1,g_v+1)$ ,对于其余的 $v$ ,换根 $\text{dp}$ 即可

再考虑 $S$ 不是全集,此时 $d$ 要考虑下界

以 $v$ 为操作中心的一些操作可以被 以 $u\in child(v)$ 为中心的操作 $(u,d’)$ 代替,则 $d’\geq d+dis(v,u)>d$

则,子树 $subtree(u)$ 中不能存在深度 $\geq d$ 的顶点,即,操作 $(v,d)$ 至少覆盖了它的一个子节点的子树 $subtree(u)$

这说明 $d$ 至少大于其所有子树中深度最大值的最小值,且保证子树中至少有一个 $S$ 中的点,否则 $v$ 不存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const int N=2e5+100;
int n;
int head[N],ver[N<<1],Next[N<<1],tot;
int fa[N],Size[N],f[N],g[N],inf[N];
char s[N];
ll ans=1;

inline void up(int &x,const int y){ x<y ? x=y : 0;}
inline void down(int &x,const int y){ x>y ? x=y : 0;}
inline void add(int u,int v){ ver[++tot]=v,Next[tot]=head[u],head[u]=tot;}

void dfs1(int x){
Size[x]=s[x]&=1;
inf[x]=(s[x]-1)&INT_MAX;
for(rei i=head[x];i;i=Next[i]){
rei y=ver[i]; if(y==fa[x]) continue;
fa[y]=x; dfs1(y); Size[x]+=Size[y];
f[x]<=f[y] ? (g[x]=f[x],f[x]=f[y]+1) : (up(g[x],f[y]+1),0);
if(Size[y]) down(inf[x],f[y]+1);
}
}

void dfs2(int x,int fy=-1){
rei sup;
f[x]<=fy ? (g[x]=f[x],f[x]=fy+1) : (up(g[x],fy+1),0);
if(Size[x]<Size[1]) down(inf[x],fy+1);
if(inf[x]<=(sup=min(f[x]-1,g[x]+1))) ans+=sup-inf[x]+1;
for(rei i=head[x];i;i=Next[i]){
rei y=ver[i]; if(fa[y]!=x) continue;
dfs2(y,f[y]+1==f[x] ? g[x] : f[x]);
}
}

int main(){
scanf("%d",&n);
for(rei i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),add(u,v),add(v,u);
scanf("%s",s+1);
dfs1(1),dfs2(1);
printf("%lld\n",ans);
getchar(),getchar();
return 0;
}