• #26 CF960G & CF923E & CF1553I


    Bandit Blues

    题目描述

    点此看题

    解法

    前置知识:长度为 \(n\) 的序列,前缀最大值个数为 \(m\) 的方案数是 \({n\brack m}\),因为它完全等价于把 \(n\) 个数划分成 \(m\) 个圆排列,每个圆排列以其最大值为代表,按最大值从小到大的顺序在原序列上排列。

    回到本题,我们可以枚举 \(n\) 的位置,这样就能把限制拆解为:前 \(i-1\) 个位置前缀最大值是 \(a-1\),后 \(n-i-1\) 个位置后缀最大值是 \(b-1\)(翻转一下就变成了前缀最大值),那么答案是:

    \[\sum_{i=1}^n {i-1\brack a-1}\cdot{n-i-1\brack b-1}\cdot {n-1\choose i-1} \]

    考虑用组合意义化简这个式子,发现这等价于:在长度为 \(n-1\) 的序列中划分出 \(a+b-2\) 个圆排列,再从 \(a+b-2\) 个圆排列中选取 \(a-1\) 个放在左边,\(b-1\) 个放在右边,那么答案是:

    \[{n-1\brack a+b-2}\cdot {a+b-2\choose a-1} \]

    现在的问题变成了求单个第一类斯特林数,回忆其递推式:\({n\brack m}={n-1\brack m-1}+(n-1){n-1\brack m}\),只需要考虑转移路径,就发现 \({n\brack m}\) 可以用这样的生成函数表示出来:

    \[{n\brack m}=[x^m]\sum_{i=0}^{n-1}(x+i) \]

    直接分治 \(\tt NTT\) 即可,时间复杂度 \(O(n\log^2 n)\)

    彩蛋

    在写完这道题之后,我自信地猜测这道题的评分是 \(2900\),结果打开一看真是 \(2900\),感觉 \(\tt CF\) 题做多了都可以自己评难度了~

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 400005;
    #define int long long
    const int MOD = 998244353;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,a,b,fac[M],inv[M],s[M],rev[M];
    void init()
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=1;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    }
    int C(int n,int m)
    {
    	if(n<m || m<0) return 0;
    	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    void NTT(int *a,int len,int op)
    {
    	for(int i=0;i<len;i++)
    	{
    		rev[i]=(rev[i>>1]>>1)|((len/2)*(i&1));
    		if(i<rev[i]) swap(a[i],a[rev[i]]);
    	}
    	for(int s=2;s<=len;s<<=1)
    	{
    		int w=(op==1)?qkpow(3,(MOD-1)/s)
    		:qkpow(3,MOD-1-(MOD-1)/s),t=s/2;
    		for(int i=0;i<len;i+=s)
    			for(int j=0,x=1;j<t;j++,x=x*w%MOD)
    			{
    				int fe=a[i+j],fo=a[i+j+t];
    				a[i+j]=(fe+x*fo)%MOD;
    				a[i+j+t]=(fe-x*fo%MOD+MOD)%MOD;
    			}
    	}
    	if(op==1) return ;
    	int inv=qkpow(len,MOD-2);
    	for(int i=0;i<len;i++) a[i]=a[i]*inv%MOD;
    }
    void solve(int *a,int l,int r)
    {
    	if(l==r) {a[0]=l;a[1]=1;return ;}
    	int mid=(l+r)>>1,len=1,zz=r-l+1;
    	int A[zz<<2]={},B[zz<<2]={};
    	solve(A,l,mid);solve(B,mid+1,r);
    	while(len<=zz) len<<=1;
    	NTT(A,len,1);NTT(B,len,1);
    	for(int i=0;i<len;i++) A[i]=A[i]*B[i]%MOD;
    	NTT(A,len,-1);
    	for(int i=0;i<=zz;i++) a[i]=A[i];
    }
    signed main()
    {
    	n=read();a=read();b=read();init();
    	if(!a || !b || a+b>n+1) {puts("0");return 0;}
    	if(n==1) {puts("1");return 0;}
    	solve(s,0,n-2);
    	printf("%lld\n",s[a+b-2]*C(a+b-2,a-1)%MOD);
    }
    

    Perpetual Subtraction

    题目描述

    点此看题

    解法

    幸好神 \(\tt OUYE\) 给我讲了一个多小时,要不然我根本不可能理解这东西。我尽量写一篇通俗易懂的题解,来帮助像我一样在线代方面没有任何基础的人吧。

    \(\tt Warning\):请确保你先了解特征值、特征向量、基向量等基础概念再来阅读本文。

    首先重点介绍一下对角化,对角化的核心是基变换。基变换的定义是:\(n\times n\) 矩阵 \(A\) 看成 \(n\) 个向量,变换这些向量所对应的基向量,得到新的矩阵 \(A'\),这个过程称为基变换

    原来的基是 \((1,0...0),(0,1...0),(0,0...1)\),它们对应单位矩阵 \(I\);现在我们把基变成 \(E^{(1)},E^{(2)}...E^{(n)}\),对应某个矩阵 \(E\);那么原来的向量 \(v\),在改变了基向量之后,就变成了 \(E^{-1}v\)

    设矩阵 \(A\) 在改变基向量之后会变成 \(A'\),有这样一个等式:\(Av=EA'v'\)\(A'v'\) 的意思就是变换之后再做乘法,右乘上 \(E\) 之后就可以得到原来的结果 \(Av\),根据这个等式进一步推导:

    \[Av=EA'E^{-1}v\rightarrow A=EA'E^{-1}\rightarrow E^{-1}AE=A' \]

    研究清楚基变换以后,对角化就是选取矩阵 \(A\)\(n\) 个特征向量组成 \(E\)那么 \(AE\) 的结果势必是对于 \(E\) 每一行的拉伸(即把 \(E\) 的每一行分别乘上一个数),所以 \(E^{-1}AE\) 的结果是对于 \(I\) 每一行的拉伸,这说明 \(A'\) 是一个对角矩阵。

    总结一句:对角化的原理就是,通过选取特征向量为基向量,把 \(A\) 基变换成一个对角矩阵 \(A'\)


    本题就是对角化的经典应用,首先写出用来矩阵加速的矩阵 \(A\)

    \[A=\left[\begin{matrix} 1 & \frac{1}{2} & ... & \frac{1}{n+1}\\ 0 & \frac{1}{2} & ... & \frac{1}{n+1}\\ ... & ... & ... & ...\\ 0 & 0 & ... & \frac{1}{n+1}\\ \end{matrix}\right] \]

    设输入的列向量是 \(p\),我们要求 \(A^mp\),核心就是求解 \(A^m\),首先改一个形式:

    \[A^m=(EE^{-1}AEE^{-1})^m=EE^{-1}AEE^{-1}EE^{-1}AEE^{-1}=E(E^{-1}AE)^mE^{-1} \]

    其中 \(E^{-1}AE\) 就是 \(A'\),对角线上的值是 \(A\) 的特征值,因为 \(det(A-\lambda I)=0\),那么 \(A'\) 就是 \(A\) 对角线上的值:

    \[A’=\left[\begin{matrix} 1 & 0 & ... & 0\\ 0 & \frac{1}{2} & ... & 0\\ ... & ... & ... & ...\\ 0 & 0 & ... & \frac{1}{n+1}\\ \end{matrix}\right] \]

    因为 \((A-\lambda I)\vec x=0\),可以手算几个特征向量 \(\vec x\) 找规律,那么可以得到 \(E\)

    \[E=\left[\begin{matrix} (-1)^0{0\choose 0} & (-1)^1{1\choose 0} & ... & (-1)^n{n\choose 0}\\ 0 & (-1)^2{1\choose 1} & ... & (-1)^{n+1}{n\choose 1}\\ ... & ... & ... & ...\\ 0 & 0 & ... & (-1)^{2n}{n\choose n}\\ \end{matrix}\right] \]

    可以通过矩阵求逆得到 \(E^{-1}\)(如果去解方程,本质是二项式反演):

    \[E^{-1}=\left[\begin{matrix} {0\choose 0} & {1\choose 0} & ... & {n\choose 0}\\ 0 & {1\choose 1} & ... & {n\choose 1}\\ ... & ... & ... & ...\\ 0 & 0 & ... & {n\choose n}\\ \end{matrix}\right] \]

    形式化地:\(E(i,j)=(-1)^{i+j}{i\choose j}\)\(E^{-1}(i,j)={i\choose j}\)

    最后我们需要计算 \(E(E^{-1}AE)^mE^{-1}p\),从右往左一个个乘上去即可,把式子写出来发现是差卷积的形式,所以一发 \(\tt NTT\) 解决问题,时间复杂度 \(O(n\log n)\)

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 400005;
    #define int long long
    const int MOD = 998244353;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,fac[M],inv[M],f[M],g[M],rev[M];
    void init()
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=1;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    }
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    void NTT(int *a,int len,int op)
    {
    	for(int i=0;i<len;i++)
    	{
    		rev[i]=(rev[i>>1]>>1)|((len/2)*(i&1));
    		if(i<rev[i]) swap(a[i],a[rev[i]]);
    	}
    	for(int s=2;s<=len;s<<=1)
    	{
    		int w=(op==1)?qkpow(3,(MOD-1)/s)
    		:qkpow(3,MOD-1-(MOD-1)/s),t=s/2;
    		for(int i=0;i<len;i+=s)
    			for(int j=0,x=1;j<t;j++,x=x*w%MOD)
    			{
    				int fe=a[i+j],fo=a[i+j+t];
    				a[i+j]=(fe+x*fo)%MOD;
    				a[i+j+t]=(fe-x*fo%MOD+MOD)%MOD;
    			}
    	}
    	if(op==1) return ;
    	int inv=qkpow(len,MOD-2);
    	for(int i=0;i<len;i++) a[i]=a[i]*inv%MOD;
    }
    signed main()
    {
    	n=read()+1;m=read();init();
    	//
    	for(int i=0;i<n;i++) f[i]=read()*fac[i]%MOD;
    	for(int i=0;i<n;i++) g[i]=inv[i];
    	reverse(f,f+n);
    	int len=1;while(len<=2*n) len<<=1;
    	NTT(f,len,1);NTT(g,len,1);
    	for(int i=0;i<len;i++) f[i]=f[i]*g[i]%MOD;
    	NTT(f,len,-1);
    	for(int i=n;i<len;i++) f[i]=g[i]=0;
    	reverse(f,f+n);
    	//
    	for(int i=0;i<n;i++)
    	{
    		f[i]=f[i]*qkpow(qkpow(i+1,MOD-2),m)%MOD;
    		g[i]=(i&1)?MOD-inv[i]:inv[i];
    	}
    	reverse(f,f+n);
    	NTT(f,len,1);NTT(g,len,1);
    	for(int i=0;i<len;i++) f[i]=f[i]*g[i]%MOD;
    	NTT(f,len,-1);
    	for(int i=n;i<len;i++) f[i]=g[i]=0;
    	reverse(f,f+n);
    	//
    	for(int i=0;i<n;i++)
    		printf("%lld ",f[i]*inv[i]%MOD);
    }
    

    stairs

    题目描述

    点此看题

    解法

    本来看不懂这题题解直接就扔了,结果 \(\tt imzzy\) 硬是给我讲懂了,张教主 \(\tt yyds\)

    首先简化问题,我们考虑数组 \(a\) 的一个极长相同的连续段 \([l,r]\),需要满足 \((r-l+1)\bmod a_l=0\) 才有解。并且通过这个可以得到一个长为 \(m\) 的数组 \(b\),其中 \(b_i\) 表示第 \(i\) 段连续段的长度,发现计数只需要知道:

    • 每一个连续段是从大到小,还是从小到大。特别地,如果 \(b_i=1\) 那么只有一种方案。
    • 连续段之间的相对关系,可以以连续段中最小的数为代表,用一个 \(1,2...m\) 的排列来描述这个关系。

    计数有一个很烦的限制:相邻两个连续段不能拼接成一个大的连续段。可以容斥,设最后的连续段数量是 \(i\),那么容斥系数是 \((-1)^{m-i}\),设 \(f_i\) 表示最后剩下 \(i\) 个连续段的方案数,答案可以写成:

    \[\sum_{i=1}^m(-1)^{m-i}\cdot f_i\cdot i! \]

    \(f_i\) 的计算是平凡的,因为根据我们的构造方案,两个 \(b\) 数组中的相邻连续段其实可以随意拼接。计算可以考虑分治 \(\tt NTT\),为了解决长度为 \(1\) 的连续段只有 \(1\) 种方案的特殊情况,我们记 \(dp_{a,b,x}\) 表示最左边的大连续段长度 =1/>1、最右边的大连续段长度 =1/>1、大连续段的总数是 \(x\),的方案数是多少。

    暴力合并左右部分,内层是一个卷积的形式,代码实现没有任何细节,时间复杂度 \(O(n\log ^2n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 400005;
    #define int long long
    const int MOD = 998244353;
    const int inv2 = (MOD+1)/2;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,a[M],b[M],rev[M],A[M],B[M],fac[M];
    struct node
    {
    	vector<int> v[2][2];
    	void resize(int x)
    	{
    		for(int i=0;i<2;i++) for(int j=0;j<2;j++)
    			v[i][j].resize(x+1);
    	}
    	vector<int>* operator [] (int x) {return v[x];}
    };
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    void NTT(int *a,int len,int op)
    {
    	for(int i=0;i<len;i++)
    	{
    		rev[i]=(rev[i>>1]>>1)|((len/2)*(i&1));
    		if(i<rev[i]) swap(a[i],a[rev[i]]);
    	}
    	for(int s=2;s<=len;s<<=1)
    	{
    		int w=(op==1)?qkpow(3,(MOD-1)/s)
    		:qkpow(3,MOD-1-(MOD-1)/s),t=s/2;
    		for(int i=0;i<len;i+=s)
    			for(int j=0,x=1;j<t;j++,x=x*w%MOD)
    			{
    				int fe=a[i+j],fo=a[i+j+t];
    				a[i+j]=(fe+x*fo)%MOD;
    				a[i+j+t]=(fe-x*fo%MOD+MOD)%MOD;
    			}
    	}
    	if(op==1) return ;
    	int inv=qkpow(len,MOD-2);
    	for(int i=0;i<len;i++) a[i]=a[i]*inv%MOD;
    }
    void add(int &x,int y) {x=(x+y)%MOD;}
    node solve(int l,int r)
    {
    	node s;s.resize(r-l+1);
    	if(r-l<=2)
    	{
    		if(r-l==1)
    		{
    			s[b[l]][b[r]][2]=(b[l]+1)*(b[r]+1);
    			s[1][1][1]=2;
    		}
    		if(r-l==2)
    		{
    			s[b[l]][b[r]][3]=(b[l]+1)*(b[l+1]+1)*(b[r]+1);
    			s[b[l]][1][2]=2*(b[l]+1);s[1][b[r]][2]+=2*(b[r]+1);
    			s[1][1][1]=2;
    		}
    		return s;
    	}
    	int mid=(l+r)>>1,len=1;
    	node X=solve(l,mid),Y=solve(mid+1,r);
    	while(len<=r-l+2) len<<=1;
    	for(int i=0;i<2;i++) for(int j=0;j<2;j++)
    	for(int x=0;x<2;x++) for(int y=0;y<2;y++)
    	{
    		vector<int> t;t.resize(r-l+2);
    		for(int p=0;p<len;p++) A[p]=B[p]=0;
    		for(int p=0;p<=mid-l+1;p++) A[p]=X[i][x][p];
    		for(int p=0;p<=r-mid;p++) B[p]=Y[y][j][p];
    		NTT(A,len,1);NTT(B,len,1);
    		for(int p=0;p<len;p++) A[p]=A[p]*B[p]%MOD;
    		NTT(A,len,-1);
    		for(int p=0;p<=r-l+1;p++) t[p]=A[p];
    		//
    		for(int p=0;p<=r-l+1;p++)
    			add(s[i][j][p],t[p]);
    		for(int p=0;p<=r-l;p++)
    		{
    			if(x+y==2) add(s[i][j][p],t[p+1]*inv2);
    			if(x+y==1) add(s[i][j][p],t[p+1]);
    			if(x+y==0) add(s[i][j][p],t[p+1]*2);
    		}
    	}
    	return s;
    }
    signed main()
    {
    	n=read();fac[0]=1;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    	for(int i=1;i<=n;i++) a[i]=read();
    	for(int l=1,r=1;l<=n;l=r)
    	{
    		for(r=l;a[l]==a[r];r++);
    		if((r-l)%a[l]) {puts("0");return 0;}
    		for(int j=1;j<=(r-l)/a[l];j++)
    			b[++m]=(a[l]>1);
    	}
    	if(m==1) {puts("1");return 0;}
    	node r=solve(1,m);int ans=0;
    	for(int i=1;i<=m;i++)
    	{
    		int t=0;
    		for(int a=0;a<2;a++) for(int b=0;b<2;b++)
    			add(t,r[a][b][i]);
    		if((m-i)&1) add(ans,MOD-t*fac[i]%MOD);
    		else add(ans,t*fac[i]);
    	}
    	printf("%lld\n",ans);
    }
    
  • 相关阅读:
    文本框textarea根据输入内容自适应高度 和输入中文和数字换行解决方法
    switch 和 if...else if 的区别
    vue中 eCharts 自适应容器
    AJAX跨域POST发送json时,会先发送一个OPTIONS预请求
    获取鼠标和元素的坐标点
    vue2 数据交互 vue-resource
    网站代码初始化
    vue2 关于ref
    事件监听
    cookie 的 写入,读取, 删除
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16377966.html
Copyright © 2020-2023  润新知