Atcoder abc297

https://atcoder.jp/contests/abc297/tasks

Ex - Diff Adjacent

正整数序列,和为N, 没有相邻元素相等

求所有满足的序列的长度 的和 mod 998244353

n 2e5

我的思路

~~2e5 打表啊 ~~

f(m,n) = 长度为m,和=n,满足没有相邻的方案数

ans(n) = sum m f(m,n)

g(m,n,v) = 长度为m, 和=n, 最后一个数为v, 满足没有相邻相同的方案数

f(m,n) = sum g(m,n,1…)

g(1,n,n) = 1

g(1,n,!=n)=0

g(m,n,v) = sum g(m-1,n-v, != v)


注意到转移和m无关, 所以相当于初始矩阵经过 m 次矩阵乘法以后得到的

1
2
3
4
5
6
7
8
初始(1行n^2列)
转换^m
m (对应第n行的是m,非n行的是0)
0
0
m
0
0

然而直接的话,矩阵大小是((n^2)^2) 因为转移每次 都并不是行列对齐

n^2都不行,更别说n^4还要乘法了

而注意到最左和最右的两个行/列矩阵,非零元不超过n

能否靠这个简化


另一些思路,有容斥相邻位置,但是2^{位置}次方吗?

不要等于,换成 f(=n) = f(<=n) - f(<= n-1)

中间想拆成生成函数表示,但是没有拆成功

长度之和 就是每个位置贡献1, 能不能拆

虽然11不能相邻,但是12间隔还是长度能达到 2n/3

题解

如果先不看长度

f(n) = 和为n的无相邻相等序列的方案数

// 注: 这里直接简单可以看成容斥。也可以属性(后缀长度l是1, 而对于长度l的小于的部分 奇偶抵消,所以 -1 幂次的系数和 最终还是 -1或1)

对于最后一位放1

f(n) += [...]1 - [...]11 + [...]111 - ... = $\sum_{i \ge 1} (-1)^{i-1} f(n-1\cdot i)$

对于最后一位放2

f(n) += [...]2 - [...]22 + [...]22 - ... = $\sum_{i \ge 1} (-1)^{i-1} f(n-2\cdot i)$

对于最后一位放v

f(n) += [...]v - [...]vv + [...]vv - ... = $\sum_{i \ge 1} (-1)^{i-1} f(n-v\cdot i)$

$f(n) = \sum_{v\ge 1} \sum_{i \ge 1} (-1)^{i-1} f(n-v\cdot i)$

$= \sum_{j=i\cdot v \in [1,n]} \sum_{i \ge 1, i|j} (-1)^{i-1} f(n-j)$

$= \sum_{j=i\cdot v \in [1,n]} f(n-j) \sum_{i \ge 1, i|j} (-1)^{i-1}$

所以, $F(x) = 1+F \star G$, $[x^n]G(x) = \sum_{i \ge 1, i|n} (-1)^{i-1}$

即$G(x) = \sum_{n\ge 1} (\sum_{i \ge 1, i|n} (-1)^{i-1} )x^n$

$= \sum_{i\ge 1} (\sum_{n \ge 1, i|n} (-1)^{i-1} )x^n$

$= \sum_{i\ge 1} (\sum_{v \ge 1} (-1)^{i-1} )x^{iv}$

$F=\frac{1}{1-G}$


$f(N,M)=[x^Ny^M]F(x,y) =$ 和为$N$长度$M$ 的方案数

在生成函数上 做容斥,同样是对于最后一位放什么数字

对于最后一位放$j$

$f(N,M) = \sum_{j\ge 1} (f(N-j,M-1) - f(N-2j,M-2) + f(N-3j,M-3) - \cdots )$

$=\sum_{j\ge 1} \sum_{i\ge 1} (-1)^{i-1} f(N-ij,M-i)$

表达式上推起来能推,但是还是不够显然

从意义上 就是 F[合法] + G[非法连续j]对应$F\star G$

所以也有$F=1+F\star G$

$F=\frac{1}{1-G}$


$\displaystyle G(x,y) = \sum_{i=1}^{\infty} \sum_{j=1}^{\infty} (-1)^{j-1}x^{ij}y^{j} = \sum_{i=1}^{\infty} \frac{x^iy}{1+x^iy}$

考虑生成函数$[x^N]H(x)=$ 和为N的合法序列的长度和

那么 $\displaystyle [x^N]H(x)= \sum_{M\ge 1} M [x^Ny^M]F(x,y)$

$\displaystyle = \sum_{M\ge 1} [x^Ny^{M-1}]\frac{\partial}{\partial y}F(x, y)$

$\displaystyle = [x^N] (\frac{\partial}{\partial y}F(x, y) |_{y=1} )$

$\displaystyle = [x^N] (\frac{\partial}{\partial y}(\frac{1}{1-G(x,y)}) |_{y=1} )$

$\displaystyle = [x^N] (\frac{\frac{\partial}{\partial y} (G(x, y) - 1)}{(1-G(x, y))^2}) |_{y=1} )$

$\displaystyle = [x^N] (\frac{\frac{\partial}{\partial y} (\sum_{i=1}^{\infty} \frac{x^iy}{1+x^iy} - 1)}{(1-\sum_{i=1}^{\infty} \frac{x^iy}{1+x^iy})^2}) |_{y=1} )$

$\displaystyle = [x^N] (\frac{ \sum_{i=1}^{\infty} \frac{x^i}{(1+x^iy)^2}}{(1-\sum_{i=1}^{\infty} \frac{x^iy}{1+x^iy})^2}) |_{y=1} )$

$\displaystyle = [x^N] \frac{ \sum_{i=1}^{\infty} \frac{x^i}{(1+x^i)^2}}{(1-\sum_{i=1}^{\infty} \frac{x^i}{1+x^i})^2}$

然而我不懂题解写成这样要干嘛,这样看上去并不好计算(但是够简洁), 还是直接处理G就足够计算了

$\displaystyle = [x^N] \frac{ \frac{\partial}{\partial y} G(x,y) |_{y=1}}{(1-G(x,y=1))^2}$

代码

https://atcoder.jp/contests/abc297/submissions/42048626

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include <bits/stdc++.h>
#include <atcoder/modint>
#include <atcoder/convolution>
const int MOD=998244353;
using mint = atcoder::static_modint<MOD>;

namespace CMM{
template<class T,int MOD> // T should be modint
class InvFacBinom{
std::vector<T> _inv;
int _n;
public:
std::vector<T> fac;
std::vector<T> ifac; // invfac
InvFacBinom(int n):_n(n){
fac = std::vector<T> (n+1,1);
_inv = std::vector<T> (n+1,1);
ifac = std::vector<T> (n+1,1);
for(int i=1;i<=n;i++) fac[i] = fac[i-1] * i;
for(int i=2;i<=n;i++) _inv[i] = (MOD-MOD/i) * _inv[MOD%i];
for(int i=1;i<=n;i++) ifac[i] = ifac[i-1] * _inv[i];
}
T inv(int v)const{
assert(v > 0 && v <= _n && v < MOD);
return _inv[v];
}
T binom(int n,int m)const{ return (m>n||m<0)?0:fac[n] * ifac[m] * ifac[n-m]; }
};
}

namespace CMM{
template<class T>
class Poly{
std::vector<T>_d;
public:
Poly(const std::vector<T> &d={}):_d(d){};
friend Poly operator+(const Poly&p0,const Poly&p1){
std::vector<T>r=p0._d;
if(p1._d.size()>r.size())r.resize(p1._d.size());
for(int i=0;i<(int)p1._d.size();i++)r[i]+=p1._d[i];
return r;
}
friend Poly operator-(const Poly&p0,const Poly&p1){
std::vector<T>r=p0._d;
if(p1._d.size()>r.size())r.resize(p1._d.size());
for(int i=0;i<(int)p1._d.size();i++)r[i]-=p1._d[i];
return r;
}
friend Poly operator*(const Poly&p0,const Poly&p1){ // ntt
return atcoder::convolution(p0._d,p1._d);
}
std::vector<T> coef()const{
return _d;
}
void Print() const{
for(auto v:_d) printf("%d ",v.val());
printf("\n");
}
template<int MOD>
static Poly PolyEX(const InvFacBinom<T,MOD>&ifb,int n){ // e^x
std::vector<T> p(n+1,0);
for(int i=0;i<=n;i++)p[i]=ifb.ifac[i];
return p;
}
template<int MOD>
static Poly PolyENegX(const InvFacBinom<T,MOD>&ifb,int n){ // e^{-x}
std::vector<T> p(n+1,0);
for(int i=0;i<=n;i++)p[i]=ifb.ifac[i]*(i%2?-1:1);
return p;
}
Poly Rev(int n) const{ // 颠倒[0..n]次的系数
std::vector<T>r=_d;
if(n+1>(int)r.size())r.resize(n+1);
for(int i=0;i<(int)r.size()/2;i++)std::swap(r[i],r[r.size()-1-i]);
return r;
}
Poly Modn(int n) const{ // return (this) mod x^n
std::vector<T>r=_d;
if((int)r.size()>n) r.resize(n);
return r;
}
Poly Inv(int n) const{ // return this^{-1}, s.t. this this^{-1} \equiv 1 \pmod{x^n}
assert(_d[0] != 0);
Poly r = std::vector<T>{_d[0].inv()};
for(int pwr=1;pwr<n;pwr*=2){
r = r.Modn(pwr);
r = r * (Poly({2}) - r * _d);
}
return r.Modn(n);
}
Poly Norm() const{ // 清除高位0
std::vector<T>r=_d;
while(r.size() > 1 && r.back()==0)r.pop_back();
return r;
}
std::pair<Poly,Poly> Div(const Poly<T>& B) const{ // return {A / B,A % B}
const Poly& A=*this;
int m=B._d.size()-1;
int n=std::max(A._d.size()-1,B._d.size()-1); // n >= m
// A=BC+D
auto C=(A.Rev(n)*(B.Rev(m)).Inv(n-m+1)).Modn(n-m+1).Rev(n-m);
auto D=(A-B*C).Norm();
return {C,D};
}
std::vector<T> MPE(const std::vector<T>& a) const{ // Multipoint evaluation
int sz=a.size();
std::vector<std::vector<Poly > > tree = {{}};
for(auto v:a)tree[0].push_back(std::vector<T>{-v,1}); // x-ai
while((tree[0].size() & (tree[0].size()-1)))tree[0].push_back(std::vector<T>{1}); // 1
while(tree.back().size() > 1){
std::vector<Poly >row={};
const auto&b=tree.back();
for(int i=0;i<(int)b.size()/2;i++) row.push_back(b[i*2]*b[i*2+1]);
tree.push_back(row);
}
std::vector<Poly > h = {Poly(_d)};
// h[0] 表示 h, 从上向下取mod
for(int i=tree.size()-1;i>=0;i--){
std::vector<Poly > nexth = {};
for(int j=0;j<(int)tree[i].size();j++) nexth.push_back(h[j/2].Div(tree[i][j]).second);
h = nexth;
}
std::vector<T> res;
for(int i=0;i<sz;i++) res.push_back(h[i]._d[0]);
return res;
}
};
};
using namespace CMM;

// --------------- template ------------------
typedef long long ll;
#define rep(i,a,n) for (ll i=a;i<(ll)(n);i++)
ll read(){ll r;scanf("%lld",&r);return r;}
int main(){
using namespace std;
using mint=atcoder::modint998244353;
const int MOD=998244353;
using poly=CMM::Poly<mint>;
int n=read();
InvFacBinom<mint,MOD> ifb(n+1);
vector<mint> g(n + 1); // g(x,y=1) = (-1)^j x^{ij}
vector<mint> dg(n + 1); // d g(x,y)/dy (y=1) = (-1)^j j x^{ij}
rep(i,1,n+1) rep(j,1,n/i+1){
dg[i * j] = dg[i * j] + ((j & 1) ? 1 : -1) * j;
g[i * j] = g[i * j] + ((j & 1) ? 1 : -1);
}
g[0] -= 1; // g-1
poly h = poly{dg} * (poly{g} * poly{g}).Inv(n); // dg/(g-1)^2
printf("%d\n",h.coef()[n].val());
return 0;
}

总结

Ex

哦,双变量生成函数,学过没用过,第一次用

学了属性容斥,把最简单的容斥忘记了。。。

没有很懂, $F(x)=\frac{1}{1-C(x)}$有什么快速可见的逻辑吗,感觉我上面推半天,题解就一句话

后面这些偏导数,正着想没想到,反过来看也挺自然,第一次见

参考

官方题解