题解:AT_agc077_d [AGC077D] Range Replace 2
更好的阅读体验
非常具有教育意义的一个数数题!
将区间
那么我们需要解决这样个问题:有一个二元组序列
首先我们先考虑对操作结束之后,不包含
那么我们试图刻画怎样的
- 选择一个区间
[l, r] ,使a_l \sim a_r 的取值均为(l, r) 或(0, 0) 之一,并将a_l \sim a_r 全部置为(0, 0) 。
我们称之为一次消除操作。对应地,我们将题目所给的操作称为一次推平操作。
正确性是显然的。对于一个满足上述条件的序列,我们将进行消除操作倒过来,则可以得到从全
那么假设现在已经得到了一个合法的序列
随后我们注意到了一件事情:当 DAG 的形态不同,最终的序列
形成新的 DAG 后,一定存在至少两个区间,他们之间的先后关系发生了反转。
考虑两个区间
[l_1, r_1] 必须在[l_2, r_2] 前进行,那么就意味着a 序列上[l_1, r_1] 区间上至少存在一个(l_2, r_2) ,且a 序列上[l_2, r_2] 上不存在(l_1, r_1) 。则假设我们将这对先后关系反转,即
[l_1, r_1] 必须在[l_2, r_2] 后进行,那么限制就变成a 序列上[l_1, r_1] 区间上不存在任何(l_2, r_2) ,且a 序列上[l_2, r_2] 上至少有一个(l_1, r_1) 。容易发现这两个限制描绘出的序列
a 是不一样的。
因此我们考虑对合法的 DAG 计数,考虑主旋律,进行 DAG 容斥!
具体是如何操作的呢?我们考虑找到 DAG 上入度为
“恰好”是不好处理的。考虑转化为钦定,假设
我们假设
我们考虑
- 如果最后一次被修改为
(0, 0) 的位置只有i ,则前i-1 个位置对i 不会产生任何影响;在最后一次操作之前,整个序列应该是形如\{(0, 0), (0, 0), \cdots, (0, 0), (l, i)\} 。那么前i-1 个位置有f_{i-1} 种方案,l 有i 中选择。因此在此情况下,g_i \leftarrow f_{i-1} \cdot i 。 - 否则我们枚举上一个,在可能在最后一次操作中被修改成
(0, 0) 的位置j 。那么在这种情况下,由于a_j 在最后一次操作之前都不是(0, 0) ,因此我们将序列分割成了相互独立的两段。- 若
a_j = a_i ,那么[j+1, i-1] 区间内可以任意消除,方案数为f_{j-i-1} ;由于此时j 是一个规模更小的子问题的末尾位置,所以[1, j] 这个区间的方案数是g_j 。\ 因此g_i \leftarrow g_j \cdot f_{i-j-1} 。 - 若
a_j \not = a_i ,那么[j+1, i-1] 区间内可以任意消除,方案数为f_{j-i-1} ;由于此时j 是一个规模更小的子问题的末尾位置,所以[1, j] 这个区间的方案数是g_j 。但是由于a_i 和a_j 不同,因此它们要在两次不同的操作中被依次置为(0, 0) ,所以我们需要给包含j 的操作确定一个右端点,可以取遍[j, i) 中的每一个值,方案数为i-j ;我们还要给包含i 的操作确定一个左端点,可以取遍(j, i] 中的每一个值,方案数为i-j ;由于我们多了一次操作,所以给T 的大小增加了1 ,因此还要乘上-1 的系数。\ 因此g_i \leftarrow -g_j \cdot f_{i-j-1} \cdot (i-j)^2 。
- 若
最后考虑
那么这道题就做完了,复杂度
#include<bits/stdc++.h>
#define endl '\n'
#define N 5006
using namespace std;
int n,MOD,f[N],g[N],h[N];
inline void add(int &x,int y) {x+=y,x-=x>=MOD?MOD:0;}
inline void dec(int &x,int y) {x+=MOD-y,x-=x>=MOD?MOD:0;}
main()
{
scanf("%d%d",&n,&MOD),f[0]=h[0]=1;
for(int i=1;i<=n;i++)
{
g[i]=1ll*i*f[i-1]%MOD;
for(int j=0;j<i;j++)
{
add(g[i],1ll*g[j]*f[i-j-1]%MOD);
dec(g[i],1ll*g[j]*f[i-j-1]%MOD*(i-j)%MOD*(i-j)%MOD);
}
for(int j=1;j<=i;j++)
add(f[i],1ll*f[i-j]*(i-j+1)%MOD*g[j]%MOD);
}
for(int i=1;i<=n;i++)
{
add(h[i],f[i]),add(h[i],h[i-1]);
for(int j=1;j<i;j++)add(h[i],1ll*h[i-j-1]*f[j]%MOD);
}
printf("%d\n",h[n]);
return 0;
}