Codeforces Round#722 div.1+div.2题解

本文深入探讨了如何处理包含百万乃至十亿节点的大型图数据,重点介绍了利用Dijkstra算法、树形DP和斯特林数在不同场景下的应用,如寻找最大团、构建啊哈树和解决汽车停车问题。这些策略对于优化图数据处理效率至关重要。
摘要由CSDN通过智能技术生成

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 1i<jk 的点对 ( i , j ) (i,j) (i,j) ∣ a i − a j ∣ ≥ M A X |a_i-a_j| \geq MAX aiajMAX ,其中 M A X MAX MAX 为序列种的最大值。

给定长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1n105) 的序列 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(2n105) 个节点的树,每个节点 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) lvrv(1lvrv109)
你需要为每个节点 v v v 赋一个权值 a v a_v av ,使得对于所有边 ( u , v ) (u,v) (u,v) ∣ a u − a v ∣ |a_u-a_v| auav 的总和最大。

题解

设有 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=sonmax(dpson,0+lxlson,dpson,1+lxrson)

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=sonmax(dpson,0+rxlson,dpson,1+rxrson)

跑树形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) ,满足以下条件之一:

  1. 两条线段呈包含关系,即 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
  2. 两条线的长度相同,即 ∣ x i − y i ∣ = ∣ x j − y j ∣ |x_i-y_i|=|x_j-y_j| xiyi=xjyj

求有多少种合法的方案。

题解

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 ip 配对,构成一种合法方案。
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(1ai) 点与 2 n − i + a 2n-i+a 2ni+a 点配对,则中间剩余的节点配对方案若合法,则整体方案也合法。
d p x + = ∑ i = 1 x − 1 d p i dp_x+=\sum_{i=1}^{x-1}{dp_i} dpx+=i=1x1dpi

综上:
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=1x1dpi

参考代码

#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 的孙子。

  1. 若有 x x x 的祖先,则贪心删除该祖先,选择 x x x 节点;
  2. 若有 x x x 的祖先,则不选择 x x x 节点;
  3. 若都没有,则选择 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(2n600) 个城市,编号从 0 0 0 n − 1 n-1 n1
m ( n ≤ m ≤ n 2 ) m(n \leq m \leq n^2) m(nmn2) 座人间大炮,每个城市至少有一个人间大炮。第 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=0minn1cnu,mid+(vmid+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(1n106) ,求合法的无标记啊哈树种数。

题解

d p i dp_i dpi 为满足下列条件的树的数量:

  • 树中存在一个根;
  • 所有边方向相同,都从根节点向下指向叶节点;
  • 根节点最多有 2 2 2 个儿子;
  • 从根节点到叶节点的最长路径长度不超过 i i i

d p i dp_i dpi 时,考虑其根节点情况,有以下几种可能:

  1. 根节点没有儿子,共 1 1 1 种情况;
  2. 根节点有 1 1 1 个儿子,共 d p i − 1 dp_{i-1} dpi1 种情况;
  3. 根节点有 2 2 2 个儿子,两个子树同构有 d p i − 1 dp_{i-1} dpi1 种,异构有 d p i − 1 ∗ ( d p i − 1 − 1 ) 2 \frac{dp_{i-1}*(dp_{i-1}-1)}{2} 2dpi1(dpi11) ,共 d p i − 1 ∗ ( d p i − 1 + 1 ) 2 \frac{dp_{i-1}*(dp_{i-1}+1)}{2} 2dpi1(dpi1+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+dpi1+2dpi1(dpi1+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=dpidpi1 ,表示最长的有向路径长度不超过为 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=dp1idp1i1, 表示最长的有向路径长度恰好为 i i i 的根节点恰好有 2 2 2 个儿子的方案数

考虑最终的啊哈树也是所有边方向相同,都从根节点向下指向叶节点(或都从叶节点指向根节点)且根节点至少有 2 2 2 的儿子的方案,那么有以下情况:

  1. 根节点有 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} 62dpn1(dpn1+1)(dpn1+2)62dpn2(dpn2+1)(dpn2+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} 22dpn1(dpn1+1)22dpn2(dpn2+1)

还有其他情况,可以表示为根节点具有 2 2 2 个儿子且从根节点出发最长路径为 l l l 的树,和另一棵具有 2 2 2 个儿子且从根节点出发最长路径为 n − k − 1 n-k-1 nk1 的树用 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=0n1(dp2ij=0n1idp2n1j)=i=0n1dp2idp1n1i

将上述贡献全部汇总即可。

参考代码

#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(1ik) 1 ≤ b i ≤ n 1 \leq b_i \leq n 1bin 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(1n109) k ( 1 ≤ k ≤ 1 0 5 ) k(1 \leq k \leq 10^5) k(1k105) ,求满足条件的数组 a a a 与数组 b b b 对数。

题解

参考官方题解

考虑以下问题:

  • n n n 个汽车逐个进入停车场。 停车场有 n n n 个编号为 1 , 2 , … , n 1,2,…,n 1,2n 的空位, n n n 个汽车中的第 i i i 个要停在第 a i a_i ai 个或其之后的空位。也就是说,第 i i i 个汽车进入停车场时,它将停在第一个空位 s ( a ≥ a i ) s(a \geq a_i) s(aai) 中。如果所有车都能停入某个空位中,则称数组 a a a 为好数组。

会发现好数组等价于更平等数组,因为在任意一个排序后的好数组中,都满足 a i ≤ i a_i \leq i aii ,即更平等数组排序后的结果。

现在让我们稍微修改一下上面的问题:

  • 一个带有 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(1ain+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(1xn)
具体而言,对于每个 i ( 1 ≤ i ≤ n ) i(1 \leq i \leq n) i(1in) 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)ni 种。

上式计算的数组 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=1kS(k,i)i!Cni(n+1)ni

其中 S ( k , i ) S(k,i) S(k,i) 可以用 FFT 在 O ( k ⋅ l o g k ) O(k \cdot log k) O(klogk) 时间复杂度内求得。

  • 第二类斯特林数具有通项式 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!1i=0k(1)iCki(ki)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!1i=0k(1)iCki(ki)n=i=0k((1)ii!1)((ki)!1(ki)n)
  • a i = ( − 1 ) i ⋅ 1 i ! a_i=(-1)^i \cdot \frac{1}{i!} ai=(1)ii!1 b i = 1 i ! ⋅ i n b_i=\frac{1}{i!} \cdot i^n bi=i!1in ,则 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=0kaibki ,可以用卷积运算。

参考代码

# 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);
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值