div.2 视频讲解:BV1b44y1z7Xs
div.1 视频讲解:BV1CN411Z7vP
div.2-A. Eshag Loves Big Arrays
题目大意
给定序列 a a a ,每次可以选择其中一些数,删除其中大于这些数平均值的数,求最多可以删除多少数。
题解
每次选择最小的数 x x x 和其他任意大于 x x x 的数 y y y ,则必定可以删除 y y y。因此最后只剩下最小的数不会被删除。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=110;
int a[MAXN];
int main()
{
int T,n,i,ans;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
ans=n;
for(i=1;i<=n;i++)
{
if(a[i]==a[1])
ans--;
}
printf("%d\n",ans);
}
}
div.2-B. Sifid and Strange Subsequences
题目大意
定义一个序列 ( b 1 , b 2 , . . . , b k ) (b_1,b_2,...,b_k) (b1,b2,...,bk) 是奇怪的,当其满足以下条件:
- 对于任意满足 1 ≤ i < j ≤ k 1 \leq i < j \leq k 1≤i<j≤k 的点对 ( i , j ) (i,j) (i,j) , ∣ a i − a j ∣ ≥ M A X |a_i-a_j| \geq MAX ∣ai−aj∣≥MAX ,其中 M A X MAX MAX 为序列种的最大值。
给定长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1≤n≤105) 的序列 a a a ,求其最大的奇怪子序列。
题解
奇怪序列中,必定最多只有一个正数。否则两个正数之差,必定小于
M
A
X
MAX
MAX 。
因此最大奇怪子序列最少可以选择所有非正数。然后再判断在选择一个最小正数的情况下,是否依旧满足条件,若可以则答案
+
1
+1
+1 。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100100;
int a[MAXN];
int main()
{
int T,n,i,ans,flag,mx;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
mx=0;
for(i=1;i<=n;i++)
{
if(a[i]>0)
{
mx=a[i];
break;
}
}
ans=i-1;
flag=(mx>0);
for(i=2;i<=ans;i++)
{
if(a[i]-a[i-1]<mx)
{
flag=0;
break;
}
}
printf("%d\n",ans+flag);
}
}
div.2-C/div.1-A. Parsa’s Humongous Tree
题目大意
给定一棵包含
n
(
2
≤
n
≤
1
0
5
)
n(2 \leq n \leq 10^5)
n(2≤n≤105) 个节点的树,每个节点
v
v
v 有两个给定整数
l
v
,
r
v
(
1
≤
l
v
≤
r
v
≤
1
0
9
)
l_v,r_v(1 \leq l_v \leq r_v \leq 10^9)
lv,rv(1≤lv≤rv≤109) 。
你需要为每个节点
v
v
v 赋一个权值
a
v
a_v
av ,使得对于所有边
(
u
,
v
)
(u,v)
(u,v) ,
∣
a
u
−
a
v
∣
|a_u-a_v|
∣au−av∣ 的总和最大。
题解
设有
x
x
x 节点被赋予的权值为
a
i
a_i
ai ,与
x
x
x 相邻的节点中,有
p
p
p 个节点的权值比
a
i
a_i
ai 大,有
q
q
q 个节点的权值比
a
i
a_i
ai 小。
那么:
- 若 p > q p > q p>q ,则可以减小 a i a_i ai 得到更优解,且减小时 p > q p > q p>q 不变,即 a x = l x a_x=l_x ax=lx 为最优解;
- 若 p < q p < q p<q ,则可以增大 a i a_i ai 得到更优解,且增大时 p < q p < q p<q 不变,即 a x = r x a_x=r_x ax=rx 为最优解;
- 若 p = q p = q p=q ,则 a x a_x ax 为任何值均可;
因此,对于每个节点 v v v ,赋值为 l v l_v lv 或 r v r_v rv 时最优。
设
d
p
x
,
0
dp_{x,0}
dpx,0 表示
x
x
x 节点赋值为
l
v
l_v
lv 时,以
x
x
x 节点为根的子树的最大边权值和;
设
d
p
x
,
1
dp_{x,1}
dpx,1 表示
x
x
x 节点赋值为
r
v
r_v
rv 时,以
x
x
x 节点为根的子树的最大边权值和。
设
s
o
n
son
son 为
x
x
x 的儿子,易得:
d p x , 0 = ∑ s o n m a x ( d p s o n , 0 + ∣ l x − l s o n ∣ , d p s o n , 1 + ∣ l x − r s o n ∣ ) dp_{x,0}=\sum_{son}{max(dp_{son,0}+|l_x-l_{son}|,dp_{son,1}+|l_x-r_{son}|)} dpx,0=son∑max(dpson,0+∣lx−lson∣,dpson,1+∣lx−rson∣)
d p x , 1 = ∑ s o n m a x ( d p s o n , 0 + ∣ r x − l s o n ∣ , d p s o n , 1 + ∣ r x − r s o n ∣ ) dp_{x,1}=\sum_{son}{max(dp_{son,0}+|r_x-l_{son}|,dp_{son,1}+|r_x-r_{son}|)} dpx,1=son∑max(dpson,0+∣rx−lson∣,dpson,1+∣rx−rson∣)
跑树形DP即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100100;
long long dp[MAXN][2],l[MAXN],r[MAXN];
vector<int> vec[MAXN];
void dfs(int x,int fa)
{
dp[x][0]=dp[x][1]=0;
for(int i=0;i<vec[x].size();i++)
{
int son=vec[x][i];
if(son==fa)
continue;
dfs(son,x);
dp[x][0]+=max(dp[son][0]+abs(l[x]-l[son]),dp[son][1]+abs(l[x]-r[son]));
dp[x][1]+=max(dp[son][0]+abs(r[x]-l[son]),dp[son][1]+abs(r[x]-r[son]));
}
}
int main()
{
int T,i,n,u,v;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%lld%lld",&l[i],&r[i]);
vec[i].clear();
}
for(i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs(1,-1);
printf("%lld\n",max(dp[1][0],dp[1][1]));
}
}
div.2-D/div.1-B. Kavi on Pairing Duty
题目大意
数轴上有
2
n
2n
2n 个点,第
i
i
i 个点在
x
=
i
x=i
x=i 处。
需要将这
2
n
2n
2n 个点分为
n
n
n 对
(
x
,
y
)
(
x
<
y
)
(x,y)(x < y)
(x,y)(x<y) ,用线段连接
x
x
x 和
y
y
y ,且对于任意两对
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi) 和
(
x
j
,
y
j
)
(x_j,y_j)
(xj,yj) ,满足以下条件之一:
- 两条线段呈包含关系,即 x i < x j < y j < y i x_i < x_j < y_j < y_i xi<xj<yj<yi 或 x j < x i < y i < y j x_j < x_i < y_i < y_j xj<xi<yi<yj
- 两条线的长度相同,即 ∣ x i − y i ∣ = ∣ x j − y j ∣ |x_i-y_i|=|x_j-y_j| ∣xi−yi∣=∣xj−yj∣
求有多少种合法的方案。
题解
设 d p x dp_x dpx 表示 n = x n=x n=x 时的方案数。
如果只考虑条件2,对于
n
n
n 的每个因子
p
p
p ,可以用长度为
p
p
p 的线段逐个连接,即
i
i
i 与
i
+
p
i+p
i+p 或
i
−
p
i-p
i−p 配对,构成一种合法方案。
即
d
p
x
+
=
f
n
u
m
x
dp_x+=fnum_x
dpx+=fnumx ,其中
f
n
u
m
x
fnum_x
fnumx 为
x
x
x 的因子数,可以打表预处理。
再考虑条件1,会发现若最左边
i
i
i 个节点和最右边
i
i
i 个节点依次交叉相连,即
a
(
1
≤
a
≤
i
)
a(1 \leq a \leq i)
a(1≤a≤i) 点与
2
n
−
i
+
a
2n-i+a
2n−i+a 点配对,则中间剩余的节点配对方案若合法,则整体方案也合法。
即
d
p
x
+
=
∑
i
=
1
x
−
1
d
p
i
dp_x+=\sum_{i=1}^{x-1}{dp_i}
dpx+=∑i=1x−1dpi
综上:
d
p
x
=
f
n
u
m
x
+
∑
i
=
1
x
−
1
d
p
i
dp_x=fnum_x+\sum_{i=1}^{x-1}{dp_i}
dpx=fnumx+i=1∑x−1dpi
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int MAXN=1000100;
int fnum[MAXN];
long long dp[MAXN];
int main()
{
int i,j,n;
long long sum=0;
for(i=1;i<MAXN;i++)
{
for(j=i;j<MAXN;j+=i)
fnum[j]++;
}
scanf("%d",&n);
for(i=1;i<=n;i++)
{
dp[i]=(sum+fnum[i])%mod;
sum=(sum+dp[i])%mod;
}
printf("%lld\n",dp[n]);
}
div.2-E/div.1-C. Trees of Tranquillity
题目大意
给定
n
n
n 个节点,有两棵有根树共享这
n
n
n 个节点,且根节点都为节点
1
1
1 。
现在构造一个具有
n
n
n 个节点的新图,若节点
u
u
u 和
v
v
v 满足以下两个条件,则在新图中连接这两点:
- 在树 1 1 1 中, u u u 是 v v v 的祖先或 v v v 是 u u u 的祖先;
- 在树 2 2 2 中, u u u 不是 v v v 的祖先且 v v v 不是 u u u 的祖先;
求新图中最大团的大小。
题解
首先理解新图中团的构成条件,会发现其中节点必须满足以下条件:
- 在树 1 1 1 中,团中的节点都在从根节点出发的一条链上;
- 在树 2 2 2 中,这些节点两两不为祖孙关系
那么可以考虑在树 1 1 1 上跑dfs,边跑边选点,同时用数据结构维护这些点在树 2 2 2 中的关系。
具体而言,在树 1 1 1 上dfs遍历到节点 x x x 时,在数据结构中查询之前选择的点中,是否在树 2 2 2 上有 x x x 的祖先或 x x x 的孙子。
- 若有 x x x 的祖先,则贪心删除该祖先,选择 x x x 节点;
- 若有 x x x 的祖先,则不选择 x x x 节点;
- 若都没有,则选择 x x x 节点;
贪心策略:对于一对祖孙节点来说,选择其中的孙子能为以后的选择提供更大的空间。
回溯时,记得将数据结构恢复原样。
答案统计历史最大被选择的点的数量即可。
数据结构可以采用dfs序+线段树,或dfs序+set进行维护。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=300300;
int cnt,n;
int dfn[MAXN],edn[MAXN],dp[MAXN];
vector<int> e1[MAXN],e2[MAXN];
struct node
{
int l,r;
long long nsum,inc;
}segtree[MAXN<<2];
void build(int i,int l,int r)
{
segtree[i].l=l;
segtree[i].r=r;
segtree[i].inc=-1;
if(l==r)
{
segtree[i].nsum=0;
return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
segtree[i].nsum=max(segtree[i<<1].nsum,segtree[i<<1|1].nsum);
}
void change(int i,int a,int b,int c)
{
if(segtree[i].l==a&&segtree[i].r==b)
{
segtree[i].inc=c;
segtree[i].nsum=c;
return;
}
if(segtree[i].inc>-1)
{
segtree[i<<1].nsum=segtree[i<<1|1].nsum=segtree[i].inc;
segtree[i<<1].inc=segtree[i<<1|1].inc=segtree[i].inc;
segtree[i].inc=-1;
}
int mid=(segtree[i].l+segtree[i].r)>>1;
if(b<=mid)
change(i<<1,a,b,c);
else if(a>mid)
change(i<<1|1,a,b,c);
else
{
change(i<<1,a,mid,c);
change(i<<1|1,mid+1,b,c);
}
segtree[i].nsum=max(segtree[i<<1].nsum,segtree[i<<1|1].nsum);
}
long long query(int i,int a,int b)
{
if(segtree[i].l==a&&segtree[i].r==b)
{
return segtree[i].nsum;
}
if(segtree[i].inc>-1)
{
segtree[i<<1].nsum=segtree[i<<1|1].nsum=segtree[i].inc;
segtree[i<<1].inc=segtree[i<<1|1].inc=segtree[i].inc;
segtree[i].inc=-1;
}
int mid=(segtree[i].l+segtree[i].r)>>1;
if(b<=mid)
return query(i<<1,a,b);
else if(a>mid)
return query(i<<1|1,a,b);
else
return max(query(i<<1,a,mid),query(i<<1|1,mid+1,b));
}
bool isfa(int x,int p)
{
return dfn[x]>=dfn[p]&&dfn[x]<=edn[p];
}
void dfs1(int x)
{
dfn[x]=++cnt;
for(int i=0;i<e1[x].size();i++)
{
dfs1(e1[x][i]);
}
edn[x]=cnt;
}
void dfs2(int x,int fa)
{
int tmp=query(1,dfn[x],edn[x]);
if(tmp)
{
dp[x]=dp[fa];
if(isfa(x,tmp))
{
change(1,dfn[tmp],edn[tmp],0);
change(1,dfn[x],edn[x],x);
}
}
else
{
dp[x]=dp[fa]+1;
change(1,dfn[x],edn[x],x);
}
for(int i=0;i<e2[x].size();i++)
{
dfs2(e2[x][i],x);
}
if(tmp)
{
if(isfa(x,tmp))
change(1,dfn[tmp],edn[tmp],tmp);
}
else
change(1,dfn[x],edn[x],0);
}
int main()
{
int T,i,u,v,ans,p;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
{
e1[i].clear();
e2[i].clear();
}
for(i=2;i<=n;i++)
{
scanf("%d",&p);
e2[p].push_back(i);
}
for(i=2;i<=n;i++)
{
scanf("%d",&p);
e1[p].push_back(i);
}
cnt=0;
dfs1(1);
build(1,1,n);
dfs2(1,0);
ans=0;
for(i=1;i<=n;i++)
ans=max(ans,dp[i]);
printf("%d\n",ans);
}
}
div.2-F/div.1-D. It’s a bird! No, it’s a plane! No, it’s AaParsa!
题目大意
给定
n
(
2
≤
n
≤
600
)
n(2 \leq n \leq 600)
n(2≤n≤600) 个城市,编号从
0
0
0 到
n
−
1
n-1
n−1。
有
m
(
n
≤
m
≤
n
2
)
m(n \leq m \leq n^2)
m(n≤m≤n2) 座人间大炮,每个城市至少有一个人间大炮。第
i
i
i 座大炮布置在
a
i
a_i
ai 城市,初始朝向
b
i
b_i
bi 城市,发射后经过
c
i
c_i
ci 时间到达所朝向的城市。
每座大炮都会不停旋转。若在某一时刻某个大炮朝向
x
x
x 城市,则下一秒会朝向
(
x
+
1
)
%
n
(x+1)\%n
(x+1)%n 城市。
球在第
0
0
0 秒从任意城市出发,通过人间大炮,到达任意城市的最早时间。
允许在任意城市等待任意时间。
题解
首先需要解决大炮会旋转的问题。虽然一座城市可能有多个大炮,但实际上如果在某一时刻前往某一城市,只可能搭乘其中某个最优的大炮。
设
c
n
u
,
v
cn_{u,v}
cnu,v 表示在第
0
0
0 秒从
u
u
u 城市出发,只利用
u
u
u 城的大炮,最早到达
v
v
v 城市的时间。
对于第
i
i
i 个大炮,其初始对
c
n
u
,
v
cn_{u,v}
cnu,v 的影响为
c
n
a
i
,
b
i
=
c
i
cn_{a_i,b_i}=c_i
cnai,bi=ci 。
过了
x
x
x 秒后,可前往
(
b
i
+
1
)
%
n
(b_i+1)\%n
(bi+1)%n城市,在第
c
i
+
1
c_i+1
ci+1 秒到达;即
c
n
a
i
,
(
b
j
+
x
)
%
n
=
c
i
+
x
cn_{a_i,(b_j+x)\%n}=c_i+x
cnai,(bj+x)%n=ci+x
但由于可能原本就有其他前往
(
b
j
+
x
)
%
n
(b_j+x)\%n
(bj+x)%n 的大炮,因此应取较小值:
c
n
u
,
v
=
min
m
i
d
=
0
n
−
1
c
n
u
,
m
i
d
+
(
v
−
m
i
d
+
n
)
%
n
cn_{u,v}=\min_{mid=0}^{n-1}{cn_{u,mid}+(v-mid+n)\%n}
cnu,v=mid=0minn−1cnu,mid+(v−mid+n)%n
这样,就有了 n 2 n^2 n2 条边,枚举起点跑 D i j k s t r a Dijkstra Dijkstra 即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=660;
int cn[MAXN][MAXN],dis[MAXN];
priority_queue<pair<int,int> > q;
int main()
{
int n,m,i,j,a,b,c,st,now,mid,to,d;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
for(j=0;j<n;j++)
cn[i][j]=2e9;
for(i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&c);
cn[a][b]=c;
}
for(i=0;i<n;i++)
{
for(j=0;j<2*n;j++)
{
mid=j%n;
to=(mid+1)%n;
cn[i][to]=min(cn[i][to],cn[i][mid]+1);
}
}
for(st=0;st<n;st++)
{
for(i=0;i<n;i++)
dis[i]=2e9;
dis[st]=0;
q.push(make_pair(0,st));
while(!q.empty())
{
now=q.top().second;
d=-q.top().first;
q.pop();
if(dis[now]<d)
continue;
to=d%n;
for(i=0;i<n;i++)
{
if(dis[to]>d+cn[now][i])
{
dis[to]=d+cn[now][i];
q.push(make_pair(-dis[to],to));
}
to=(to+1)%n;
}
}
for(i=0;i<n;i++)
printf("%d ",dis[i]);
puts("");
}
}
div.1-E. Mashtali and Hagh Trees
题目大意
定义满足以下条件的有向树为啊哈树:
- 最长的有向路径恰好为 n n n ;
- 每个节点最多有 3 3 3 条有向边(无关有向边的方向)
- 定义节点 u u u 和节点 v v v 为朋友节点,若存在一条有向路径从一点到另一点。树上每一对不为朋友节点的 u u u 和 v v v ,必定存在节点 w w w ,是节点 u u u 和节点 v v v 的公共朋友节点。
给定 n ( 1 ≤ n ≤ 1 0 6 ) n(1 \leq n \leq 10^6) n(1≤n≤106) ,求合法的无标记啊哈树种数。
题解
设 d p i dp_i dpi 为满足下列条件的树的数量:
- 树中存在一个根;
- 所有边方向相同,都从根节点向下指向叶节点;
- 根节点最多有 2 2 2 个儿子;
- 从根节点到叶节点的最长路径长度不超过 i i i ;
求 d p i dp_i dpi 时,考虑其根节点情况,有以下几种可能:
- 根节点没有儿子,共 1 1 1 种情况;
- 根节点有 1 1 1 个儿子,共 d p i − 1 dp_{i-1} dpi−1 种情况;
- 根节点有 2 2 2 个儿子,两个子树同构有 d p i − 1 dp_{i-1} dpi−1 种,异构有 d p i − 1 ∗ ( d p i − 1 − 1 ) 2 \frac{dp_{i-1}*(dp_{i-1}-1)}{2} 2dpi−1∗(dpi−1−1) ,共 d p i − 1 ∗ ( d p i − 1 + 1 ) 2 \frac{dp_{i-1}*(dp_{i-1}+1)}{2} 2dpi−1∗(dpi−1+1) 种情况;
综上,易得
d
p
i
dp_i
dpi 的转移式为
d
p
i
=
1
+
d
p
i
−
1
+
d
p
i
−
1
∗
(
d
p
i
−
1
+
1
)
2
dp_i=1+dp_{i-1}+\frac{dp_{i-1}*(dp_{i-1}+1)}{2}
dpi=1+dpi−1+2dpi−1∗(dpi−1+1)
基于 d p i dp_i dpi 的差分,我们可以得到一些相关的数据:
- 设 d p 1 i = d p i − d p i − 1 dp1_i=dp_i-dp_{i-1} dp1i=dpi−dpi−1 ,表示最长的有向路径长度不超过为 i i i 的根节点恰好有 2 2 2 个儿子的方案数,或最长的有向路径长度恰好为 i i i 的根节点有最多 2 2 2 个儿子的方案数
- 设 d p 2 i = d p 1 i − d p 1 i − 1 dp2_i=dp1_i-dp1_{i-1} dp2i=dp1i−dp1i−1, 表示最长的有向路径长度恰好为 i i i 的根节点恰好有 2 2 2 个儿子的方案数
考虑最终的啊哈树也是所有边方向相同,都从根节点向下指向叶节点(或都从叶节点指向根节点)且根节点至少有 2 2 2 的儿子的方案,那么有以下情况:
- 根节点有 3 3 3 个儿子,则有 2 ∗ d p n − 1 ∗ ( d p n − 1 + 1 ) ∗ ( d p n − 1 + 2 ) 6 − 2 ∗ d p n − 2 ∗ ( d p n − 2 + 1 ) ∗ ( d p n − 2 + 2 ) 6 \frac{2*dp_{n-1}*(dp_{n-1}+1)*(dp_{n-1}+2)}{6}-\frac{2*dp_{n-2}*(dp_{n-2}+1)*(dp_{n-2}+2)}{6} 62∗dpn−1∗(dpn−1+1)∗(dpn−1+2)−62∗dpn−2∗(dpn−2+1)∗(dpn−2+2) 种
- 根节点有 2 2 2 个儿子,则有 2 ∗ d p n − 1 ∗ ( d p n − 1 + 1 ) 2 − 2 ∗ d p n − 2 ∗ ( d p n − 2 + 1 ) 2 \frac{2*dp_{n-1}*(dp_{n-1}+1)}{2}-\frac{2*dp_{n-2}*(dp_{n-2}+1)}{2} 22∗dpn−1∗(dpn−1+1)−22∗dpn−2∗(dpn−2+1) 种
还有其他情况,可以表示为根节点具有 2 2 2 个儿子且从根节点出发最长路径为 l l l 的树,和另一棵具有 2 2 2 个儿子且从根节点出发最长路径为 n − k − 1 n-k-1 n−k−1 的树用 k k k 条边拼接而成。如下图的红树、蓝树和黑边。
上述情况的贡献为:
∑ i = 0 n − 1 ( d p 2 i ∗ ∑ j = 0 n − 1 − i d p 2 n − 1 − j ) = ∑ i = 0 n − 1 d p 2 i ∗ d p 1 n − 1 − i \sum_{i=0}^{n-1}{(dp2_i* \sum_{j=0}^{n-1-i}{dp2_{n-1-j}})}=\sum_{i=0}^{n-1}{dp2_i*dp1_{n-1-i}} i=0∑n−1(dp2i∗j=0∑n−1−idp2n−1−j)=i=0∑n−1dp2i∗dp1n−1−i
将上述贡献全部汇总即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int MAXN=1000100;
ll dp[MAXN],dp1[MAXN],dp2[MAXN];
ll powmod(ll x,ll p)
{
ll ret=1;
while(p)
{
if(p&1)
ret=ret*x%mod;
x=x*x%mod;
p>>=1;
}
return ret;
}
int main()
{
ll n,i,ans;
ll inv2=powmod(2,mod-2);
ll inv6=powmod(6,mod-2);
scanf("%lld",&n);
dp[0]=dp1[0]=dp2[0]=1;
for(i=1;i<=n;i++)
{
dp[i]=(1+dp[i-1]+dp[i-1]*(dp[i-1]+1)%mod*inv2)%mod;
dp1[i]=(dp[i]-dp[i-1]+mod)%mod;
dp2[i]=(dp1[i]-dp1[i-1]+mod)%mod;
}
ans=dp1[n-1];
ans=(ans+2*dp[n-1]*(dp[n-1]+1)%mod*(dp[n-1]+2)%mod*inv6)%mod;
if(n>=2)
ans=(ans-2*dp[n-2]*(dp[n-2]+1)%mod*(dp[n-2]+2)%mod*inv6)%mod;
ans=(ans+2*dp[n-1]*(dp[n-1]+1)%mod*inv2)%mod;
if(n>=2)
ans=(ans-2*dp[n-2]*(dp[n-2]+1)%mod*inv2)%mod;
for(i=1;i<n;i++)
{
ans=(ans+dp2[i]*dp1[n-1-i])%mod;
}
ans=(ans%mod+mod)%mod;
printf("%lld\n",ans);
}
div.1-F. AmShZ Farm
题目大意
若一个包含 n n n 个元素且每个元素都在 [ 1 , n ] [1,n] [1,n] 范围内的整数数组,可以通过向每个元素添加非负整数转化为一个 1 1 1 到 n n n 的排列,则称其为更平等数组。
如果包含 k k k 个元素的数组 b b b 与包含 n n n 个元素的更平等数组 a a a 满足以下条件,则称数组 b b b 兼容 数组 a a a:
- 对于任意 i ( 1 ≤ i ≤ k ) i(1 \leq i \leq k) i(1≤i≤k) , 1 ≤ b i ≤ n 1 \leq b_i \leq n 1≤bi≤n 且 a b 1 = a b 2 = . . . = a b k a_{b_1}=a_{b_2}=...=a_{b_k} ab1=ab2=...=abk
给定 n ( 1 ≤ n ≤ 1 0 9 ) n(1 \leq n \leq 10^9) n(1≤n≤109) 和 k ( 1 ≤ k ≤ 1 0 5 ) k(1 \leq k \leq 10^5) k(1≤k≤105) ,求满足条件的数组 a a a 与数组 b b b 对数。
题解
参考官方题解。
考虑以下问题:
- 有 n n n 个汽车逐个进入停车场。 停车场有 n n n 个编号为 1 , 2 , … , n 1,2,…,n 1,2,…,n 的空位, n n n 个汽车中的第 i i i 个要停在第 a i a_i ai 个或其之后的空位。也就是说,第 i i i 个汽车进入停车场时,它将停在第一个空位 s ( a ≥ a i ) s(a \geq a_i) s(a≥ai) 中。如果所有车都能停入某个空位中,则称数组 a a a 为好数组。
会发现好数组等价于更平等数组,因为在任意一个排序后的好数组中,都满足 a i ≤ i a_i \leq i ai≤i ,即更平等数组排序后的结果。
现在让我们稍微修改一下上面的问题:
- 一个带有 n + 1 n + 1 n+1 个空位的圆形停车场。 有 n n n 个车要在其中停车,其中第 i i i 个车要在第 a i ( 1 ≤ a i ≤ n + 1 ) ai(1 \leq a_i \leq n+1) ai(1≤ai≤n+1) 个或其顺时针之后的空位上。也就是说,第 i i i 个汽车进入停车场时,它将停在第一个空位 s s s 上,满足 s s s是顺时针方向(包括 a i a_i ai)之后的第一个空位。
显而易见,所有车停入后,有一个空位将为空,我们将此空位称为 x x x 。根据 x x x 的不同,我们可以将数组分为以下两种:
- x = n + 1 x = n + 1 x=n+1 的数组就是好数组,也就是更平等的数组。
- x ≠ n + 1 x \neq n + 1 x=n+1 的数组称为坏数组。
每个好数组都可以映射到
n
n
n 个坏数组上,只需将所有
a
i
a_i
ai 增加
x
(
1
≤
x
≤
n
)
x(1 \leq x \leq n)
x(1≤x≤n) 。
具体而言,对于每个
i
(
1
≤
i
≤
n
)
i(1 \leq i \leq n)
i(1≤i≤n) ,
a
i
=
(
a
i
+
x
)
%
(
n
+
1
)
+
1
a_i=(a_i+x)\% (n+1) +1
ai=(ai+x)%(n+1)+1 。
请注意,
a
a
a 变化时与
a
a
a 兼容的数组
b
b
b 依旧满足条件。
因此,好数组的数量在所有可能的
(
n
+
1
)
n
(n + 1)^n
(n+1)n 个数组中的比例为
1
n
+
1
\frac{1}{n+1}
n+11 。
已知兼容数组 b b b 需要满足 a b 1 = a b 2 = . . . = a b k a_{b_1}=a_{b_2}=...=a_{b_k} ab1=ab2=...=abk 。设 a b 1 = x a_{b_1}=x ab1=x ,固定 x x x ,分别考虑方案:
- 兼容数组 b b b 的 k k k 个元素映射到数组 a a a 中 i i i 个值为 x x x 的方案有 S ( k , i ) ⋅ i ! S(k,i) \cdot i! S(k,i)⋅i! 种。
-
- 其中 S S S 是第二类斯特林数, S ( n , m ) S(n,m) S(n,m) 表示将 n n n 个不同的元素拆分成 m m m 个集合的方案数
- 数组 a a a 中选出 i i i 个值为 x x x 的数供兼容数组 b b b 映射的方案有 C n i ⋅ ( n + 1 ) n − i C_n^i \cdot (n+1)^{n-i} Cni⋅(n+1)n−i 种。
上式计算的数组 a a a 不一定是好数组,好数组占总数的比例为 1 n + 1 \frac{1}{n+1} n+11 ,因此需要除以 n + 1 n+1 n+1 。同时由于 x x x 有 n + 1 n+1 n+1 种选择,需要乘以 n + 1 n+1 n+1 ,以上两个操作可以相互抵消。
因此好数组
a
a
a 和兼容数组
b
b
b 的对数共有:
∑
i
=
1
k
S
(
k
,
i
)
⋅
i
!
⋅
C
n
i
⋅
(
n
+
1
)
∣
n
−
i
∣
\sum_{i=1}^{k}{S(k,i) \cdot i! \cdot C_n^i \cdot (n+1)^{|n-i|}}
i=1∑kS(k,i)⋅i!⋅Cni⋅(n+1)∣n−i∣
其中 S ( k , i ) S(k,i) S(k,i) 可以用 FFT 在 O ( k ⋅ l o g k ) O(k \cdot log k) O(k⋅logk) 时间复杂度内求得。
- 第二类斯特林数具有通项式: S ( n , k ) = 1 k ! ∑ i = 0 k ( − 1 ) i C k i ( k − i ) n S(n,k)=\frac{1}{k!} \sum_{i=0}^{k}{(-1)^i C_k^i (k-i)^n} S(n,k)=k!1∑i=0k(−1)iCki(k−i)n
- 加以变化 S ( n , k ) = 1 k ! ∑ i = 0 k ( − 1 ) i C k i ( k − i ) n = ∑ i = 0 k ( ( − 1 ) i ⋅ 1 i ! ) ⋅ ( 1 ( k − i ) ! ⋅ ( k − i ) n ) S(n,k)=\frac{1}{k!} \sum_{i=0}^{k}{(-1)^i C_k^i (k-i)^n}=\sum_{i=0}^k{((-1)^i \cdot \frac{1}{i!}) \cdot (\frac{1}{(k-i)!} \cdot (k-i)^n)} S(n,k)=k!1∑i=0k(−1)iCki(k−i)n=∑i=0k((−1)i⋅i!1)⋅((k−i)!1⋅(k−i)n)
- 设 a i = ( − 1 ) i ⋅ 1 i ! a_i=(-1)^i \cdot \frac{1}{i!} ai=(−1)i⋅i!1 , b i = 1 i ! ⋅ i n b_i=\frac{1}{i!} \cdot i^n bi=i!1⋅in ,则 S ( n , k ) = ∑ i = 0 k a i ⋅ b k − i S(n,k)=\sum_{i=0}^k{a_i \cdot b_{k-i}} S(n,k)=∑i=0kai⋅bk−i ,可以用卷积运算。
参考代码
# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=3e5+10;
const int MAXM=18;
const ll mod=998244353;
ll powmod(ll x,ll p)
{
ll ret=1;
while(p)
{
if(p&1)
ret=(ret*x)%mod;
x=x*x%mod;
p>>=1;
}
return ret;
}
ll n,k,C[MAXN],ans,A[MAXN],B[MAXN],rev[MAXN];
ll fact[MAXN],ifact[MAXN];
void NTT(ll *A, bool inv){
ll n=(1<<MAXM);
for(int i=0;i<(1<<MAXM);++i)
if(rev[i]<i)
swap(A[i],A[rev[i]]);
for(int ln=1;ln<n;ln<<=1){
ll w=powmod(3,mod/2/ln);
if(inv)
w=powmod(w,mod-2);
for(int i=0;i<n;i+=ln+ln){
ll wn=1;
for(int j=i;j<i+ln;++j){
int x=A[j],y=A[j+ln]*wn%mod;
A[j]=(x+y)%mod;
A[j+ln]=(x-y+mod)%mod;
wn=wn*w%mod;
}
}
}
if(inv){
ll invn=powmod(1<<MAXM,mod-2);
for(int i=0;i<n;++i)
A[i]=A[i]*invn%mod;
}
}
ll power(ll a, ll b, ll md)
{
return(!b?1:(b&1?a*power(a*a%md,b/2,md)%md:power(a*a%md,b/2,md)%md));
}
int main()
{
scanf("%lld%lld",&n,&k);
C[0]=fact[0]=1;
for(int i=1;i<MAXN;++i){
C[i]=C[i-1]*(n-i+1)%mod*powmod(i,mod-2)%mod;
fact[i]=fact[i-1]*i%mod;
}
ifact[MAXN-1]=powmod(fact[MAXN-1],mod-2);
for(int i=MAXN-2;i>=0;--i)
ifact[i]=ifact[i+1]*(i+1)%mod;
for(int i=1;i<(1<<MAXM);++i)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(MAXM-1));
for(int i=0;i<=k;++i)
{
A[i]=powmod(i,k)*ifact[i]%mod;
B[i]=ifact[i];
if(i%2)
B[i]=(mod-B[i])%mod;
}
NTT(A,0),NTT(B,0);
for(int i=0;i<MAXN;++i)
A[i]=A[i]*B[i]%mod;
NTT(A,1);
for(int i=1;i<=k;++i)
ans=(ans+A[i]*C[i]%mod*powmod(n+1,abs(n-i))%mod*fact[i]%mod)%mod;
printf("%lld\n",ans);
}