又是被 djq 带飞的一场 orz,终场 rk2,又是罚时被锤了
1001,1002 俩 hard 先略过,不会做 /ll
这种数数神题被 djq 一眼秒了,我还能说什么……
首先我们考虑合法的 A A A 满足什么性质,如果 A A A 中的第 i i i 个数 A i A_i Ai 满足它是前缀非严格最大值(前面可以和它相等但不能比他大),那么就在 i − 1 i-1 i−1 和 i i i 之间切一刀,这样会把 A A A 划分成若干个小段。
我们考虑合并一些小段,让合并之后的一个段中,每个数字都出现恰好两次,并且让这样的段尽量多。记合并之后段的集合为 S S S。
考虑一个会算重的做法,即区分 A A A 中的每个数字来自于哪个排列,那么显然从 n n n 到 1 1 1 倒着插一遍即可。具体的,假设当前插入两个排列中的 i i i,首先它们可以作为开头或者分别插入和它来自于同一个排列的某个数后面。并且,如果两个都想作为开头,那么就有两种顺序。因此方案数为 ( n − i + 1 ) 2 + 1 (n-i+1)^2+1 (n−i+1)2+1。把所有数字乘起来即可,即 ∏ ( i 2 + 1 ) \prod(i^2+1) ∏(i2+1)。
考虑这个东西会算重多少次, S S S 中的每个段,都可以交换每个数字来自的排列而对答案没有影响。因此这样会算重 2 ∣ S ∣ 2^{|S|} 2∣S∣ 次。
我们再定义答案的 egf 为 G G G,会发现一个合法的 A A A 在 G 2 G^2 G2 中也会被算重 2 ∣ S ∣ 2^{|S|} 2∣S∣ 次!因为 G 2 G^2 G2 相当于枚举了 S S S 的一个子集划分,即划分成两个没有交的子集 P , Q P,Q P,Q,一个 G G G 贡献 P P P,另一个 G G G 贡献 Q Q Q。
这真是太高妙了,不知道 djq 如何一眼看出来的 orz。然后的话,我们就有:
G 2 = ∑ i = 0 ∞ x i ∏ j = 0 i ( j 2 + 1 ) G^2=\sum_{i=0}^\infty x^i\prod_{j=0}^i(j^2+1) G2=i=0∑∞xij=0∏i(j2+1)
直接多项式开根即可。复杂度 O ( n log n ) O(n \log n) O(nlogn)。
代码就不放了,多项式板子有点长。但是跟我一起说:djq 牛逼!
这个题就毫无技巧性可言了,就是细节、细节、细节。一发 AC 我还是很高兴的。
考虑每个数 i i i 向 ( a i + c ) m o d m (ai+c) \bmod m (ai+c)modm 连一条边,那么就会形成一个基环树森林。对于每个点 i i i,暴力的话就是枚举所有的 v 1 + v 2 = i v_1+v_2=i v1+v2=i,然后计算从 i i i 出发走 ∣ v 1 − v 2 ∣ |v_1-v_2| ∣v1−v2∣ 步到的是奇数还是偶数。
但是这显然可以简单的优化,分 v 1 ≥ v 2 v_1 \ge v_2 v1≥v2 和 v 1 < v 2 v_1
这玩意儿可以拆成在树上的部分和在环上的部分,树上的部分直接前缀和解决,环上的部分又要分类讨论了。设环长为 k k k,如果环是个奇环,那么 2 k 2k 2k 步会经过环上所有点并走回起点,把 l l l 分成若干个整环和剩下的零头处理即可。如果环是个偶环,那么就只能经过和当前奇偶性相同的点,也把 l l l 分成若干个“半环”和零头处理即可。
这样每个基环树的处理都是 O ( 点 数 + 环 长 ) O(点数+环长) O(点数+环长) 的,总复杂度就是线性的了。
#include
typedef long long LL;
using namespace std;
template<typename T> inline void chkmin(T &a, const T &b) { a = a < b ? a : b; }
template<typename T> inline void chkmax(T &a, const T &b) { a = a > b ? a : b; }
const int MAXN = 1000005;
struct Edge { int to, next; } edge[MAXN];
int head[MAXN], nxt[MAXN], vis[MAXN], T, n, a1, a2, m, tot;
int id[MAXN << 2], sum[MAXN], pre[MAXN << 2], od0, od1, c;
bool isc[MAXN];
LL ans;
void add_edge(int u, int v) {
edge[++tot] = Edge { v, head[u] };
head[u] = tot;
}
int calc(int s, int p) {
int r = 0;
if (c & 1) {
int t = p / (2 * c);
r += t * (od0 + od1);
p -= t * 2 * c;
r += pre[s + p] - (s > 1 ? pre[s - 2] : 0);
} else {
int t = p / c;
r += t * (s & 1 ? od1 : od0);
p -= t * c;
r += pre[s + p] - (s > 1 ? pre[s - 2] : 0);
}
return r;
}
void dfs(int u, int d, int rt) {
if (d > 0) sum[d] = (d > 1 ? sum[d - 2] : 0) + (u & 1);
int l = max((u + 1) >> 1, u - n), r = min(n, u), s = 0;
auto upd = [&](int p, int q) {
if (p < d) {
s += sum[d - p];
if (q + 2 < d) s -= sum[d - q - 2];
else p += (d - p + 1) / 2 * 2;
}
if (p >= d) {
p -= d, q -= d;
int t = p & 1 ? (rt + 1) % c : rt;
if (p & 1) --p, --q;
if (p > 1) s -= calc(t, p - 2);
s += calc(t, q);
}
};
if (l <= r) upd(l * 2 - u, r * 2 - u);
l = max(u - n, 0), r = (u - 1) >> 1;
if (l <= r) upd(u - r * 2, u - l * 2);
ans += s;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (!vis[v]) dfs(v, d + 1, rt);
}
}
int main() {
for (scanf("%d", &T); T--;) {
scanf("%d%d%d%d", &n, &a1, &a2, &m);
for (int i = 0; i < m; i++) {
nxt[i] = ((LL)a1 * i + a2) % m;
add_edge(nxt[i], i);
}
for (int i = 0; i < m; i++) if (!vis[i]) {
int j = i;
for (; !vis[j]; j = nxt[j]) vis[j] = i + 1;
if (vis[j] == i + 1) {
isc[j] = 1;
for (int k = nxt[j]; k != j; k = nxt[k]) isc[k] = 1;
}
}
fill(vis, vis + m, 0);
for (int i = 0; i < m; i++) if (isc[i] && !vis[i]) {
c = 0; id[c++] = i;
for (int j = nxt[i]; j != i; j = nxt[j]) id[c++] = j;
od0 = od1 = 0;
for (int j = 0; j < c; j++) {
vis[id[j]] = 1;
id[c + c + c + j] = id[c + c + j] = id[c + j] = id[j];
(j & 1 ? od1 : od0) += id[j] & 1;
}
for (int j = 0; j < c * 4; j++)
pre[j] = (j > 1 ? pre[j - 2] : 0) + (id[j] & 1);
for (int j = 0; j < c; j++) {
dfs(id[j], 0, j);
}
// printf("%lld\n", ans);
}
LL fm = (LL)(n + 1) * (n + 1), g = __gcd(fm - ans, fm);
printf("%lld/%lld\n", (fm - ans) / g, fm / g);
ans = tot = 0;
for (int i = 0; i < m; i++)
head[i] = vis[i] = isc[i] = 0;
}
return 0;
}
其实是个水题。考虑“球进洞”的操作,相当于给你一个 2 n + 1 2n+1 2n+1 的序列,每次可以删除某相邻两个元素,到只剩一个元素时视为操作结束(最后这个元素也必然恰好是洞)。
考虑每个点的 x x x 坐标对答案的贡献,即它被它右边的点删除时贡献为 − x -x −x,被左边删除时贡献为 x x x,留下来的话贡献就是 0 0 0。
于是 n 2 n^2 n2 dp,即 f ( i , j ) f(i,j) f(i,j) 表示当前点左边有 i i i 个点,右边有 j j j 个点时,被右边的点删掉的概率。这个预处理即可,于是复杂度 O ( n 2 + T n ) O(n^2+Tn) O(n2+Tn)。 x x x 坐标居然有负数挂了一发,给队友贡献罚时了 /ll
#include
typedef long long LL;
using namespace std;
template<typename T> inline void chkmin(T &a, const T &b) { a = a < b ? a : b; }
template<typename T> inline void chkmax(T &a, const T &b) { a = a > b ? a : b; }
const int MAXN = 6005, MAXM = 100005, MOD = 998244353;
int f[MAXN][MAXN], xx[MAXN], inv[MAXM], T, n;
void init() {
int n = 100000;
inv[1] = 1;
for (int i = 2; i <= n; i++)
inv[i] = MOD - (LL)(MOD / i) * inv[MOD % i] % MOD;
n = 6000;
for (int i = 0; i < n; i++)
for (int j = 0; j + i < n; j++) if (!((i + j) & 1)) {
LL c = 0;
if (j > 0) c += 1;
if (i > 1) c += (LL)(i - 1) * f[i - 2][j];
if (j > 1) c += (LL)(j - 1) * f[i][j - 2];
f[i][j] = c % MOD * inv[i + j] % MOD;
}
}
int main() {
init();
for (scanf("%d", &T); T--;) {
scanf("%d", &n);
for (int i = 1; i <= n * 2 + 1; i++)
scanf("%d", xx + i);
LL ans = 0;
for (int i = 1; i <= n * 2 + 1; i++) {
LL p = f[i - 1][n * 2 + 1 - i], q = f[n * 2 + 1 - i][i - 1];
ans = (ans + (q - p + MOD) * xx[i]) % MOD;
}
printf("%lld\n", (ans + MOD) % MOD);
}
return 0;
}
其实这个题也不是很难,就是不能想歪了。
考虑对于每个 rose,我们把它挂在它包含的深度最小的那个点上进行计算(如果在它的中心点上计算反而还不太好做)。
f v f_v fv 表示 v v v 的子树中能挂上多少个 rose,那么枚举这个点上挂的 rose,设这个 rose 的中心点为 m m m,半径为 r r r,那么贡献就是所有到 m m m 距离为 r + 1 r+1 r+1 的点的 d p dp dp 值加起来。这个可以直接在点分树上查询,复杂度 O ( n log n ) O(n \log n) O(nlogn)。
code by djq
#include
#define rep(i, n) for(int i = 0; i < (int)(n); i ++)
#define rep1(i, n) for(int i = 1; i <= (int)(n); i ++)
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MOD = 998244353;
int n, m, x[100005], r[100005], val[100005];
vector<PII> G[100005];
int pre[100005][20];
vector<int> hv[100005];
LL dp[100005];
vector<pair<PII, int> > pat[100005];
vector<LL> vsum[100005];
vector<LL> esum[100005];
void dfs0(int v, int par)
{
pre[v][0] = par;
rep1(i, 19) pre[v][i] = pre[pre[v][i - 1]][i - 1];
rep(i, G[v].size()) {
int u = G[v][i].first;
if(u == par) continue;
dfs0(u, v);
}
}
int kpar(int v, int k)
{
rep(i, 20) if(k >> i & 1) v = pre[v][i];
return v == 0 ? 1 : v;
}
bool del[100005];
int siz[100005];
void dfs1(int v, int par)
{
siz[v] = 1;
rep(i, G[v].size()) {
int u = G[v][i].first;
if(del[u] || u == par) continue;
dfs1(u, v);
siz[v] += siz[u];
}
}
int cent(int v, int par, int tot)
{
rep(i, G[v].size()) {
int u = G[v][i].first;
if(del[u] || u == par) continue;
if(siz[u] > tot / 2) return cent(u, v, tot);
}
return v;
}
int dfs2(int v, int par, const pair<PII, int>& cur)
{
int ret = cur.second;
pat[v].push_back(cur);
rep(i, G[v].size()) {
int u = G[v][i].first;
if(del[u] || u == par) continue;
ret = max(ret, dfs2(u, v, MP(cur.first, cur.second + 1)));
}
return ret;
}
void gen_tre(int v)
{
dfs1(v, 0);
v = cent(v, 0, siz[v]);
del[v] = true;
int ml = 0;
pat[v].push_back(MP(MP(v, -1), 0));
rep(i, G[v].size()) {
int u = G[v][i].first, e = G[v][i].second;
if(del[u]) continue;
int cl = dfs2(u, 0, MP(MP(v, e), 1));
ml = max(ml, cl);
esum[e].resize(cl + 1);
}
vsum[v].resize(ml + 1);
rep(i, G[v].size()) {
int u = G[v][i].first;
if(del[u]) continue;
gen_tre(u);
}
}
void add(int v, LL dat)
{
rep(i, pat[v].size()) {
int cv = pat[v][i].first.first, ce = pat[v][i].first.second, cd = pat[v][i].second;
vsum[cv][cd] += dat;
if(ce != -1) esum[ce][cd] += dat;
}
}
LL query(int v, int r)
{
LL ret = 0;
rep(i, pat[v].size()) {
int cv = pat[v][i].first.first, ce = pat[v][i].first.second, cd = pat[v][i].second;
if(cd <= r && cd + vsum[cv].size() > r) ret += vsum[cv][r - cd];
if(ce != -1 && cd <= r && cd + esum[ce].size() > r) ret -= esum[ce][r - cd];
}
return ret;
}
void gen_dp(int v, int par)
{
dp[v] = 0;
rep(i, G[v].size()) {
int u = G[v][i].first;
if(u == par) continue;
gen_dp(u, v);
dp[v] += dp[u];
}
rep(i, hv[v].size()) {
int cur = hv[v][i];
dp[v] = max(dp[v], query(x[cur], r[cur] + 1) + val[cur]);
}
add(v, dp[v]);
}
void solve()
{
scanf("%d%d", &n, &m);
rep1(i, n) G[i].clear();
rep(i, n - 1) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(MP(v, i));
G[v].push_back(MP(u, i));
}
rep(i, m) scanf("%d%d%d", &x[i], &r[i], &val[i]);
dfs0(1, 0);
rep1(i, n) hv[i].clear();
rep(i, m) hv[kpar(x[i], r[i])].push_back(i);
rep1(i, n) pat[i].clear();
rep1(i, n) vsum[i].clear();
rep(i, n - 1) esum[i].clear();
rep1(i, n) del[i] = false;
gen_tre(1);
gen_dp(1, 0);
printf("%lld\n", dp[1]);
}
int main()
{
int T;
scanf("%d", &T);
while(T --) solve();
return 0;
}
这题是强大的 lqs 做的。
考虑图中的最远点对,走到它们中任意一个点都是必胜态。删掉它们(如果有多个最远点对则全删掉),对于剩下的点重复该操作。这样的原因是,如果当前 A A A 走到了次远点对上,那么 B B B 可以走到该点对的另一个点, A A A 要么无路可走,要么只能走到最远点对上,无论哪种都是必输的。
因此不断进行这种操作之后,如果集合中的点只剩下一个,且为 1 1 1 号点,那么先手必败(因为 1 1 1 号点必然只能走到某个点对的一个点上),否则先手必胜。
感觉还是挺妙的,换我估计想不到。复杂度的话大概是 O ( n 2 log n ) O(n^2 \log n) O(n2logn),需要每对点之间的距离做一个排序。
code by lqs
#include
using namespace std;
int test,n,x[2222],y[2222],lst,cnt,pre,pos,num;
bool used[2222],vis[2222],flg;
struct edge
{
long long d;
int x,y;
bool operator < (const edge &u) const
{
if (d!=u.d) return d>u.d;
if (x!=u.x) return x>u.x;
return y>u.y;
}
}arr[2222222];
int main()
{
scanf("%d",&test);
while(test--)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d%d",&x[i],&y[i]);
}
cnt=0;
for (int i=1;i<=n;i++)
{
for (int j=i+1;j<=n;j++)
{
arr[++cnt]=(edge){1ll*(x[i]-x[j])*(x[i]-x[j])+1ll*(y[i]-y[j])*(y[i]-y[j]),i,j};
}
}
sort(arr+1,arr+cnt+1);
memset(vis,0,sizeof(vis));
memset(used,0,sizeof(used));
pos=1;
while(pos<=cnt)
{
pre=pos;
while(pos<=cnt && arr[pos].d==arr[pre].d) pos++;
for (int i=pre;i<pos;i++)
if (!vis[arr[i].x] && !vis[arr[i].y])
used[arr[i].x]=used[arr[i].y]=1;
for (int i=pre;i<pos;i++)
if (used[arr[i].x] && used[arr[i].y])
vis[arr[i].x]=vis[arr[i].y]=1;
}
if (vis[1]) printf("YES\n");
else printf("NO\n");
}
return 0;
}
子集卷积裸题。先考虑所有不为 0 0 0 的 b i b_i bi,这些 b i b_i bi 直接做子集卷积即可。也就是 fwt 之后记个子集大小卷 n n n 次。
如果 b i b_i bi 为 0 0 0,那么显然答案贡献是 ∏ ( p i + 1 ) \prod (p_i+1) ∏(pi+1)。
lqs 写的好像是 fwt 之后每一位做个 exp,反正效果都一样。复杂度 O ( 2 n n 2 ) O(2^n n^2) O(2nn2)。
code by lqs
#include
using namespace std;
const int mod=998244353;
int n,a[22][2222222],p,b,cnt[2222222],res,inv[55],ans[2222222],q,msk,f[55],g[55];
int binpow(int a,int t)
{
int res=1,p=a;
for (int i=t;i;i>>=1)
{
if (i&1) res=1ll*res*p%mod;
p=1ll*p*p%mod;
}
return res;
}
void add(int &x,int y)
{
x+=y;
if (x>=mod) x-=mod;
}
void FWT_or(int a[],int flg)
{
for (int i=2;i<=(1<<21);i<<=1)
{
for (int p=i>>1,j=0;j<(1<<21);j+=i)
{
for (int k=j;k<j+p;k++)
{
if (flg==1)
{
add(a[k+p],a[k]);
}
else
{
add(a[k+p],mod-a[k]);
}
}
}
}
}
void calcexp()
{
g[0]=1;
for (int i=1;i<=21;i++)
{
g[i]=0;
for (int j=0;j<i;j++)
{
g[i]=(1ll*f[j+1]*g[i-1-j]%mod*(j+1)+g[i])%mod;
}
g[i]=1ll*g[i]*inv[i]%mod;
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=50;i++) inv[i]=binpow(i,mod-2);
for (int i=0;i<(1<<21);i++)
{
for (int j=0;j<21;j++)
{
if (i&(1<<j)) cnt[i]++;
}
}
res=1;
for (int i=1;i<=n;i++)
{
scanf("%d%d",&p,&b);
if (!b) res=1ll*res*(p+1)%mod;
else
{
add(a[cnt[b]][b],p);
}
}
for (int i=1;i<=21;i++) FWT_or(a[i],1);
for (int i=0;i<(1<<21);i++)
{
for (int j=0;j<=21;j++) f[j]=a[j][i];
calcexp();
for (int j=0;j<=21;j++) a[j][i]=g[j];
}
for (int i=1;i<=21;i++) FWT_or(a[i],-1);
for (int i=0;i<(1<<21);i++)
{
ans[i]=1ll*res*a[cnt[i]][i]%mod;
}
scanf("%d",&q);
while(q--)
{
scanf("%d",&msk);
printf("%d\n",(ans[msk]+mod)%mod);
}
return 0;
}
看起来好像很困难,但事实上很简单。
首先,根据 LIS = 最小下降子序列覆盖 可知,如果 x y < n xy
大概把序列分成 x x x 个最大长度为 y y y 的下降序列即可。同时不难发现,这些下降序列所在的位置连续是最优的,于是我们就可以贪心了。贪心的过程中时刻保证 x y ≥ n xy \ge n xy≥n 即可保证正确性。
#include
typedef long long LL;
using namespace std;
template<typename T> inline void chkmin(T &a, const T &b) { a = a < b ? a : b; }
template<typename T> inline void chkmax(T &a, const T &b) { a = a > b ? a : b; }
int ans[100005];
int main() {
int T, n, x, y;
for (scanf("%d", &T); T--;) {
scanf("%d%d%d", &n, &x, &y);
if (x + y - 1 > n || (LL)x * y < n) {
puts("NO");
continue;
}
puts("YES");
int lst = 0, c = 0;
for (int i = 1; i <= n; i++) {
if (n - i > (LL)(x - 1) * y) continue;
for (int j = i; j > lst; j--) ans[++c] = j;
lst = i, --x;
}
for (int i = 1; i <= n; i++)
printf("%d%c", ans[i], " \n"[i == n]);
}
return 0;
}
我们先考虑一个简化的问题,即对于任意一张无向图,从 s s s 开始随机游走回到 s s s 的概率。经典结论就是,答案为 ( s s s 的度数 + 1 ) / ( +1)/( +1)/(整张图的总度数 + n ) +n) +n)。证明需要先证明其存在极限,这个我不会,既然题目说有极限那就肯定有极限了(雾)。
既然有极限,我们就定义 f i f_i fi 表示从点 s s s 出发最后待在 i i i 的概率。设 i i i 的度数为 d i d_i di,则有:
f i = f i d i + 1 + ∑ ( i , j ) ∈ E f j d j + 1 f_i=\frac{f_i}{d_i+1}+\sum_{(i,j) \in E}\frac{f_j}{d_j+1} fi=di+1fi+(i,j)∈E∑dj+1fj
上面有 n n n 个变量, n n n 个方程,应当能解出唯一解。
我们把 f i = d i + 1 n + ∑ j d j \displaystyle f_i=\frac{d_i+1}{n+\sum_j d_j} fi=n+∑jdjdi+1 带入上面的方程,不难发现上述方程显然成立。于是就得证了。
回到这个题,如果当前点和 y = x y=x y=x 这条直线不连通,那么我们猜想连通块大小不会很大,证明不会证。否则的话由于 y = x y=x y=x 无穷长, ∞ \infty ∞ 步之后回到 s s s 的概率显然为 0 0 0。
(事实上题解说连通块大小不会超过几百)
code by djq
#include
#define rep(i, n) for(int i = 0; i < (int)(n); i ++)
#define rep1(i, n) for(int i = 1; i <= (int)(n); i ++)
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MOD = 998244353;
map<pair<LL, LL>, int> vis;
int cnt;
LL gcd(LL u, LL v)
{
return v == 0 ? u : gcd(v, u % v);
}
bool dfs(LL x, LL y)
{
if(x == y) return true;
if(vis.find(MP(x, y)) != vis.end()) return false;
vis[MP(x, y)] = 1;
cnt ++;
if(gcd(x, y + 1) != 1) {
if(dfs(x, y + 1)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
if(gcd(x + 1, y) != 1) {
if(dfs(x + 1, y)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
if(gcd(x, y - 1) != 1) {
if(dfs(x, y - 1)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
if(gcd(x - 1, y) != 1) {
if(dfs(x - 1, y)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
if(gcd(x + 1, y + 1) != 1) {
if(dfs(x + 1, y + 1)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
if(gcd(x + 1, y - 1) != 1) {
if(dfs(x + 1, y - 1)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
if(gcd(x - 1, y - 1) != 1) {
if(dfs(x - 1, y - 1)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
if(gcd(x - 1, y + 1) != 1) {
if(dfs(x - 1, y + 1)) return true;
vis[MP(x, y)] ++;
cnt ++;
}
return false;
}
void solve()
{
LL x, y;
scanf("%lld%lld", &x, &y);
vis.clear();
cnt = 0;
if(dfs(x, y)) printf("0/1\n");
else {
int c0 = vis[MP(x, y)], c1 = cnt;
int g = gcd(c0, c1);
printf("%d/%d\n", c0 / g, c1 / g);
}
}
int main()
{
int T;
scanf("%d", &T);
while(T --) solve();
return 0;
}
首先考虑如果没有 − 1 -1 −1 怎么做。首先,最小值显然是最后一个 1 1 1,于是把序列拆成两半,后面那一半的数字集体减 1 1 1,递归找最小值。这样可以建出笛卡尔树,直接 O ( n ) O(n) O(n) dp 树的拓扑序即可。
如果有 − 1 -1 −1,那类似的,就是个区间 dp。即 f ( i , j , k ) f(i,j,k) f(i,j,k) 表示区间 [ j , k ] [j,k] [j,k],数字集体减了 i i i 后的方案数。枚举最小值(即最后一个 1 1 1)的所在位置,转移即可。
这是 O ( n 4 ) O(n^4) O(n4) 的,但是由于常数很小(题解原话),可以通过。
code by djq
#include
#define rep(i, n) for(int i = 0; i < (int)(n); i ++)
#define rep1(i, n) for(int i = 1; i <= (int)(n); i ++)
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MOD = 1e9 + 7;
int fac[105], ifac[105], inv[105];
int n, d[105];
int nxtp[105], dp[105][105][105];
int power(int x, int t)
{
int ret = 1;
while(t > 0) {
if(t & 1) ret = 1LL * ret * x % MOD;
x = 1LL * x * x % MOD;
t >>= 1;
}
return ret;
}
void init()
{
fac[0] = 1;
rep1(i, 100) fac[i] = 1LL * fac[i - 1] * i % MOD;
ifac[100] = power(fac[100], MOD - 2);
for(int i = 100; i >= 1; i --) ifac[i - 1] = 1LL * ifac[i] * i % MOD;
rep1(i, 100) inv[i] = 1LL * fac[i - 1] * ifac[i] % MOD;
}
void solve()
{
scanf("%d", &n);
rep1(i, n) scanf("%d", &d[i]);
memset(dp, 0, sizeof(dp));
rep1(i, n + 1) rep(j, n + 1) dp[i][j + 1][j] = 1;
nxtp[n + 1] = n + 1;
for(int i = n; i >= 1; i --) nxtp[i] = d[i] != -1 ? i : nxtp[i + 1];
for(int i = n; i >= 1; i --) {
rep1(j, n) for(int k = j; k < nxtp[j]; k ++) dp[i][j][k] = 1;
rep1(j, n) {
if(d[j] != i && d[j] != -1) continue;
for(int k = j; k >= 1; k --) if(dp[i][k][j - 1] != 0)
for(int l = max(j, nxtp[k]); l <= n && dp[i + 1][j + 1][l] != 0; l ++)
dp[i][k][l] = (dp[i][k][l] + 1LL * dp[i][k][j - 1] * dp[i + 1][j + 1][l] % MOD * inv[l - k + 1]) % MOD;
}
}
int ans = 1LL * dp[1][1][n] * fac[n] % MOD;
printf("%d\n", ans);
}
int main()
{
init();
int T;
scanf("%d", &T);
while(T --) solve();
return 0;
}