2021 集训队互测 题解 (Remake)

2021集训队互测 题解

2023 "Remake" Version

指 lemonlime 比赛名是 Remake(确信)

由于个人水平问题,不保证会做题,只写我会的题目。

T1(12/0/0) T2(12/0/0) T3(12/0/0)
D1T1 D1T2 D1T3
D2T1 D2T2 D2T3
D3T1 D3T2 D3T3
D4T1 D4T2 D4T3
D5T1 D5T2 D5T3
D6T1 D6T2 D6T3
D7T1 D7T2 D7T3
D8T1 D8T2 D8T3
D9T1 D9T2 D9T3
D10T1 D10T2 D10T3
D11T1 D11T2 D11T3
D12T1 D12T2 D12T3
D1 T1 #4 愚蠢的在线法官
Problem

给定一棵 nn 个点的有根树,11 为根。点有点权 viv_i

现在给出 kk 个点 c1,,ckc_1,\cdots,c_k,考虑一个 k×kk\times k 的矩阵 AA,其中 Ai,j=vLCA(ci,cj)A_{i,j}=v_{LCA(c_i,c_j)}

求出 AA 的行列式值,答案模 998244353998244353

n,k5×105,vi0n,k\leq 5\times 10^5,v_i\neq 0

2s,512MB2s,512MB

Sol

如果存在相同的 cic_i,则矩阵存在两行相同,此时答案显然为 00。只需要考虑两两不同的情况。

考虑行列式的置换意义,即考虑每个排列 pp,一个 pp 的贡献为 iAi,pi\prod_i A_{i,p_i},系数为 (1)n+c(-1)^{n+c},其中 ccpp 的环数量。

考虑根处的情况。如果 cic_icpic_{p_i} 的路径经过根,则这一项值为 c1c_1,与两侧具体的点无关。考虑先决定每个子树内置换的连接情况(一些点可以决定经过根连接到其他部分),然后考虑根上不同子树匹配的过程。设有 ll 个子树,第 ii 个子树中选择有 aia_i 条置换路径(p1p2pk\to p_1\to p_2\to\cdots\to p_k\to)从其他部分连进来再连出去(其余部分全部在子树内形成置换)。考虑不同子树间的路径拼接成环的情况,可以发现此时的形式与行列式等价,即如下矩阵的行列式:

[Oa1c1c1c1c1Oa2c1c1c1c1c1c1c1c1Oal]\begin{bmatrix}O_{a_1}&c_1&c_1&c_1\\ c_1&O_{a_2}&c_1&c_1\\ c_1&c_1&\ddots&c_1\\ c_1&c_1&c_1&O_{a_l} \end{bmatrix}

(如果给定点中包含根,则还需要再加一行一列 c1c_1

这表示每个子树内的路径不能再连接自己子树内的其它路径,但连接其余路径都是等价的。

此时可以发现,如果 ai>1a_i>1,则矩阵存在两行相同,从而矩阵行列式为 00。因此只有 ai=0,1a_i=0,1 的情况考虑根处匹配后权值非零,即对于每个根的子树,只需要考虑子树内连接置换环,并向外连出 0,10,1 条路径的情况。

00 条路径的情况变为了根处的问题,对于连出 11 条路径的情况,考虑新建一条虚拟路径,将连出的一条路径与虚拟路径拼接形成环,此时得到如下行列式的组合意义:

[011111Oa1c1c1c11c1Oa2c1c11c1c1c11c1c1c1Oal]\begin{bmatrix} 0&1&1&1&1\\ 1&O_{a_1}&c_1&c_1&c_1\\ 1&c_1&O_{a_2}&c_1&c_1\\ 1&c_1&c_1&\ddots&c_1\\ 1&c_1&c_1&c_1&O_{a_l} \end{bmatrix}

因此之前的结论仍然成立:对于根的一个儿子节点,其每一个子树内仍然只需要考虑置换环中向外连接 0,10,1 条路径的方案数,从而这对于任意点都成立。

因此设 fuf_u 表示 uu 子树内连接置换环,所有方案权值和(或者只保留 uu 子树内部分的答案),gug_u 表示 uu 子树内连接置换环,但有一条路径连向子树外的方案数(或者 uu 子树内部分加上上一个矩阵的第一行第一列后的行列式)。

然后考虑自下向上转移。对于一个点,设其有 dd 个子树选择了 gg,剩余子树选择了 ff,则有 dd 段路径需要连接。考虑新的 ff,此时考虑 uu 是否被选中,可以得到两种不同矩阵:

det(cu(1δi,j))d×d=cuddet[011110111111110]=cud(1)d1(d1)(cu(1δi,j[i>1]))(d+1)×(d+1)=cud+1det[111110111111110]=cud+1(1)d\det (c_u*(1-\delta_{i,j}))_{d\times d}=c_u^d*\det\begin{bmatrix}0&1&1&1\\1&0&1&1\\1&1&\ddots&1\\1&1&1&0\end{bmatrix}=c_u^d*(-1)^{d-1}*(d-1)\\ (c_u*(1-\delta_{i,j}*[i>1]))_{(d+1)\times (d+1)}=c_u^{d+1}*\det\begin{bmatrix}1&1&1&1\\1&0&1&1\\1&1&\ddots&1\\1&1&1&0\end{bmatrix}=c_u^{d+1}*(-1)^{d}\\

(其中最后两个矩阵的求行列式方式:第一个可以通过将所有行加到第一行变为第二个,第二个可以通过用第一行去减所有行解决)

而求 gg 类似于加入一段路径,唯一区别为权值是 11 而不是 cuc_u,此时可以将第一行第一列同时乘 cuc_u,再变为上述形式。这相当于将 dd 加一后的上述形式,但结果需要乘 cu2c_u^{-2}

考虑组合意义,上述系数相当于每有一个子树选择 gg 则乘上 cu-c_u,乘 d1d-1 则相当于再选一个选择 gg 的子树求方案系数和,然后减去不选时的答案。因此设 dpi,0/1dp_{i,0/1} 表示考虑了前 ii 个子树,是否选择了一个选择 gg 子树时的权值。此时 dp0=cud(1)d,dp1=cud(1)dddp_0=\sum c_u^d(-1)^d, dp_1=\sum c_u^d(-1)^d*d,最后两种情况下都可以通过 dp0/1dp_{0/1} 得到 f,gf,g

在第一种情况(uu 没有被选中)时,直接求 gg 需要乘一个 cu1c_u^{-1},可以通过在 dpdp 时选择选 gg 的子树(dp0dp1dp_0\to dp_1)时不乘 cuc_u 系数以规避该逆元(此时系数正好为 cud1d(1)dc_u^{d-1}*d*(-1)^d虽然这里也可以直接求逆

复杂度 O(n)O(n)

一些不正经的做法:直接模拟消元的过程,由于子树外部分值相同,可以只记录外面需要减多少。但该做法遇到对角元消成 00(某个 f=0f=0)时会遇到问题,但输入权值随机因此没卡掉。

Code
#include<cstdio>
using namespace std;
#define N 500059
#define mod 998244353
int n,k,a,b,v[N],is[N],head[N],cnt;
struct edge{int t,next;}ed[N*2];
void adde(int f,int t)
{
	ed[++cnt]=(edge){t,head[f]};head[f]=cnt;
	ed[++cnt]=(edge){f,head[t]};head[t]=cnt;
}
int f[N],g[N];
void dfs(int u,int fa)
{
	int c0=1,c1=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		dfs(ed[i].t,u);
		int r0=(1ll*c0*f[ed[i].t]+1ll*c0*g[ed[i].t]%mod*(mod-v[u]))%mod,r1=(1ll*c1*f[ed[i].t]+1ll*(c0+1ll*c1*v[u])%mod*g[ed[i].t]%mod*(mod-1))%mod;
		c0=r0,c1=r1;
	}
	if(is[u])f[u]=1ll*c0*v[u]%mod,g[u]=c0;
	else f[u]=(mod+c0-1ll*v[u]*c1%mod)%mod,g[u]=(mod-c1)%mod;
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=k;i++)
	{
		scanf("%d",&a);
		if(is[a]){printf("0\n");return 0;}
		is[a]=1;
	}
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs(1,0);
	printf("%d\n",f[1]);
}
D1 T2 #25 这是一道集训队胡策题
Problem

给定一个 n×nn\times n0101 矩阵 cc

求出有多少组序列 a,ba,b,满足 a,ba,b 长度为 nn,且 i,j\forall i,jci,j=aic_{i,j}=a_ici,j=bjc_{i,j}=b_j 至少有一者成立。答案模 998244353998244353

n5000n\leq 5000

2s,1024MB2s,1024MB

partial: n300n\leq 300

Sol

*由于原数据强度过于离谱,不保证做法正确性(虽然编了个证明)

考虑枚举 a1a_1,则对于第一行的一个元素,如果 c1,ja1c_{1,j}\neq a_1,那么一定有 bj=ci,jb_j=c_{i,j}。再对这些列进行考虑,重复这一过程直到无法确定更多行列的取值。此时固定了一些行列的值,它们无法固定更多行列的值说明它们与剩余行列相交的部分都被它们的值满足了,因此剩余行列的取值问题可以看成只保留这些行这些列的一个子问题。

对于每一列,只有当 a1=c1,ja_1=c_{1,j} 时这一列才可能不被确定。因此对于 aia_i 的不同取值,得到的子问题的列数量之和不超过 nn。因此每一次划分后子问题的 a×b|a|\times |b| 之和不会变大,且划分轮数不超过 nn,从而直接枚举一行取值并dfs,复杂度不超过 O(n3)O(n^3)。但该做法无法通过。虽然使用神必技巧可以卡过去

在接下来的讨论过程中,可以发现全 00 或全 11 的行列多次成为特殊情况,因此考虑先去除这一情况。

不妨设第一行为全 00。如果 a1=0a_1=0,则直接变为删去这一行的子问题。如果 a1=1a_1=1,则所有列都有 bj=0b_j=0,此时再考虑所有行,如果一行为全 00,则 aia_i 有两种取值,否则 aia_i 只能是 11

因此将所有全 00 行一起考虑。假设有 kk 个全 00 行。如果这些行全部让 ai=0a_i=0,则变为删去这些行的问题。否则,如果至少有一行满足 ai=1a_i=1,则所有 bj=0b_j=0,剩余行都不是非零,因而剩余行都有 ai=1a_i=1,此时方案已经被确定,这部分方案数为 2k12^k-1,因此答案为 2k12^k-1 加上删去这 kk 行后子问题的答案。

因此可以使用这一方式,重复去除全 0/10/1 行列的过程,接下来只考虑每一行列都有两种权值的情况。

然而原数据弱到这样删不能删时直接 return 2 都过了,强度弱到离谱(xpp出来背锅)

反例:

answer:6
------------
8
10000000
01000000
11100000
11010000
11111000
11110100
11111010
11111111

考虑上述做法中的性质。由于权值只有 {0,1}\{0,1\},因此如果钦定了一行为 00,则固定列的权值时,只可能固定为 11,接下来又只会将行固定为 00,进而最后的确定值情况一定是若干行被确定为 00,列被确定为 11

考虑重排确定的行列,可以将矩阵变为如下形式:

[0AB1]\begin{bmatrix} 0&A\\ B&1 \end{bmatrix}

其中钦定的为 AA 部分的行列。在删去全 0/10/1 行列后,不存在行列中一个划分了一半另外一个不划分的情况(即 A,BA,B 非空),可以避免大量讨论。

可以发现前面的行中 11 的数量一定小于等于后面的行中 11 的数量,列同理。同时对于两个 11 数量相同的行,如果它们相同则不需要考虑,如果不同则它们不可能被划分到上图中两个不同的部分。因此存在如下性质:

将行列分别按照 11 的数量从小到大排序(相同任意排序),则上述形式中,行被划分成的两部分在排序中一定对应一段前缀和一段后缀,列同理。

因此排序后,容易找到这样的形式:考虑枚举划分后上半部分行数,记录每一行第一个 11 的位置,即可得到列最多能划分到哪里,然后即可判断是否存在这一形式。这里对于列的处理可以从最右一列开始,随着行向下走向左扩展合法的列,然后判断。这样的处理复杂度线性于找到的 AA 的行列数量之和。

如果不存在非满的这样的形式,则从钦定任意一行开始,只能将所有行列全部决定(钦定 00 对应从 AA 开始,钦定 11 对应从 BB 开始,不存在非满形式说明不能中途停止),因此只有两种合法的方案:a=0,b=1a=0,b=1 或者反过来。此时可以直接得到结果。

否则,考虑找到第一个满足条件的形式(行数最少)。此时考虑 AA 部分行列,这部分内部不再存在一个满足条件的形式(否则与找到的这一形式的最小性矛盾),因此考虑 AA 部分内部,这部分只存在两组解:行全 00 列全 11 或者反过来。此时:

  1. 如果行全 00 列全 11,则变为只剩 BB 部分的子问题。
  2. 如果行全 11 列全 00,则剩余列只能是 11,剩余行只能是 00,此时有唯一解。

因此这种情况答案为 BB 部分答案加一。从而完成了整个过程。

复杂度 O(n2)O(n^2),其中在行列排序并求出每一行每一列第一个 11 与最后一个 00 之后,剩余部分为线性(因为选择 AA 的复杂度只与 AA 的边长有关)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 5050
#define mod 998244353
int n,ct[N],id[N],p2[N];
char s[N][N],t[N][N];
bool cmp(int a,int b){return ct[a]<ct[b];}
int l1x[N],r0x[N],l1y[N],r0y[N];
int solve(int lx,int rx,int ly,int ry)
{
	if(lx>rx)return p2[ry-ly+1];
	if(ly>ry)return p2[rx-lx+1];
	if(l1x[lx]>ry)
	{
		int tp=lx;while(tp<=rx&&l1x[tp]>ry)tp++;
		return (p2[tp-lx]-1+solve(tp,rx,ly,ry))%mod;
	}
	if(r0x[rx]<ly)
	{
		int tp=rx;while(tp>=lx&&r0x[tp]<ly)tp--;
		return (p2[rx-tp]-1+solve(lx,tp,ly,ry))%mod;
	}
	if(l1y[ly]>rx)
	{
		int tp=ly;while(tp<=ry&&l1y[tp]>rx)tp++;
		return (p2[tp-ly]-1+solve(lx,rx,tp,ry))%mod;
	}
	if(r0y[ry]<lx)
	{
		int tp=ry;while(tp>=ly&&r0y[tp]<lx)tp--;
		return (p2[ry-tp]-1+solve(lx,rx,ly,tp))%mod;
	}
	int nw=ry,mn=ry;
	for(int i=lx;i<rx;i++)
	{
		mn=min(mn,l1x[i]-1);
		while(nw>=ly&&r0y[nw]<=i)nw--;
		if(nw<=mn)return (1+solve(i+1,rx,ly,mn))%mod;
	}
	return 2;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++)
	{
		ct[i]=0;id[i]=i;
		for(int j=1;j<=n;j++)ct[i]+=s[i][j]=='1';
	}
	sort(id+1,id+n+1,cmp);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)t[i][j]=s[id[i]][j];
	for(int i=1;i<=n;i++)
	{
		ct[i]=0;id[i]=i;
		for(int j=1;j<=n;j++)ct[i]+=t[j][i]=='1';
	}
	sort(id+1,id+n+1,cmp);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)s[i][j]=t[i][id[j]];
	for(int i=1;i<=n;i++)l1x[i]=l1y[i]=n+1;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(s[i][j]=='0')r0x[i]=j,r0y[j]=i;
	for(int i=n;i>=1;i--)for(int j=n;j>=1;j--)if(s[i][j]=='1')l1x[i]=j,l1y[j]=i;
	p2[0]=1;for(int i=1;i<=n;i++)p2[i]=2*p2[i-1]%mod;
	printf("%d\n",solve(1,n,1,n));
}
D1 T3 #42 树上的孤独
Problem

给定 nn 个点的有根树 T1T_1mm 个点的有根树 T2T_2,两棵树均以 11 为根。每个点都有颜色。

qq 次操作,每次为如下两者之一:

  1. 修改 T1T_1 中某个点的颜色。
  2. 查询 T1T_1u1u_1 子树内距离 u1u_1 不超过 d1d_1 的所有点,加上 T2T_2u2u_2 子树内距离 u2u_2 不超过 d2d_2 的所有点中的颜色种类数。部分强制在线,d1,d2d_1,d_2 需要异或上一次询问的结果。

n20,m2×105,q106n\leq 20,m\leq 2\times 10^5,q\leq 10^6

3s,1024MB3s,1024MB

partial: q105q\leq 10^5

Sol

首先考虑没有第一棵树的情况,考虑对于每个点,对于每种颜色维护该颜色在子树中出现的最浅深度。然后再维护每种最浅深度维护的次数。一次询问即为最浅深度小于等于某个值的颜色数量和。

考虑从下往上做,那么维护最浅深度的部分需要支持合并,在合并时找出所有在两侧同时出现的颜色,以及单点修改(加入当前点),维护次数需要支持合并,单点修改(合并两侧颜色时减一),前缀求和(处理询问)。

一种简单的实现方式为使用线段树合并实现两部分。对于次数部分,由于询问强制在线,可以使用可持久化线段树合并:合并时不使用两侧的点,而是新建点,修改同样新建点,复杂度不变。

这样第二棵树部分复杂度为 O(mlogm)O(m\log m)

然后考虑加入第一棵树。注意到第一棵树每次询问只会加入 O(n)O(n) 个点,询问相当于在原询问上加入 O(n)O(n) 种颜色(注意去重)。

直接的方式是对于每种颜色,求出它在 u2u_2 子树内距离 u2u_2 最近的一次出现的深度,相当于在某一点的最浅深度中查询一个值。此时可以对这部分同样使用可持久化线段树合并,但这样查询为 O(logm)O(\log m),复杂度为 O((m+nq)logm)O((m+nq)\log m),难以接受。虽然将线段树改成多叉平衡修改/询问复杂度,卡卡常还是能过

考虑做到 O(1)O(1) 查询,但这难以在线做到:有 O(m2)O(m^2) 个可能的询问。

因此考虑离线处理,注意到 T1T_1u2u_2 都没有强制在线,因此考虑对于每次询问,离线求出对于 T1T_1 中的每种颜色求出其在 u2u_2 子树内出现的最浅深度。

那么可以预先处理出每个 u2u_2 上的所有询问(求出 T1T_1 每个时刻的颜色情况),在合并过程中进行处理。但之前的线段树合并做法在合并时询问复杂度也是 O(logm)O(\log m)

因此考虑询问更快的方式。如果直接使用数组维护每个点处的最浅深度情况,则询问可以 O(1)O(1),但空间难以接受,同时难以合并。

考虑轻重链剖分优化。使用数组记录每个点的最浅深度情况,同时记录子树内出现过的颜色种类。每个点先处理其重儿子,从重儿子处继承结果,然后依次处理轻儿子,对轻儿子使用新一层的数组处理,然后向上合并,枚举轻儿子子树内出现过的每种颜色。

这样层数为 O(logm)O(\log m),因此空间复杂度为 O(mlogm)O(m\log m),合并复杂度也为 O(mlogm)O(m\log m)。同时,使用该方式即可离线 O(1)O(1) 使用数组回答单组询问。

使用该方式,则复杂度为 O(nq+mlogm)O(nq+m\log m)

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 200500
#define M 22
#define K 1000500
int n,m,q,a,b,ls,head[N],cnt,dis[M][M],ist[N];
struct edge{int t,next;}ed[N*2];
void adde(int f,int t)
{
	ed[++cnt]=(edge){t,head[f]};head[f]=cnt;
	ed[++cnt]=(edge){f,head[t]};head[t]=cnt;
}
int cl[K][M],qu[K][5],rs[K][M],cl2[N];
vector<int> qi[N];
void dfs0(int u,int fa)
{
	for(int i=1;i<=n;i++)dis[u][i]=-1;
	dis[u][u]=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		dfs0(ed[i].t,u);
		for(int j=1;j<=n;j++)if(dis[ed[i].t][j]!=-1)
		dis[u][j]=dis[ed[i].t][j]+1;
	}
}
int sz[N],sn[N];
void dfs1(int u,int fa)
{
	sz[u]=1;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		dfs1(ed[i].t,u);sz[u]+=sz[ed[i].t];
		if(sz[ed[i].t]>sz[sn[u]])sn[u]=ed[i].t;
	}
}
int rt[N],ch[N*59][2],su[N*59],ct;
int rv[M][N],ci[M],si[M][N],dep[N];
int ins(int x,int l,int r,int s,int v)
{
	int st=++ct;
	ch[st][0]=ch[x][0];ch[st][1]=ch[x][1];su[st]=su[x]+v;
	if(l==r)return st;
	int mid=(l+r)>>1;
	if(mid>=s)ch[st][0]=ins(ch[x][0],l,mid,s,v);
	else ch[st][1]=ins(ch[x][1],mid+1,r,s,v);
	return st;
}
int merge(int x,int y,int l,int r)
{
	if(!x||!y)return x+y;
	int st=++ct;su[st]=su[x]+su[y];
	if(l==r)return st;
	int mid=(l+r)>>1;
	ch[st][0]=merge(ch[x][0],ch[y][0],l,mid);
	ch[st][1]=merge(ch[x][1],ch[y][1],mid+1,r);
	return st;
}
int query(int x,int l,int r,int r1)
{
	if(!x)return 0;
	if(l>r1)return 0;
	if(r<=r1)return su[x];
	int mid=(l+r)>>1;
	return query(ch[x][0],l,mid,r1)+query(ch[x][1],mid+1,r,r1);
}
void dfs2(int u,int fa,int ds)
{
	dep[u]=dep[fa]+1;
	if(sn[u])
	{
		dfs2(sn[u],u,ds);
		rt[u]=rt[sn[u]];
	}
	else rt[u]=++ct;
	if(!rv[ds][cl2[u]])si[ds][ci[ds]+1]=cl2[u],ci[ds]++;
	else rt[u]=ins(rt[u],1,m,rv[ds][cl2[u]],-1);
	rv[ds][cl2[u]]=dep[u],rt[u]=ins(rt[u],1,m,dep[u],1);
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])
	{
		dfs2(ed[i].t,u,ds+1);
		rt[u]=merge(rt[u],rt[ed[i].t],1,m);
		for(int j=1;j<=ci[ds+1];j++)
		{
			int c1=si[ds+1][j],di=rv[ds+1][c1];
			rv[ds+1][c1]=0;
			if(rv[ds][c1]==0)si[ds][ci[ds]+1]=c1,ci[ds]++;
			else if(rv[ds][c1]<=di){rt[u]=ins(rt[u],1,m,di,-1);continue;}
			else rt[u]=ins(rt[u],1,m,rv[ds][c1],-1);
			rv[ds][c1]=di;
		}
		ci[ds+1]=0;
	}
	for(int i=0;i<qi[u].size();i++)
	{
		int x=qi[u][i];
		for(int j=1;j<=n;j++)rs[x][j]=rv[ds][cl[x][j]];
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs0(1,0);
	for(int i=1;i<=n;i++)head[i]=0;cnt=0;
	for(int i=1;i<m;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs1(1,0);
	for(int i=1;i<=n;i++)scanf("%d",&cl[0][i]);
	for(int i=1;i<=m;i++)scanf("%d",&cl2[i]);
	for(int i=1;i<=q;i++)
	{
		scanf("%d",&qu[i][0]);
		for(int j=1;j<=n;j++)cl[i][j]=cl[i-1][j];
		if(qu[i][0]==1)scanf("%d%d%d%d",&qu[i][1],&qu[i][2],&qu[i][3],&qu[i][4]),qi[qu[i][2]].push_back(i);
		else scanf("%d%d",&qu[i][1],&qu[i][2]),cl[i][qu[i][1]]=qu[i][2];
	}
	dfs2(1,0,1);
	for(int i=1;i<=q;i++)if(qu[i][0]==1)
	{
		qu[i][3]^=ls;qu[i][4]^=ls;
		int as=query(rt[qu[i][2]],1,m,dep[qu[i][2]]+qu[i][4]);
		for(int j=1;j<=n;j++)
		if(dis[qu[i][1]][j]!=-1&&dis[qu[i][1]][j]<=qu[i][3]&&(rs[i][j]>dep[qu[i][2]]+qu[i][4]||!rs[i][j])&&!ist[cl[i][j]])as++,ist[cl[i][j]]=1;
		for(int j=1;j<=n;j++)ist[cl[i][j]]=0;
		printf("%d\n",ls=as);
	}
}
D2 T1 #6 序列
Problem

给定 n,mn,m,你需要构造一个长度为 nn 的正整数序列 aa,满足如下 mm 条限制:

axi,ayi,azia_{x_i},a_{y_i},a_{z_i} 的中位数为 viv_i

构造任意一组方案或输出无解。

n,m105n,m\leq 10^5

1s,512MB1s,512MB

Sol

考虑如何描述中位数的限制。三个数的中位数为 viv_i 等价于三个数中最多有一个数大于 viv_i,最多有一个数小于 viv_i,从而可以将限制拆成若干个两数之间的限制:

对于 axi,ayi,azia_{x_i},a_{y_i},a_{z_i} 中任意两个不同的数 b,cb,c,如果 b>vib> v_icvic\leq v_i,如果 b<vib<v_icvic\geq v_i

此时每个限制只涉及两个变量,问题可以被表示为一个 2-SAT 的形式:

首先,对于每个变量 aia_i,设若干个变量 xi,1,,xi,109x_{i,1},\cdots,x_{i,10^9} 依次表示 xix_i 是否大于等于 1,2,,1091,2,\cdots,10^9,那么这些变量的取值需要满足如果 xi,v+1x_{i,v+1} 为真,则 xi,vx_{i,v} 为真,以及对应的逆否命题。一组限制可以表示为如果 xb,vi+1x_{b,v_i+1} 为真,则 xc,vi+1x_{c,v_i+1} 为假以及如果 xb,vix_{b,v_i} 为假,则 xc,vix_{c,v_i} 为真。这样得到了一个 2-SAT,有解当且仅当 2-SAT 有解,且通过 2-SAT 的解,考虑每组 xx 的情况即可构造原问题解。

同时注意到一个变量可以只取它涉及到的限制中的那些 viv_i(使用调整容易证明),因此可以在 xi,1,,xi,109x_{i,1},\cdots,x_{i,10^9} 中只保留涉及到的那么 viv_i ,然后使用相同的方式建图,这样点数降为 O(n+m)O(n+m),可以通过

复杂度 O(n+mlogm)O(n+m\log m)

(关于 2-SAT:反向边是必要的,否则拓扑序找到的解不一定对)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 600500
int n,m,s[N][4],lb[N],head[N],cnt,su;
vector<int> vl[N];
struct edge{int t,next;}ed[N*3];
void adde(int f,int t)
{
	ed[++cnt]=(edge){t,head[f]};head[f]=cnt;
}
int ti,dfn[N],low[N],st[N],ct,id,scc[N],ci;
void dfs(int u)
{
	dfn[u]=++ti;low[u]=dfn[u];
	st[++ct]=u;
	for(int i=head[u];i;i=ed[i].next)
	if(!dfn[ed[i].t])dfs(ed[i].t),low[u]=min(low[u],low[ed[i].t]);
	else if(!scc[ed[i].t])low[u]=min(low[u],dfn[ed[i].t]);
	if(low[u]==dfn[u])
	{
		ci++;
		while(st[ct+1]!=u)scc[st[ct]]=ci,ct--;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<4;j++)scanf("%d",&s[i][j]);
		for(int j=0;j<3;j++)vl[s[i][j]].push_back(s[i][3]);
	}
	for(int i=1;i<=n;i++)
	{
		sort(vl[i].begin(),vl[i].end());
		vector<int> ls;
		for(int j=0;j<vl[i].size();j++)if(!j||vl[i][j]!=vl[i][j-1])ls.push_back(vl[i][j]);
		vl[i]=ls;lb[i]=lb[i-1]+vl[i-1].size();
	}
	su=(lb[n]+vl[n].size())*2+1;
	for(int i=1;i<=n;i++)for(int j=1;j<vl[i].size();j++)
	adde((lb[i]+j+1)*2+1,(lb[i]+j)*2+1),adde((lb[i]+j)*2,(lb[i]+j+1)*2);
	for(int i=1;i<=m;i++)
	for(int j=0;j<3;j++)
	for(int k=j+1;k<3;k++)
	{
		int f=s[i][j],t=s[i][k];
		int fv=lower_bound(vl[f].begin(),vl[f].end(),s[i][3])-vl[f].begin()+1;
		int tv=lower_bound(vl[t].begin(),vl[t].end(),s[i][3])-vl[t].begin()+1;
		if(fv<vl[f].size()&&tv<vl[t].size())adde((lb[f]+fv+1)*2+1,(lb[t]+tv+1)*2),adde((lb[t]+tv+1)*2+1,(lb[f]+fv+1)*2);
		adde((lb[f]+fv)*2,(lb[t]+tv)*2+1),adde((lb[t]+tv)*2,(lb[f]+fv)*2+1);
	}
	for(int i=2;i<=su;i++)if(!dfn[i])dfs(i);
	for(int i=2;i<=su;i+=2)if(scc[i]==scc[i+1]){printf("NO\n");return 0;}
	printf("YES\n");
	for(int i=1;i<=n;i++)
	{
		int as=1;
		for(int j=1;j<=vl[i].size();j++)if(scc[(lb[i]+j)*2+1]<scc[(lb[i]+j)*2])as=vl[i][j-1];
		printf("%d ",as);
	}
}
D2 T2 #20 Imbalance
Problem

给定 nn,偶数 kk 以及一个长度小于 kk 的前缀 ss

求有多少长度为 nn0101 串满足其以 ss 为前缀,且任意长度为 kk 的子串中 0,10,1 数量不相同。答案模 998244353998244353

n114n\leq 114

2s,1024MB2s,1024MB

partial: n66n\leq 66

Sol

考虑前缀和 susu,则第一个限制为 sui+1sui{0,1}su_{i+1}-su_i\in\{0,1\},第二个限制为 sui+ksuik2su_{i+k}-su_i\neq \frac k2。此时考虑将 susu 排成 kk 列的矩阵,则第一个限制为横向相邻两个以及第一列和最后一列的限制,第二个限制为纵向相邻两个的限制,即限制几乎只包含矩阵的相邻元素。

那么考虑按照大小分治做。对于 kk 小的情况,可以直接记录最后 k1k-1 个位置的值做到 O(n2k)O(n2^k)

对于 kk 大的情况,考虑将序列每 kk 个分一段,则限制为一段的一个后缀中 11 的数量加上下一段中对应前缀 11 的数量不能等于 k2\frac k2。但前后缀难以同时处理,因此考虑枚举分段后每一段中 11 的个数和,变为前缀和间的限制,即枚举 [1,k],[k+1,2k],[1,k],[k+1,2k],\cdots 段的 11 数量,这样的复杂度为 O(knk)O(k^{\frac nk})

同时可以发现,如果 [1,k][1,k] 的和小于一半,因为相邻两段的和最多相差 11,因此合法串不可能再有一个长度为 kk 的段和大于 k2\frac k2。因此可以将所有合法串分为任意长度为 kk 的段和大于 k2\frac k2 的和小于 k2\frac k2 的,此时又可以发现一个的答案即为反转 ss 后另外一个的答案,因此只需考虑求一个的过程,这里考虑小于 k2\frac k2 的。

记第 ii 段的和为 sis_i,第 ii 段前 jj 个元素的和为 vi,jv_{i,j},则限制为:

  1. vi,0=0,vi,j+1vi,j{0,1},vi,k=siv_{i,0}=0,v_{i,j+1}-v_{i,j}\in\{0,1\},v_{i,k}=s_i
  2. vi+1,jvi,j+si<k2v_{i+1,j}-v_{i,j}+s_i<\frac k2(最后一段超出 nn 的部分不参与限制)

那么最直接的方式为记录每一行 v,jv_{*,j} 的状态dp,或者轮廓线。但这样只能做到 O((knk)2)O((k^{\frac nk})^2)n=114,k=24n=114,k=24 时难以通过。

考虑第二个限制,相当于 vi+1,jvi,j<k2siv_{i+1,j}-v_{i,j}<\frac k2-s_i,即在同一点开始时, vi+1v_{i+1} 前缀和对应的折线不能比 viv_i 的折线高 k2si\frac k2-s_i 或以上。那么如果将 viv_i 的折线从 vi+1v_{i+1} 的折线上方 k2si\frac k2-s_i 开始,则限制变为两条折线不能相交。

对于多条折线(多部分)的情况,那么最后一条(第 ll 条)折线从 (0,0)(0,0) 开始,第 l1l-1 条折线从 (0,k2sl1)(0,\frac k2-s_{l-1}) 开始,以此类推,第 ii 条折线从 (0,k2(li)j=il1sj)(0,\frac k2*(l-i)-\sum_{j=i}^{l-1}s_j) 开始,限制为所有折线不能相交。除去最后一条折线外,每条折线的终点为 (k,si+(k2(li)j=il1sj))(k,s_i+(\frac k2*(l-i)-\sum_{j=i}^{l-1}s_j)),最后一条折线的终点为 (nk(l1),sl)(n-k(l-1),s_l),折线只能向右 (1,0)(1,0) 和右上 (1,1)(1,1)

对于固定了一个前缀的情况,考虑对前缀之外的部分进行划分,这样前缀相当于对第一段的和进行一些限制:第一段的每个前缀和不能大于某个值,这相当于在将所有折线放到同一平面上后,最上方的第一条折线有一个有折线组成的上边界。考虑不能相交的限制,则相当于所有路径只能在上边界下走。

对于不能相交条件,考虑 LGV Lemma:

nn 个起点 sis_inn 个终点,满足如下限制:

  1. 如果 a<b,c>da<b,c>d,则 satcs_a-t_csbtds_b-t_d 路径一定相交(常规形式为二维平面上从左侧走到右侧)
  2. 如果两条路径相交,则可以交换两条路径的后半段,交换两个终点(常规形式为路径不同步之间没有相关限制)

考虑求出 s1t1,s2t2,,sntns_1-t_1,s_2-t_2,\cdots,s_n-t_n 的不相交路径组个数,则:

考虑一个 n×nn\times n 的矩阵 AAAi,jA_{i,j} 表示 sitjs_i-t_j 的路径方案数。

考虑 detA\det A,其考虑了两种情况的路径:

  1. s1t1,s2t2,,sntns_1-t_1,s_2-t_2,\cdots,s_n-t_n 的不相交路径,这部分即为答案。
  2. 存在相交的路径,可以发现由于交换的存在,任意一组相交路径有若干个交换点,每一个都可以任选是否交换,而行列式中的 (1)p(-1)^p 在每次交换后符号改变,因此系数为 (1+(1))k=0(1+(-1))^k=0,其中 kk 为交点数。

因此 ans=detAans=\det A

回到此问题,它几乎满足上述限制,唯一问题是最后一条折线的终点 (nk(l1),sl)(n-k(l-1),s_l) 不在 x=kx=k 上,可能导致第一个条件不满足(有路径从终点右下方绕过去)。

因此考虑要求 (nk(l1),sl)(n-k(l-1),s_l) 右下角部分不能被经过,这样就满足了第一个条件,可以使用 LGV Lemma 解决问题。

那么问题相当于上述方式给出的网格(最多 k+1k+1n+kn+k 列)上有 nk\frac nk 个起点 nk\frac nk 个终点,求出每一个起点到每一个终点的路径方案数。由于有复杂的上边界,考虑直接 dp 处理,则复杂度为 O(nknk)=O(n2)O(nk*\frac nk)=O(n^2)。总复杂度 O(n2knk)O(n^2*k^{\frac nk}),差一点通过。

注意到此时网格的种类是有限的:网格上边界只和第一条折线的起始坐标(即 i<lk2si\sum_{i<l} \frac k2-s_i )有关,右下角的限制只和 sls_l 有关。因此总共只有 O(nk)O(nk) 种可能的矩形。考虑枚举这样的矩形,再枚举终点或起点做 dp,预处理出所有可能的结果。这部分复杂度 O(n3k2)O(n^3k^2),但因为各种常数原因速度非常快。

然后 LGV Lemma 部分即可直接使用预处理的结果求解。复杂度 O(knk(nk)3+n3k2)O(k^{\frac nk}*(\frac nk)^3+n^3k^2)。但这里 l5l\leq 5n3,n2n,n!n^3,n2^n,n! 的求行列式做法效率差距可能不大(甚至 n3n^3 更慢)

最后合并两部分即可,这里可以取 k22k\leq 22 时使用第一种做法,否则使用第二种做法。

总复杂度大概似乎好像是 O(exp(cnlogn))O(\exp(c\sqrt{n\log n})) 的。

Code
#include<cstdio>
using namespace std;
#define mod 998244353
int n,k,m;
char s[121];
struct task1{
	int dp[2100001],nt[2100001],ct[4200001];
	void solve()
	{
		for(int i=1;i<1<<k;i++)ct[i]=ct[i-(i&-i)]+1;
		int rv=0;
		for(int i=m;i>=1;i--)rv=rv*2+s[i]-'0';
		for(int i=0;i<1<<k-m;i++)if(ct[rv+(i<<m)]!=(k>>1))dp[(rv+(i<<m))>>1]++;
		for(int i=1;i<=n-k;i++)
		{
			for(int j=0;j<1<<k-1;j++)
			{
				if(ct[j]!=(k>>1))nt[j>>1]+=dp[j];
				if(ct[j]+1!=(k>>1))nt[(j+(1<<k-1))>>1]+=dp[j];
			}
			for(int j=0;j<1<<k-1;j++)dp[j]=nt[j]%mod,nt[j]=0;
		}
		int as=0;
		for(int i=0;i<1<<k-1;i++)as=(as+dp[i])%mod;
		printf("%d\n",as);
	}
}t1;
struct task2{
	int df[61][61][61][121],dg[61][61][61],ri[121];
	int lb[121],rb[121],dp[121][121];
	int t,rl,vl[8],fi[8],f[8][8],as;
	void init()
	{
		t=(n-m-1)/k+1,rl=n-m-k*(t-1);
		int si=0;
		for(int i=k;i>=0;i--)ri[i]=k/2;
		for(int i=m;i>=0;i--)ri[i+k-m]=k/2-1-si,si+=s[i]=='1';
		for(int dx=0;dx<=k/2*(t-1);dx++)
		for(int lx=0;lx<k/2&&lx<=rl;lx++)
		{
			for(int i=rl;i<=k;i++)lb[i]=lx;
			for(int i=0;i<=k;i++)rb[i]=ri[i]+dx;
			for(int sx=0;sx<=dx;sx++)
			{
				for(int i=0;i<=k;i++)for(int j=0;j<=dx+k/2;j++)dp[i][j]=0;
				dp[0][sx]=1;
				for(int i=0;i<k;i++)for(int j=lb[i];j<=rb[i];j++)
				dp[i+1][j]=(dp[i+1][j]+dp[i][j])%mod,dp[i+1][j+1]=(dp[i+1][j+1]+dp[i][j])%mod;
				for(int i=0;i<=dx+k/2;i++)df[dx][lx][sx][i]=0;
				for(int i=lb[k];i<=rb[k];i++)df[dx][lx][sx][i]=dp[k][i];
				if(lx>=lb[rl]&&lx<=rb[rl])dg[dx][lx][sx]=dp[rl][lx];else dg[dx][lx][sx]=0;
			}
		}
	}
	int di[64],ci[64];
	void doit()
	{
		fi[1]=0;
		for(int i=2;i<=t;i++)fi[i]=fi[i-1]+k/2-vl[i];
		for(int i=1;i<=t;i++)f[i][1]=dg[fi[t]][vl[1]][fi[i]];
		for(int j=2;j<=t;j++)for(int i=1;i<=t;i++)f[i][j]=df[fi[t]][vl[1]][fi[i]][fi[j]+vl[j]];
		for(int i=1;i<1<<t;i++)di[i]=0,ci[i]=ci[i-(i&-i)]+1;
		di[0]=1;
		for(int i=0;i<1<<t;i++)for(int j=1;j<=t;j++)if((~i)&(1<<j-1))di[i|(1<<j-1)]=(di[i|(1<<j-1)]+1ll*di[i]*f[ci[i]+1][j]%mod*((ci[i>>j]&1)?mod-1:1))%mod;
		as=(as+di[(1<<t)-1])%mod;
	}
	void dfs(int u)
	{
		if(u==t+1){doit();return;}
		for(int v=0;v<k/2&&(u>1||v<=rl);v++)vl[u]=v,dfs(u+1);
	}
	void solve()
	{
		init();dfs(1);
		for(int i=1;i<=m;i++)s[i]^=1;
		init();dfs(1);
		printf("%d\n",as);
	}
}t2;
int main()
{
	scanf("%d%d%d%s",&n,&k,&m,s+1);
	if(k<=22)t1.solve();
	else t2.solve();
}
D2 T3 #33 学姐买瓜
Problem

qq 次操作,每次操作为如下类型之一:

  1. 加入一个区间 [li,ri][l_i,r_i]
  2. 给一个区间 [l,r][l,r],求出最多能在加入过的区间内选出多少个,满足选出的区间都在 [l,r][l,r] 内且选出区间两两不交。

所有区间都在 [1,n][1,n] 内,且端点为整数。

n,q3×105n,q\leq 3\times 10^5

1s,256MB1s,256MB

Sol

首先考虑单组询问,有一种显然的贪心方式:每次从当前 ll 开始,选择 lill_i\geq l 的区间中 rir_i 最小的一个区间,然后 lri+1l\leftarrow r_i+1

因此考虑 rbxrb_x 表示 lixl_i\geq x 的区间的中 rir_i 的最小值加一的结果,那么询问相当于从 ll 开始,每次跳 rbxrb_x,跳到下一步大于 r+1r+1 时停止,跳的次数即为答案。

考虑加入区间对 rbrb 的影响。可以发现这相当于对 llil\leq l_i 部分的 rbrb 前缀取 min\min,这进一步可以变换为 rbrb 的区间赋值,维护值域相同的段即可均摊 O(qlogn)O(q\log n) 得到所有的区间赋值操作。

改整个区间的跳转难以直接处理。考虑对于一次将 [l,r][l,r]rbrb 设为 vv 的操作,变为 ll+1,l+1l2,,r1rl\to l+1,l+1\to l_2,\cdots,r-1\to r 以及 rvr\to v。然后令最后一条边权值为 11,剩余边权值为 00,这样权值表示了原先的跳转次数。

可以发现使用这一方式后,均摊只会更改 O(q)O(q) 次一个点的后继(一次最多使一个点不满足 ii+1i\to i+1)。那么只需要支持 O(q)O(q) 次修改一个点跳到的后继以及边权,O(q)O(q) 次从一点开始跳的询问。

考虑 LCT 维护,使用 LCT 维护固定根的树,每个点的父亲为它跳到的节点。询问时 split 出 ll 到根的链,在这个 splay 上二分第一个大于 rr 的位置,由右侧部分链上边权和即可得到答案。

复杂度 O(qlogn)O(q\log n)

也可以分块维护,能够通过。

Code
#include<cstdio>
#include<set>
using namespace std;
#define N 300590
int n,q,a,b,c;
int fa[N],ch[N][2],vl[N],su[N];
bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void pushup(int x){su[x]=su[ch[x][0]]+su[ch[x][1]]+vl[x];}
void rotate(int x)
{
	int f=fa[x],g=fa[f],tp=ch[f][1]==x;
	fa[x]=g;if(nroot(f))ch[g][ch[g][1]==f]=x;
	fa[ch[x][!tp]]=f;ch[f][tp]=ch[x][!tp];
	fa[f]=x;ch[x][!tp]=f;
	pushup(f);pushup(x);
}
void splay(int x)
{
	while(nroot(x))
	{
		int f=fa[x],g=fa[f];
		if(nroot(f))rotate((ch[g][1]==f)^(ch[f][1]==x)?f:x);
		rotate(x);
	}
}
void access(int x)
{
	int tp=0;
	while(x)
	{
		splay(x);
		ch[x][1]=tp;pushup(x);
		tp=x;x=fa[x];
	}
}
void modify(int x,int f)
{
	access(x);
	splay(x);
	fa[ch[x][0]]=0;ch[x][0]=0;pushup(x);
	access(f);splay(f);
	fa[x]=f;pushup(f);
}
set<pair<int,int> > si;
void ins(int l,int r)
{
	pair<int,int> tp=*si.lower_bound(make_pair(l,0));
	if(tp.second<=r)return;
	while(1)
	{
		set<pair<int,int> >::iterator it=si.lower_bound(make_pair(l,n+2));
		if(it==si.begin())break;
		it--;tp=*it;
		if(tp.second<r)break;
		si.erase(tp);
		int u=tp.first;
		modify(u,u+1);
		access(u);splay(u);vl[u]=0;pushup(u);
	}
	modify(l,r+1);si.insert(make_pair(l,r));
	access(l);splay(l);vl[l]=1;pushup(l);
}
int query(int l,int r)
{
	access(l);splay(n+1);
	int tp=n+1,as=n+1;
	while(1)
	{
		int fg=tp>r;
		if(fg)as=tp;
		if(!ch[tp][fg])break;
		tp=ch[tp][fg];
	}
	splay(tp);splay(as);
	return su[ch[as][1]]-(as>r+1);
}
int main()
{
	scanf("%d%d",&q,&n);
	for(int i=1;i<=n;i++)fa[i]=i+1;
	si.insert(make_pair(n+1,n+1));
	while(q--)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a==1)ins(b,c);
		else printf("%d\n",query(b,c));
	}
}
D3 T1 #9 Lovely Dogs
Problem

给定一棵以 11 为根的有根树,点有点权 viv_i,保证 viv_i 构成一个 nn 阶排列。

给定 kk,定义积性函数 ff 满足 f(pq)=[qk](1)qf(p^q)=[q\leq k](-1)^q

定义 St(u)St(u)uu 子树中的点构成的集合,对于每个点 uu,求:

iSt(u)f(vi2)+i,jSt(u),i<jf(vivj)\sum_{i\in St(u)}f(v_i^2)+\sum_{i,j\in St(u),i<j}f(v_iv_j)

n2×105n\leq 2\times 10^5

1s,256MB1s,256MB

Sol

可以发现原式的两倍等于:

i,jSt(u)f(vivj)+iSt(u)f(vi2)\sum_{i,j\in St(u)}f(v_iv_j)+\sum_{i\in St(u)}f(v_i^2)

后者容易树上前缀和解决,只需要考虑前者。这相当于给出一个集合 SS,求出 iSjSf(vivj)\sum_{i\in S}\sum_{j\in S}f(v_iv_j)

注意到 ff 几乎是完全积性的:(1)a+b=(1)a(1)b(-1)^{a+b}=(-1)^a(-1)^b,且如果 f(ij)0f(ij)\neq 0,则 ijij 的每个质因子次数不超过 kk,因此 i,ji,j 也满足质因子次数不超过 kk。因此可以发现如果 f(ij)0f(ij)\neq 0 则一定有 f(ij)=f(i)f(j)f(ij)=f(i)f(j)。那么原问题相当于 iSf(vi)jS[f(vivj)0]f(vj)\sum_{i \in S}f(v_i)\sum_{j\in S}[f(v_iv_j)\neq 0]f(v_j)

首先考虑给定 ii, 给定一个集合 SS,如何求 jS[f(vivj)0]f(vj)\sum_{j\in S}[f(v_iv_j)\neq 0]f(v_j)

考虑先去掉所有存在质因子次数大于 kkviv_i,这样如果 vivjv_iv_j 存在一个大于 kk 次的质因子 pk+ip^{k+i},则一定有 pvi,pvjp|v_i,p|v_j

这样去掉这些数后,对于一个 ii,设其质因数分解的结果为 i=1kpiqi\prod_{i=1}^kp_i^{q_i},则去掉质因子次数大于 kk 的点后,有满足 f(vivj)0f(v_iv_j)\neq 0 等价于 i,vj\forall i,v_jpip_i 质因子的次数不超过 kqik-q_i,即 pikqi+1vjp_i^{k-q_i+1}\not| v_j

考虑对每个 ii 容斥,容斥后变为选择一个集合,要求这些 pikqi+1p_i^{k-q_i+1} 全部整除 vjv_j,从而权值为 j[iSpikqi+1vj]f(vj)\sum_j[\prod_{i\in S} p_i^{k-q_i+1}|v_j]f(v_j),系数为 (1)S(-1)^S。这相当于统计某个 vv 的所有倍数的 f(vj)f(v_j) 之和。

注意到 2c2^c(其中 ccxx 的质因子数量)不超过 xx 的因数个数,因此对每种权值进行容斥,总共会得到不超过 O(nlogn)O(n\log n)iSpikqi+1\prod_{i\in S} p_i^{k-q_i+1}。以下记 T(v)T(v) 表示对权值 vv 进行容斥得到的所有 iSpikqi+1\prod_{i\in S} p_i^{k-q_i+1} 的集合,V(v,x)V(v,x) 表示对 vv 容斥到 xx 时的权值。

则对于一个子树 uu 的询问 iSt(u)jSt(u)f(vivj)\sum_{i\in St(u)}\sum_{j\in St(u)}f(v_iv_j),其相当于如下形式:

iSt(u)f(vi)aT(vi)V(vi,a)(jSt(u),avjf(vj))\sum_{i\in St(u)}f(v_i)*\sum_{a\in T(v_i)}V(v_i,a)(\sum_{j\in St(u),a|v_j}f(v_j))

vv 的因子集合为 R(v)R(v),则相当于:

iSt(u)f(vi)aT(vi)V(vi,a)(jSt(u),aR(vj)f(vj))\sum_{i\in St(u)}f(v_i)*\sum_{a\in T(v_i)}V(v_i,a)(\sum_{j\in St(u),a\in R(v_j)}f(v_j))

aa 同时是 T(vi),R(vj)T(v_i),R(v_j) 的子集。因此考虑枚举 aa,得到:

a(iSt(u),aT(vi)f(vi)V(vi,a))(iSt(u),aR(vi)f(vi))\sum_a (\sum_{i\in St(u),a\in T(v_i)}f(v_i)*V(v_i,a))*(\sum_{i\in St(u),a\in R(v_i)}f(v_i))

这相当于对于每种 aa,统计子树内 T(vi)T(v_i) 包含 aa 的点的对应权值和,R(vi)R(v_i) 包含 aa 的点的对应权值和,两个权值相乘即为贡献。

最后,注意到考虑所有数,iT(vi),iR(vi)=O(nlogn)\sum_i |T(v_i)|,\sum_i |R(v_i)|=O(n\log n)。此时有两种方式:

  1. 使用线段树合并记录每个 aa 的两种和,向上合并即可。复杂度 O(nlog2n)O(n\log^2 n),但是可以通过。
  2. 对每种权值分开考虑。对于一个 aa,考虑取出 R,TR,T 中包含 aa 的点,aa 处贡献只与这些点相关。因此考虑对这些点建虚树,此时虚树的每条边上所有点贡献相同,不在虚树某条边上的点贡献显然为 00。每条边的贡献可以树上差分处理。求虚树的 LCA 可以 ST 表做到线性,合并可以线性,只剩下dfs序排序的过程。这里可以先将所有点按照 dfs 序排序,再依次对每个点加入 T,RT,R,即可避免排序过程。复杂度可以做到 O(nlogn)O(n\log n),但是常数较大。
Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 200500
#define ll long long
int n,k,a,b,v[N],head[N],cnt;
struct edge{int t,next;}ed[N*2];
void adde(int f,int t)
{
	ed[++cnt]=(edge){t,head[f]};head[f]=cnt;
	ed[++cnt]=(edge){f,head[t]};head[t]=cnt;
}

int ci,dep[N],st[N*2],si[N*2][19],lg[N*2],id[N];
void dfs0(int u,int fa)
{
	dep[u]=dep[fa]+1;st[++ci]=u;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs0(ed[i].t,u),st[++ci]=u;
}
void init_st()
{
	dfs0(1,0);
	for(int i=1;i<=ci;i++)si[i][0]=st[i],id[st[i]]=id[st[i]]?id[st[i]]:i;
	for(int d=1;d<=18;d++)
	for(int i=1;i+(1<<d)-1<=ci;i++)
	{
		int x=si[i][d-1],y=si[i+(1<<d-1)][d-1];
		si[i][d]=dep[x]<dep[y]?x:y;
	}
	for(int i=2;i<=ci;i++)lg[i]=lg[i>>1]+1;
}
int getLCA(int x,int y)
{
	int l=id[x],r=id[y];
	if(l>r)l^=r^=l^=r;
	int le=lg[r-l+1],u=si[l][le],v=si[r-(1<<le)+1][le];
	return dep[u]<dep[v]?u:v;
}

ll as[N];
struct sth{int x,a,b;};
vector<sth> rs[N];
int sr[N],ct,sv[N][2];
void solve(vector<sth> si)
{
	sr[ct=1]=si[0].x;sv[1][0]=si[0].a;sv[1][1]=si[0].b;
	for(int i=1;i<si.size();i++)
	{
		int x=si[i].x,a=si[i].a,b=si[i].b;
		while(1)
		{
			int l=getLCA(x,sr[ct]);
			if(dep[l]<=dep[sr[ct-1]])
			{
				as[sr[ct]]+=1ll*sv[ct][0]*sv[ct][1],as[sr[ct-1]]-=1ll*sv[ct][0]*sv[ct][1];
				sv[ct-1][0]+=sv[ct][0];sv[ct-1][1]+=sv[ct][1];
				sv[ct][0]=sv[ct][1]=0,ct--;
			}
			else
			{
				as[sr[ct]]+=1ll*sv[ct][0]*sv[ct][1],as[l]-=1ll*sv[ct][0]*sv[ct][1];
				sr[ct]=l;break;
			}
		}
		sr[++ct]=x;sv[ct][0]=a;sv[ct][1]=b;
	}
	while(ct)
	{
		as[sr[ct]]+=1ll*sv[ct][0]*sv[ct][1],as[sr[ct-1]]-=1ll*sv[ct][0]*sv[ct][1];
		sv[ct-1][0]+=sv[ct][0];sv[ct-1][1]+=sv[ct][1];
		sv[ct][0]=sv[ct][1]=0,ct--;
	}
}

struct sth1{int p,q;};
vector<sth1> frs[N];
int pw[N][22];
void init_pr()
{
	for(int i=2;i<=n;i++)if(!frs[i].size())
	for(int j=i;j<=n;j+=i)
	{
		int ct=0,tp=j;
		while(tp%i==0)tp/=i,ct++;
		frs[j].push_back((sth1){i,ct});
	}
	for(int i=1;i<=n;i++)
	{
		pw[i][0]=1;
		for(int j=1;j<=k;j++)
		pw[i][j]=1ll*pw[i][j-1]*i>n?n+1:pw[i][j-1]*i;
	}
}

int c1,v1[21][2];
vector<sth1> tp;
void dfs(int u,ll vl,int tv)
{
	if(u==c1+1){tp.push_back((sth1){vl,tv});return;}
	for(int i=0;i<=v1[u][1]&&vl<=n;i++,vl*=v1[u][0],tv*=-1)dfs(u+1,vl,tv);
}

void dfs1(int u,int fa){for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u),as[u]+=as[ed[i].t];}

int vis[N];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	init_st();init_pr();
	for(int i=1;i<=ci;i++)
	{
		int u=st[i],rv=1;
		if(vis[u])continue;vis[u]=1;
		c1=frs[v[u]].size();
		for(int j=1;j<=c1;j++)
		{
			v1[j][0]=frs[v[u]][j-1].p;
			v1[j][1]=frs[v[u]][j-1].q;
			rv*=(v1[j][1]&1)?-1:1;
			if(v1[j][1]>k)rv=0;
		}
		if(!rv)continue;
		tp.clear();dfs(1,1,1);
		for(int j=0;j<tp.size();j++)
		rs[tp[j].p].push_back((sth){u,rv,0});
		int fg=1;
		for(int j=1;j<=c1;j++)fg&=v1[j][1]*2<=k;
		if(fg)as[u]++;
		for(int j=1;j<=c1;j++)v1[j][0]=pw[v1[j][0]][k+1-v1[j][1]],v1[j][1]=1;
		tp.clear();dfs(1,1,1);
		for(int j=0;j<tp.size();j++)
		{
			int vl=tp[j].p,vb=tp[j].q*rv;
			if(rs[vl].size()&&rs[vl].back().x==u)rs[vl][rs[vl].size()-1].b=vb;
			else rs[vl].push_back((sth){u,0,vb});
		}
	}
	for(int i=1;i<=n;i++)if(rs[i].size())
	solve(rs[i]);
	dfs1(1,0);
	for(int i=1;i<=n;i++)printf("%lld\n",as[i]/2);
}
D3 T2 #35 Alice、Bob 与 DFS
Problem

给定 nn 个点 mm 条边的有向无环图,保证 1,2,,n1,2,\cdots,n 是一个拓扑序。图中每个点有黑白中的一种颜色。

kk 个程序,第 ii 个程序从 rir_i 开始对 DAG 进行 dfs,初始时所有程序处于挂起状态。

两人进行博弈,每人每次可以进行如下操作:

选择一个被挂起且还没有结束的程序,继续运行。

程序在 dfs 一个点时按照固定顺序遍历其出边,如果该点是白点,则当前操作的人在遍历到该点的任意一条出边时都可以选择是否跳过该条出边,如果是黑点则不能选择。

当一个运行的程序进行下一次 dfs 调用时,程序被挂起。

选择运行的程序被挂起或结束时换另外一人操作,无法操作者输。

双方最优操作,求谁获胜。

n2×105,m4×105,k2×105n\leq 2\times 10^5,m\leq 4\times 10^5,k\leq 2\times 10^5

2s,256MB2s,256MB

partial: 只有白点

Sol

首先考虑如何描述程序 dfs 的过程,函数调用可以看成一个栈,因此可以得到如下方式:

有一个栈依次记录了程序剩余未执行的 dfs 调用。每个调用是黑色或者白色的,白色代表回溯时可以跳过这个调用,黑色代表不行。

初始时,栈中倒序存储 rir_i 的出边,颜色为 rir_i 颜色,一个人对该栈操作时使用如下方式:

选择弹出栈顶若干元素,直到遇到一个黑色元素或者手动选择停止。如果栈为空则直接结束。

否则,拿出栈顶元素,设其为 uu,向栈中逆序加入 uu 的所有出边,颜色为 uu 的颜色。此次操作结束。

可以发现以栈作为状态,则游戏是平等的。同时,可以发现从一个状态出发不能回到自身(如果回到自身,考虑栈中拓扑序最小的点,如果它被操作则该点出现次数减一,从而所有的这个点都不能被操作(弹出或者拿出),然后考虑剩下的点,可以发现任何点都不能被操作,矛盾),因此游戏无环,可以使用 SG 函数描述该游戏。接下来将栈描述为 {v1,v2,}\{v_1,v_2,\cdots\},SG 函数记为 SG({v1,v2,})SG(\{v_1,v_2,\cdots\})。同时,记 uu 的出边组成序列 S(u)S(u)

直接记录每个栈的 SG 值是不能接受的,需要考虑这个游戏的性质。

首先考虑全部为白点的情况,考虑此时直接 dfs(u) 与在中间某个时刻调用 dfs(u)(即栈底有其它元素)的博弈区别。设栈底的额外元素为 v1,,vkv_1,\cdots,v_k,考虑两种游戏的区别。此时第一步可以弹出栈顶任意个元素,如果不弹空 uu 的部分,则两个博弈等价,而如果弹空 uu 的部分直到 v1,,vkv_1,\cdots,v_k,可以发现这部分相当于在第二个游戏中,每一步不仅可以使用第一个游戏的对应操作,也可以选择一个 ll,直接弹出到 {v1,,vl}\{v_1,\cdots,v_l\},然后拿出 vlv_l,即 {v1,,vl1}+S(vl)\{v_1,\cdots,v_{l-1}\}+S(v_l)

这相当于在之前的所有博弈中,每一步的后继状态加入了所有的 {v1,,vi1}+S(vi)\{v_1,\cdots,v_{i-1}\}+S(v_i)。注意到 SG 的计算方式是后继状态的 mexmex,可以发现,每一步都加入这些后继状态的 SG 值求 mexmex,相当于在 N\N 中去掉这些值,在剩下的数上求 mexmex,此时求 mexmex 的过程变为与第一个游戏形式相同的过程,因此得到如下结论:

记从 uu 出发,没有任何额外状态的游戏的 SG 值为 fuf_u,则在全是白点的游戏中某一步 dfsuu 时,设栈中之前的元素为 v1,,vkv_1,\cdots,v_k,则当前局面的 SG 值为所有在 SG({v1,,vi1}+S(vi)),ikSG(\{v_1,\cdots,v_{i-1}\}+S(v_i)),i\leq k 中没有出现过的自然数中的第 fu+1f_u+1 个。

那么对于全部是白点的游戏,只需要求出 vuv_u 即可。考虑求一个 fuf_u,相当于需要求出所有 SG({v1,,vi1}+S(vi)),ikSG(\{v_1,\cdots,v_{i-1}\}+S(v_i)),i\leq kmexmex。注意到求一个 SG({v1,,vi1}+S(vi))SG(\{v_1,\cdots,v_{i-1}\}+S(v_i)) 时,由上述结论只需要 fvif_{v_i} 和之前的所有 SG({v1,,vj1}+S(vj))SG(\{v_1,\cdots,v_{j-1}\}+S(v_j))。那么从后往前考虑出边,每次相当于在还没有出现过的数中选择第 kk 个,然后加入这个数。这个操作可以使用 BIT 实现。这样可以在 O(mlogm)O(m\log m) 的复杂度内求出所有 ff。结束后的状态为先手必败状态,因此可以看成最后有一个 sg=0sg=0 的额外状态的情况,进而单点出发的 SG 等于 fu+1f_u+1,这样就解决了原问题。(也可以在之前的游戏中看成栈底有一个结束状态,SG 值为 00

现在考虑加入黑点的情况。在之前的做法中,我们记录了能达到的额外后继状态的 SG 值构成的集合 TT,然后发现从 uu 开始的游戏答案有固定的形式(第 kk 个在 TT 中没有出现的权值),该形式与 TT 的值无关,因此无需记录 TT

但加入黑点后,会出现一些问题:

  1. 向栈中加入一个黑色元素相当于清空之前能到达的状态,即清空 TT,但这不影响整个过程。
  2. 黑色点只有一个操作,从而它的 SG 值只与之前的 TT 中是否包含 00 有关:包含则为 11,否则为 00。但这与之前的白点时的 SG 形式完全不同。

但可以注意到,黑点只涉及到 0,10,1 权值,因此考虑特殊处理 0,10,1,即使用如下方式描述 SG 值:

记录一个整数 kk,如果 k<2k<2,则最后的 SG 值固定为 kk,否则 SG 值为 2\geq 2 的整数中第 k1k-1 个没有在 TT 中出现的值。

此时 SG 值的判断需要知道 TT 中是否有 0,10,1,因此令 gu,0/1,0/1g_{u,0/1,0/1} 表示考虑求从 uu 出发的游戏的博弈,额外后继状态中是否存在 0,10,1 时使用上述方式描述的 SG 值形式。

对于一个白点,使用之前的方式倒着考虑,依次求值并加入到 TT 即可。

对于一个黑点,由于黑色元素不能被跳过,相当于求出新的值,然后清空 TT,再加入新的 SG,即 TT 中只剩下一个元素。

可以发现这样能正确表示一个白点的求 SG 过程,以及一个黑点的求 SG 过程,因此归纳可得这样能正确表示整个游戏的 SG 值,最后直接由 gu,1,0g_{u,1,0} 得到答案即可(结束状态为先手必败的状态)。

复杂度 O(mlogm)O(m\log m)

75pts可能的细节问题:黑点在有出边时会向栈中放入黑色元素,进而 SG 值只可能属于 {0,1}\{0,1\},但黑点可能没有出边。

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 400500
int n,a,b,k,v[N];
vector<int> s[N];
struct BIT{
	int tr[N];
	void modify(int x,int v){for(int i=x;i<=4e5;i+=i&-i)tr[i]+=v;}
	int kth(int k){int as=0;k--;for(int i=1<<18;i;i>>=1)if(as+i<=4e5&&i-tr[as+i]<=k)k-=i-tr[as+i],as+=i;return as+1;}
}tr;
int dp[N][4];
void solve()
{
	for(int i=n;i>=1;i--)
	for(int t=0;t<4;t++)
	if(v[i])
	{
		int tp=t;
		vector<int> tv;
		for(int j=0;j<s[i].size();j++)
		{
			int vl=dp[s[i][j]][tp];
			if(vl<2)tp|=1<<vl;
			else
			{
				int rv=1+tr.kth(vl-1);
				tv.push_back(rv-1);tr.modify(rv-1,1);
			}
		}
		if(tp<3)dp[i][t]=tp&1?1:0;
		else dp[i][t]=1+tr.kth(1);
		for(int j=0;j<tv.size();j++)tr.modify(tv[j],-1);
	}
	else
	{
		int tp=t;
		for(int j=0;j<s[i].size();j++)
		{
			int vl=dp[s[i][j]][tp];
			tp=1+(!!vl);//This is OK because black node only needs to check whether 0 is in T
		}
		dp[i][t]=tp==3?2:tp&1;//!!1
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		while(a--)scanf("%d",&b),s[i].push_back(b);
		reverse(s[i].begin(),s[i].end());
	}
	solve();
	scanf("%d",&a);
	while(a--)scanf("%d",&b),k^=dp[b][1];
	printf("%s\n",k?"Alice":"Bob");
}
D3 T3 #37 音符大师
Problem

在一维数轴上有两个长度为 LL 的区间,初始时两个区间都在 [0,L][0,L]

接下来有 nn 个事件顺序发生。在第 ii 个区间中,你需要使得存在某个区间覆盖了点 xix_i

在每次事件前,你可以对区间进行移动,将一个区间移动 11 的距离需要 11 的代价。

求完成所有事件需要的最小总代价。

n5×104,L50n\leq 5\times 10^4,L\leq 50

3s,512MB3s,512MB

partial: L5L\leq 5

Sol

首先可以发现,在移动过程中可以大量使用贪心思路:

  1. 如果当前区间覆盖了下一个点,则下一个事件之前可以不移动:将移动放到下一个事件之后不会变差。
  2. 否则,需要选择一个区间去接住这个点,此时可以让该区间移动最少的距离,使得端点接住 xix_i:将其余移动放到下一个事件之后不会变差。

从而每个状态在下一个事件处只有 O(1)O(1) 个转移。同时,任意时刻区间左端点只会是某个 xix_i 或者某个 xiLx_i-L

L=0L=0 的情况是一个经典问题:在时刻 ii,一定有一个点在 xix_i,只需要记录另外一个点的位置,因此设 dpi,xdp_{i,x} 表示时刻 ii,另外一个点在 xx 的最小代价,下一个事件处有如下转移:

  1. xix_i 的点去接 xi+1x_{i+1},则相当于整体加后移动到 dpi+1dp_{i+1}
  2. 另外一个点去接,则相当于求出 mindpi,x+xxi+1\min dp_{i,x}+|x-x_{i+1}|,然后转移到 dpi+1,xidp_{i+1,x_i}

因此可以使用一棵线段树维护 dpidp_i,直接转移即可。复杂度 O(nlogn)O(n\log n)

考虑直接套用该做法,此时区间左端点有 L+1L+1 个可能取值,因此设 dpi,k,xdp_{i,k,x} 表示时刻 ii,一个区间左端点在 xikx_i-k,另外一个区间左端点在 xx 的最小代价,使用线段树维护每一个 dpi,kdp_{i,k}。此时:

  1. 如果 xikx_i-k 的区间覆盖了下一个点,则整个线段树不需要改变,直接移动到下一个状态即可。
  2. 否则,如果另外一个区间覆盖了下一个点,则需要将这些状态拿出来,交换两维后重新插入,这里需要 O(L2+Llogn)O(L^2+L\log n) 的复杂度,
  3. 否则,需要移动区间,如果移动第一个区间,则相当于将整棵线段树整体加并移动到某一个 dpdp 状态。
  4. 如果移动另外一个区间,则相当于转移到某个 dpi+1,0/L,xikdp_{i+1,0/L,x_i-k},其中去掉第二种情况后根据贪心第二维只需要考虑 0,L0,L,因此这一部分只会转移到 O(L)O(L) 个状态,复杂度 O(Llogn)O(L\log n)

因此这样的复杂度为 O(nL2+nLlogn)O(nL^2+nL\log n),状态数 O(nL)O(nL),但不能通过。

但如果对拿出的状态使用如下优化,则复杂度似乎变为了 O(nLlogn)O(nL\log n),但我不会证:

对于拿出来的那些状态,如果一个状态与另外一个状态的第一个区间相同,第二个区间距离为 kk,且第一个状态的代价大于等于第二个状态的代价加上 kk,则第一个状态可以被第二个状态替代。

使用上述方式,删去可以被替代的状态,只保留剩余状态。

重新考虑上述做法,困难的部分在于第二部分的 L2L^2 个状态,即不移动区间就能接到下一个点的情况。

考虑跳过能不移动接住的一段,在需要移动时再转移。这样根据贪心思路,在时刻 ii 移动时,移动后区间左端点一定为 xix_ixiLx_i-L,因此只需要记录 dpi,0/1,xdp_{i,0/1,x}。但此时转移不再是一个一个转移,而是可能出现跳跃。

考虑一个 dpi,0/1dp_{i,0/1} 向后转移的情况,对于一个状态 dpi,0/1,xdp_{i,0/1,x},需要找到两个区间之后不能接住的第一个点,然后转移到那个位置。这看起来难以处理(不同 xx 转移位置不同),但考虑先求出只有第一个区间 [xiL,xi][x_i-L,x_i][xi,xi+L][x_i,x_i+L] 时第一个不能接住的位置 jj,这样如果第二个区间 [x,x+L][x,x+L] 不能接住这个点 xjx_j,则这两个区间不能接住的第一个位置就是 xjx_j,而第二个区间能接住 xjx_j 的情况只有 O(L)O(L) 个。从而转移除去 O(L)O(L) 个状态外,剩余状态转移相同。因此考虑如下实现:

首先考虑求下一个不能接住的点,可以对于每一个位置记录这个位置下一次需要被接住的时间,给两个区间询问不能被接住的点相当于所有没有被覆盖部分的区间最小值。这里只能记录当前时刻之后需要被接住的时间,这可以使用可持久化线段树或者直接线段树解决。

对于一个 dpi,0/1dp_{i,0/1},首先求出 O(L)O(L) 个特殊状态,对于这些特殊状态逐个转移。对于剩余状态,它们需要转移到同一个时刻,且它们都不能接住下一个时刻的点,因此使用之前的方式,移动第一个区间相当于将线段树合并过去,移动第二个区间相当于在合并前,两侧在线段树上求出类似 mindpi,x+xxi+1\min dp_{i,x}+|x-x_{i+1}| 的结果,然后加入 O(1)O(1) 个状态。

线段树上维护 mindpi,x,mindpi,x+x,mindpi,xx\min dp_{i,x},\min dp_{i,x}+x,\min dp_{i,x}-x 即可。

复杂度 O(nLlogn)O(nL\log n)

bonus: 如果对取出的 O(L)O(L) 个特殊状态使用之前的优化,保留有用状态,则复杂度似乎变为了 O(nlogn)O(n\log n)

bonus 2: 如果大力维护 dp,加入一个状态时枚举附近能被扔掉的状态扔掉,则好像直接暴力复杂度就是 O(nlogn)O(n\log n) 甚至 O(n)O(n) 的。

bouns 3: 好像只维护代价前 kk 小的状态,取 k=300k=300 就能过,不能理解这东西怎么卡,加上 bonus 2 就更抽象了。

以上bonus我都不会证。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 100500
#define M 10005000 
#define ll long long
int n,k,vi[N],v[N],ci,lb[N],rb[N],ri[N];
int ch[M][2],rt[N][2],ct;
ll mn[M][2],lz[M],as=1e18;
vector<int> rs;
int getid()
{
	int a;
	if(!rs.size())a=++ct;
	else a=rs.back(),rs.pop_back();
	ch[a][0]=ch[a][1]=0;mn[a][0]=mn[a][1]=1e18;lz[a]=0;
	return a;
}
void doit(int x,ll v){lz[x]+=v;mn[x][0]+=v;mn[x][1]+=v;}
void pushdown(int x)
{
	if(lz[x])
	{
		for(int t=0;t<2;t++)doit(ch[x][t],lz[x]);
		lz[x]=0;
	}
}
void modify(int x,int l,int r,int s,ll v0,ll v1)
{
	mn[x][0]=min(mn[x][0],v0);mn[x][1]=min(mn[x][1],v1);
	if(l==r)return;
	pushdown(x);
	int mid=(l+r)>>1,fg=mid<s;
	if(!ch[x][fg])ch[x][fg]=getid();
	if(!fg)modify(ch[x][0],l,mid,s,v0,v1);
	else modify(ch[x][1],mid+1,r,s,v0,v1);
}
int merge(int x,int y,int l,int r)
{
	if(!x||!y)return x+y;
	rs.push_back(y);
	mn[x][0]=min(mn[x][0],mn[y][0]);
	mn[x][1]=min(mn[x][1],mn[y][1]);
	if(l==r)return x;
	pushdown(x);pushdown(y);
	int mid=(l+r)>>1;
	ch[x][0]=merge(ch[x][0],ch[y][0],l,mid);
	ch[x][1]=merge(ch[x][1],ch[y][1],mid+1,r);
	return x;
}
ll query(int x,int l,int r,int l1,int r1,int id)
{
	if(l>r1||r<l1||!x)return 1e18;
	if(l>=l1&&r<=r1)return mn[x][id];
	pushdown(x);
	int mid=(l+r)>>1;
	return min(query(ch[x][0],l,mid,l1,r1,id),query(ch[x][1],mid+1,r,l1,r1,id));
}
void dfs(int x,int l,int r)
{
	if(!x)return;
	if(l==r){as=min(as,mn[x][0]+v[l]);return;}
	pushdown(x);
	int mid=(l+r)>>1;
	dfs(ch[x][0],l,mid);dfs(ch[x][1],mid+1,r);
}
ll tp[91][2],ci2,fg[91];
int dfs1(int x,int l,int r,int l1,int r1)
{
	if(l>r1||r<l1||!x)return x;
	if(l==r)
	{
		tp[++ci2][0]=l;tp[ci2][1]=mn[x][0]+v[l];
		rs.push_back(x);return 0;
	}
	pushdown(x);
	int mid=(l+r)>>1;
	ch[x][0]=dfs1(ch[x][0],l,mid,l1,r1);
	ch[x][1]=dfs1(ch[x][1],mid+1,r,l1,r1);
	if(!ch[x][0]&&!ch[x][1])rs.push_back(x),x=0;
	return x;
}
struct segt{
	struct node{int x,l,r,mn;}e[N*4];
	void build(int x,int l,int r)
	{
		e[x].l=l;e[x].r=r;e[x].mn=n+1;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(x<<1,l,mid);build(x<<1|1,mid+1,r);
	}
	void modify(int x,int s,int v)
	{
		if(e[x].l==e[x].r){e[x].mn=v;return;}
		int mid=(e[x].l+e[x].r)>>1;
		modify(x<<1|(mid<s),s,v);
		e[x].mn=min(e[x<<1].mn,e[x<<1|1].mn);
	}
	int query(int x,int l,int r)
	{
		if(e[x].l>r||e[x].r<l)return n+1;
		if(e[x].l>=l&&e[x].r<=r)return e[x].mn;
		return min(query(x<<1,l,r),query(x<<1|1,l,r));
	}
}tr;
int nt[N];
int query1(int nw,int l1,int r1){return min(tr.query(1,1,l1-1),tr.query(1,r1+1,ci));}
int query2(int nw,int l1,int r1,int l2,int r2)
{
	if(l1>l2)swap(l1,l2),swap(r1,r2);
	return min(min(tr.query(1,1,l1-1),tr.query(1,r1+1,l2-1)),tr.query(1,r2+1,ci));
}
int main()
{
	scanf("%d%d",&n,&k);
	ci=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&vi[i]),v[++ci]=vi[i];
		if(vi[i]>=k)v[++ci]=vi[i]-k;
	}
	sort(v+1,v+ci+1);
	int c2=ci;ci=0;
	for(int i=1;i<=c2;i++)if(i==1||v[i]!=v[i-1])v[++ci]=v[i];
	for(int i=1;i<=ci;i++)ri[i]=lower_bound(v+1,v+ci+1,v[i]+k+1)-v-1;
	for(int i=0;i<=n;i++)lb[i]=lower_bound(v+1,v+ci+1,vi[i]-k)-v,rb[i]=lower_bound(v+1,v+ci+1,vi[i])-v;
	for(int i=0;i<=n;i++)rt[i][0]=getid(),rt[i][1]=getid();
	modify(rt[0][0],1,ci,1,0,0);
	tr.build(1,1,ci);
	for(int i=n;i>=1;i--)nt[i]=tr.query(1,rb[i],rb[i]),tr.modify(1,rb[i],i);
	for(int i=0;i<=n;i++,tr.modify(1,rb[i],nt[i]))
	for(int t=0;t<2;t++)
	{
		int id=t?rb[i]:lb[i];
		int nt=query1(i,id,ri[id]);
		if(nt==n+1){dfs(rt[i][t],1,ci);continue;}
		ci2=0;rt[i][t]=dfs1(rt[i][t],1,ci,lb[nt],rb[nt]);
		for(int j=1;j<=ci2;j++)
		{
			int x1=id,x2=tp[j][0],t1=query2(i,x1,ri[x1],x2,ri[x2]);
			ll vl=tp[j][1];
			if(t1==n+1){as=min(as,vl);continue;}
			for(int p=0;p<2;p++)
			{
				if(x1>=rb[t1])modify(rt[t1][1],1,ci,x2,vl+v[x1]-v[rb[t1]]-v[x2],vl+v[x1]-v[rb[t1]]+v[x2]);
				else modify(rt[t1][0],1,ci,x2,vl+v[lb[t1]]-v[x1]-v[x2],vl+v[lb[t1]]-v[x1]+v[x2]);
				swap(x1,x2);
			}
		}
		ll lv=v[lb[nt]]+query(rt[i][t],1,ci,1,lb[nt],0),rv=query(rt[i][t],1,ci,rb[nt],ci,1)-v[rb[nt]];
		if(lv<1e17)modify(rt[nt][0],1,ci,id,lv-v[id],lv+v[id]);
		if(rv<1e17)modify(rt[nt][1],1,ci,id,rv-v[id],rv+v[id]);
		if(id>=rb[nt])doit(rt[i][t],v[id]-v[rb[nt]]),merge(rt[nt][1],rt[i][t],1,ci);
		else doit(rt[i][t],v[lb[nt]]-v[id]),merge(rt[nt][0],rt[i][t],1,ci);
	}
	printf("%lld\n",as);
}
D4 T1 #13 基础图论练习题
Problem

nn 个点,标号为 0,,n10,\cdots,n-1。给定两类边:

  1. 给定 di,wid_i,w_i,表示对于所有标号差为 did_i 的点对间连一条权值为 wiw_i 的边。
  2. 给定 ui,vi,wiu_i,v_i,w_i,表示连边 (ui,vi)(u_i,v_i),边权为 wiw_i

第一类边有 aa 组,第二类边有 bb 条。

现在图有若干个连通块,求和所有连通块的最小生成树边权总和,答案模 998244353998244353

n1018,a,b5×104n\leq 10^{18},a,b\leq 5\times 10^4

1s,512MB1s,512MB

partial: a=2,b=0a=2,b=0 | b=0b=0 | a200,b1000a\leq 200,b\leq 1000

Sol

*如果你会 border 理论,这下面的很多东西是显然的。但是我不会 border,所以全靠乱编!!1(然后写到一半反应过来这东西是border)

考虑按照边权从小到大加入所有边,求出加入每组/每条边后,图的连通块个数,这样即可结合边权得到答案。

首先考虑只有 aa 的情况,即:给定若干个 did_i,求它们使图变成了多少个连通块。

首先考虑 a=2a=2,此时如果 gcd(d1,d2)>1\gcd(d_1,d_2)>1,则图会按照 modgcd(d1,d2)\bmod \gcd(d_1,d_2) 分成若干部分,考虑一部分,内部变为 gcd(d1,d2)=1\gcd(d_1,d_2)=1 的情况。设此时点数为 mm,考虑这 mm 个点的连通块数量,此时相当于 xxx±d1,x±d2x\pm d_1,x\pm d_2 相连。可以发现,如果 d1+d2md_1+d_2\leq m,则存在一种遍历所有点的方式:如果当前编号大于等于 d2d_2 则走到 d2-d_2,否则 +d1+d_1。这样由于 gcd(d1,d2)=1\gcd(d_1,d_2)=1,可以遍历 [0,d1+d21][0,d_1+d_2-1] 内的所有点,从而这些点连通,进而所有 mm 个点连通。而如果 d1+d2>md_1+d_2>m,可以发现 [0,d1+d21][0,d_1+d_2-1] 这些点使用上述游走方式,通过两类边连成了一个大环,因此再删去点时连通块数量求出。这样解决了 a=2,b=0a=2,b=0 的情况。

但环的结构难以扩展到 a>2a>2 的情况,因此需要其它的考虑方式。

首先,分析上述过程可以发现,如果存在两个 d1,d2d_1,d_2 满足 d1+d2nd_1+d_2\leq n,则它们等效于一组 gcd(d1,d2)\gcd(d_1,d_2) 的边。使用上述过程容易证明。

因此可以重复这一过程,每次尝试合并最小的两个 did_i 直到不能合并,此时的 dd 满足任意两个 did_i 的和大于 nn,因此最多有一个 d1d_1 满足 d1n2d_1\leq \frac n2,剩余的 did_i 都大于 n2\frac n2

此时如果 d1>n2d_1>\frac n2,则中间的 n2d1n-2d_1 个点没有连边,考虑开头 nd1n-d_1 个点和结尾 nd1n-d_1 个点,将结尾这组边反过来编号,可以发现变为如下形式的问题:

有两组点 (0,0),(0,1),(0,0),(0,1),\cdots(1,0),(1,1),(1,0),(1,1),\cdots

现在有若干个 yi=vdiy_i=v-d_i,对于一个 yiy_i,它会在每一对 (0,yi),(1,yi1x)(0,y_i),(1,y_i-1-x) 间连边。

求出图的连通块数量。

如果 d1n2d_1\leq \frac n2,类似地考虑开头结尾各 d1d_1 个点,中间的点一定通过 d1d_1 这一组边和开头相连,不用再考虑。剩余的边都可以表述为上述形式。

但此时 d1d_1 也会连接开头结尾。它连接了编号为 i(i<d1)i(i<d_1)(n1(n1imodd1))(n-1-(n-1-i\bmod d_1)),即在上述形式中 (0,i),(1,(n1i)modd1)(0,i),(1,(n-1-i)\bmod d_1) 间的边。根据两侧和是否大于等于 d1d_1,这些边可以被描述为两组 yi=(n1)modd1,d1+((n1)modd1)y_i=(n-1)\bmod d_1,d_1+((n-1)\bmod d_1) 的边。这里第二组边可能超过 d1d_1 个点,但将超过的点扩充进来,它们会被这组边连接到某一组前 d1d_1 个点中,从而扩充后连通块数量不变。因此这种情况也被转化为了上述问题。

现在考虑上述问题的做法。手玩这种连边方式可以发现如下性质:

如果存在 yi=a,by_i=a,b 的两组边,a>b,2b>aa>b,2b>a,则使用它们可以造出一组 yi=2bay_i=2b-a 的等价边。

构造方式:(0,x)(1,b1x)(0,ab+x)(1,2ba1x)(0,x)\to(1,b-1-x)\to (0,a-b+x)\to (1,2b-a-1-x),依次使用 b,a,bb,a,b,可以发现每一步一定合法。

从大到小考虑所有的 yiy_i,该性质相当于如果当前考虑的是 yy,上一个权值为 y+ay+a,则可以造出一个 yay-a

考虑一直使用该性质,直到出现两个不同的距离:y+a,y,yby+a,y,y-b,其中 b<ab<a,上一步相当于跳到镜像位置,考虑三个位置的距离,可以发现是辗转相减的过程:(a,b)(b,ab)(a,b)\to (b,a-b)\to \cdots

考虑维护这样的过程,记录当前考虑到的 yy 以及之前的差 dd,考虑下一个元素 aa

首先让 yy 不断减 dd 直到再减小于 aa,如果此时 y=ay=a 则这样就考虑了 aa

否则,考虑将 dd 变为 yay-a,为了处理辗转相减的过程,在变换之前向后面加入一个元素 ydy-d,然后继续。

根据辗转相减的过程,可以发现这样处理后,dd 只会变化 O(logn)O(\log n) 次,从而这样操作后所有的 yy 构成了 O(logn)O(\log n) 段等差数列。上述过程的复杂度为 O((a+logn)loga)O((a+\log n)\log a)

然后考虑算连通块数量(接下来是意识流证明):

考虑相邻两个差 a,ba,b,如果 2ba2b\geq a,那么根据之前操作一定存在 2ba2b-a,考虑这三组边,可以发现 aa 这组边可以将 [b,a1][b,a-1] 部分映射到 [0,b1][0,b-1] 部分中,再通过 bb 这组边,可以实现将下标第二维对 aba-b 取模。除此之外,这组边的作用一定可以被 b,2bab,2b-a 替代出来。那么接下来可以只考虑 [0,b1][0,b-1] 部分,这里不会增加连通块。

否则,2b<a2b<a,有一些部分可能不能被连到 [0,b1][0,b-1] 里面。设上一步之前的差为 kk,则上一步中一个点 (0,i)(0,i) 可以到 (0,imodk)(0,i\bmod k),也可以到 (1,(a1i)modk)(1,(a-1-i)\bmod k),如果这两者有一个在 [0,b1][0,b-1] 中,则它可以被连下去。否则之前的边都无法让它连到这一部分,因此不能被连下去,从而这部分会贡献的连通块数量为满足 imodkb,(a1i)modkbi\bmod k\geq b,(a-1-i)\bmod k\geq bi[0,k1]i\in[0,k-1] 数量。根据上述过程,akba-k\leq b,因此两个能连下去区间会有一个 amodka\bmod k 的重叠,能连下去的 ii 数量等于 2b(amodk)2b-(a\bmod k),新增连通块数量等于 aa 减去这个值。

再考虑最后一个差 lsls,可以发现它会贡献 lsls 个连通块,第 ii 个为 (0,i)(1,ls1i)(0,i)-(1,ls-1-i),如果这些还能连接,则可以通过上述方式造出更小的差。

这样得到了连通块数量。(我也不知道为啥对,但是它过了,也可以参考border理论)

然后考虑 aa 很大的情况。注意到对于 di>n2d_i>\frac n2 的部分,它们最后能被 O(logn)O(\log n) 段等差数列表示,可以发现每段保留两个元素就可以表示处这段等差数列,那么每一段保留两个,再保留可能存在的 n2\leq \frac n2d1d_1,这 O(logn)O(\log n) 个数即可表示所有第一类边的连边情况。

这样可以在每次加入第一类边后求新的表示,只保留需要部分。从而每次加入时只需要求 O(logn)O(\log n) 组边的表示。直接做即可做到 O(alogalogn)O(a\log a\log n)。这解决了 b=0b=0 的问题。

然后考虑加入第二类边,同样考虑每次加边后算连通块数,即求出在上述连边的情况上额外加 bb 条边,连通块数会减少多少。

如果能求出在加入第一类边后,这 bb 条边的端点属于哪个连通块,则容易得到额外减少的连通块数量:再做一次并查集,求出额外合并了多少次即可。

那么考虑如何对于每个连通块中的点得到一个相同的表示。考虑将一个点放入之前求连通块个数的过程,每一段等差数列后一个点可以连到 (0,imodk)(0,i\bmod k),或者 (1,(a1i)modk)(1,(a-1-i)\bmod k)。如果两个中有一个能进入下一段等差数列则选择这个继续,如果两个都能进入,则此时一定存在一段 aka-k,进而任取一段最后都能得到相同的结果。如果两段都不能进入,那么考虑取 imodki\bmod k 作为它的表示,可以发现 imodki\bmod k 一定是这一个连通块中编号最小的点。

那么这样可以 O(logn)O(\log n) 求出表示,如果每次加入第一类边后重新求表示,则复杂度为 O(ablogn+alogalogn)O(ab\log n+a\log a\log n),可以获得 7676 分。

考虑一个点的表示变化的过程,可以发现这个过程类似于每次取模(无论是否改变 d1d_1,操作都类似于将一些等差数列的差换成它的某个约数),因此可以发现一个点的表示变化只会有 O(logn)O(\log n) 次。

同时表示的变化显然是一个合并的过程。从合并点的角度考虑,如果合并时两个点已经通过第二类边连通,则可以减少一条第二类边,即第二类边能减少的连通块数量减一。因此考虑如下做法:

考虑对所有出现过的数的所有可能表示建出点,维护并查集。加入第二类边 (u,v)(u,v) 时,如果 u,vu,v 已经连通则这条边没有用处,否则这条边会额外减少一个连通块。

加入第一类边 dd 时,如果表示发生了变化,对于一个变化 aba\to b,考虑并查集中对 (a,b)(a,b) 连边,如果两点之前就连通,则说明加入这组边后,第二类边可以额外合并的次数减少了 11

这样即可维护出每次加边后,除去第二类边额外能合并多少次,因此可以求出答案。一种方式是先无视第二类边求出答案,然后重新顺序考虑,如果上述过程中第二类边成功加入,则答案加上对应 wiw_i,同时加入第一类边时,每有一次合并时已经连通的情况,答案减去这组边的 wiw_i

这部分复杂度 O(blognα(blogn))O(b\log n\alpha(b\log n)),只需要再考虑求出表示变化的所有时刻。

最直接的方式是向前倍增找表示变化的位置,这样的复杂度为 O(blog2alogn)O(b\log^2 a\log n),卡卡常应该能过。

稍微优化一点的方式是求表示时二分跳过前面一段 b>xb>x 的部分,这样因为真正的变化不超过 O(logn)O(\log n) 次,复杂度变为 O(blogaloglogalogn)O(b\log a\log\log a*\log n),但和上一种差不多。

进行一些常数优化后,甚至可以比log^2的std还快

最后好像有优秀的 O(blog2)O(b\log^2) 求法,但是我懒了。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
using namespace std;
#define N 100500
#define M 2142503
#define ll long long
#define mod 998244353
ll n,a,b,c,d,e;
struct sth1{ll v,a,b;}sr[N];
bool cmp(sth1 a,sth1 b){return a.v==b.v?a.a<b.a:a.v<b.v;}
ll ci,as;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
vector<ll> s0;
struct sth{ll a,b;};
vector<sth> s1,st[N];
ll rv,ri[N];
ll init_s()
{
	sort(s0.begin(),s0.end());
	vector<ll> tp;
	for(int i=0;i<s0.size();i++)if(s0[i]&&(!i||s0[i]!=s0[i-1]))tp.push_back(s0[i]);
	s0=tp;
	ll nw1=1,vl=s0[0];
	while(nw1<s0.size())
	{
		ll v1=s0[nw1];
		if(v1+vl>n)break;
		else vl=gcd(vl,v1),nw1++;
	}
	s0[0]=vl;
	if(s0[0]*2<=n)rv=s0[0];else rv=-1,nw1=0;
	set<ll> t1;
	for(int i=nw1;i<s0.size();i++)t1.insert(n-s0[i]);
	if(rv!=-1)t1.insert((n-1)%rv+1),t1.insert((n-1)%rv+1+rv);
	ll nw=-1,de=-1;
	s1.clear();
	while(!t1.empty())
	{
		ll u=*t1.rbegin();t1.erase(u);
		if(nw==-1)
		{
			nw=de=u;
			s1.push_back((sth){nw,de});
			continue;
		}
		nw-=(nw-u)/de*de;
		if(nw==u)continue;
		if(nw-de>0)t1.insert(nw-de);
		else s1.push_back((sth){nw,nw});
		de=nw-u;if(de>u)de=u;
		s1.push_back((sth){u,de});
		nw=u;
	}
	vector<sth> s2;
	for(int i=0;i<s1.size();i++)if(i==0||(s1[i].a!=s1[i-1].a||s1[i].b!=s1[i-1].b))s2.push_back(s1[i]);
	s1=s2;
	ll ct=0;
	if(rv==-1)ct+=n-2*s1[0].a;
	for(int i=0;i+1<s1.size();i++)
	if(s1[i].b>s1[i+1].a*2-s1[i].a%s1[i].b)ct+=s1[i].b-s1[i+1].a*2+s1[i].a%s1[i].b;
	ct+=s1[s1.size()-1].b;
	return ct;
}
ll getid(ll x,ll rv,vector<sth> &s1)
{
	ll nw,fg;
	if(rv!=-1)fg=0,nw=x%rv;
	else
	{
		ll vl=s1.size()?s1[0].a:0;
		if(x<vl)fg=0,nw=x;
		else if(x>=n-vl)fg=1,nw=n-1-x;
		else return x;
	}
	ll ls=-1,li=n;
	int nt=0;
	for(int i=128;i;i>>=1)if(nt+i+1<s1.size()&&s1[nt+i+1].a>=nw)nt+=i;
	for(int i=nt;i<s1.size();i++)
	{
		if(nw>=s1[i].a)return fg?(li-1-nw)%ls:nw;
		ls=s1[i].b;li=s1[i].a;
		nw%=ls;
		if((li-1-nw)%ls<nw)nw=(li-1-nw)%ls,fg^=1;
	}
	return fg?(li-1-nw)%ls:nw;
}
int fa[M],c1;
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
ll ca,v2[N];
vector<sth> op[N];
struct hashtable{
	ll vl[M];
	int hd[M],id[M],nt[M],ct;
	#define djq 2097151
	void modify(ll x,int v)
	{
		vl[++ct]=x;id[ct]=v;
		nt[ct]=hd[x&djq];hd[x&djq]=ct;
	}
	int query(ll x)
	{
		for(int i=hd[x&djq];i;i=nt[i])if(vl[i]==x)return id[i];
		return -1;
	}
}si;
void solve(ll u)
{
	si.modify(u,++c1);
	ll lv=u;
	int nw=0,ls=c1;
	while(1)
	{
		for(int i=15;i>=0;i--)if(nw+(1<<i)<=a&&getid(u,ri[nw+(1<<i)],st[nw+(1<<i)])==lv)nw+=1<<i;
		if(nw==a)break;
		nw++;
		ll nt=getid(u,ri[nw],st[nw]),fg=0,ni=si.query(nt);
		if(ni==-1)ni=++c1,fg=1,si.modify(nt,c1);
		op[nw].push_back((sth){ls,ni});
		lv=nt;
		if(!fg)break;
	}
}
int main()
{
	scanf("%lld%lld%lld",&n,&a,&b);rv=ri[0]=-1;
	for(int i=1;i<=a;i++)scanf("%lld%lld",&c,&d),sr[i].v=d,sr[i].a=-1,sr[i].b=c;
	for(int i=1;i<=b;i++)scanf("%lld%lld%lld",&c,&d,&e),sr[i+a].v=e,sr[i+a].a=d,sr[i+a].b=c;
	sort(sr+1,sr+a+b+1,cmp);ci=n;
	for(int i=1;i<=a+b;i++)
	if(sr[i].a==-1)
	{
		s0.push_back(sr[i].b);
		ll nc=init_s();
		st[++ca]=s1;v2[ca]=sr[i].v;ri[ca]=rv;
		vector<ll> tp;
		if(rv!=-1)tp.push_back(rv);
		for(int j=0;j<s1.size();j++)
		{
			tp.push_back(n-s1[j].a);
			if(s1[j].a>s1[j].b)tp.push_back(n-s1[j].a+s1[j].b);
		}
		s0=tp;
		as=(as+1ll*(ci-nc%mod+mod)%mod*sr[i].v)%mod;
		ci=nc;
	}
	else
	{
		sr[i].a=getid(sr[i].a,rv,s1);
		sr[i].b=getid(sr[i].b,rv,s1);
	}
	for(int i=1;i<=a+b;i++)if(sr[i].a!=-1)
	{
		ll u=sr[i].a,v=sr[i].b;
		if(si.query(u)==-1)solve(u);
		if(si.query(v)==-1)solve(v);
	}
	for(int i=1;i<=c1;i++)fa[i]=i;
	ca=0;
	for(int i=1;i<=a+b;i++)
	if(sr[i].a==-1)
	{
		ca++;
		for(int j=0;j<op[ca].size();j++)
		{
			int u=op[ca][j].a,v=op[ca][j].b;
			if(finds(u)==finds(v))as=(as+mod-v2[ca])%mod;
			else fa[finds(u)]=finds(v);
		}
	}
	else
	{
		int u=si.query(sr[i].a),v=si.query(sr[i].b);
		if(finds(u)!=finds(v))fa[finds(u)]=finds(v),as=(as+sr[i].v)%mod;
	}
	printf("%lld\n",as);
}
D4 T2 #31 机器
Problem

求出如下最大费用流的答案:

除去源点汇点外,剩余边构成一个 nn 个点 mm 条边的有向图,每条边流量为 ++\infty,费用为 00

源点向第 ii 个点有 pip_i 条连边,第 jj 条边的流量为 11,费用为 viai,jv_i-a_{i,j}

ii 个点向汇点有 qiq_i 条连边,第 jj 条边的流量为 11,费用为 vibi,j-v_i-b_{i,j}

n2000,m2×104,p,q2000,a,b106,v108n\leq 2000,m\leq 2\times 10^4,p,q\leq 2000,a,b\leq 10^6,v\leq 10^8,所有 v,a,bv,a,b 非负且随机生成。

2s,1024MB2s,1024MB

Sol

流量最大可以到达 4×1064\times 10^6,直接流显然不能通过,但有一些乱搞的手段:

  1. 直接流可以得到大约 2828 分。
  2. 考虑按照边权确定一个可能的流量,然后预先流这么多再跑费用流调整,可以得到大约 5656 分。
  3. 如果图是外向树,可以模拟费用流。考虑缩点变为 DAG,然后直接把模拟费用流复制过来(不向上合并而是枚举每个可达点匹配)。问题是这样做显然是错的,但是可以得到 9292 分(WA1 TLE1),结合前述方式甚至可以得到 9696 分。

考虑加一条 TST\to S 的边变为最大费用循环流,然后考虑最大费用循环流的对偶:

考虑最大循环流的问题:

记点 ii 的出边集合为 OutiOut_i,入边集合为 IniIn_i,记边 jj 的流量上限为 vjv_j,费用为 cjc_j,流量为 xjx_j。那么最大循环流可以被表示为如下线性规划:

\max. &\sum _jx_jc_j\\ s. t. &\sum_{j\in In_i}x_j-\sum_{j\in Out_i}x_j=0,&\forall i\\ &x_j\leq f_j,&\forall j

考虑对线性规划进行对偶,记点的限制对应变量 yiy_i,边的限制对应变量 ziz_i,同时记边 jj 的入边,出边为 fj,tjf_j,t_j,则可以得到如下线性规划:

\min. &\sum_j f_jz_j\\ s. t. &y_{t_j}-y_{f_j}+z_j\geq c_j&\forall j\\ &y_j\ can\ be\ negative

此时 zz 容易处理:取最小值即可,因此问题变为找到一组任意的 yy,最小化 jfjmax(0,yfiyti+cj)\sum_j f_j*\max(0,y_{f_i}-y_{t_i}+c_j)

对本题进行对偶,记权值为 y1,,yn,yS,yTy_1,\cdots,y_n,y_S,y_T,则容量为 \infty 的边相当于限制 yfyty_f\leq y_t,此时可以发现问题变为如下形式:

找到一组 yy,满足对于图中的一条边 ftf\to t,有 yfyty_f\leq y_t,同时 yTySy_T\leq y_SSS 连向 ii 的边有代价 max(0,ySyi+viai,j)\max(0,y_S-y_i+v_i-a_{i,j})ii 连向 TT 的边有代价 max(0,yiyTvibi,j)\max(0,y_i-y_T-v_i-b_{i,j}),最小化总代价。

注意到这些代价都只考虑边权差,因此整体平移不影响情况,可以令 yT=0y_T=0,此时可以发现 ySy_S 越小越好,因此 yS=0y_S=0。此时可以发现不同点之间的代价独立,每个点代价为 max(0,yi+viai,j)+max(0,yivibi,j)\sum \max(0,y_i+v_i-a_{i,j})+\sum \max(0,y_i-v_i-b_{i,j})。这是一个上凸函数。

因此问题变为对有向图上每个点决定权值 yiy_i,满足如果存在边 ftf\to tyfyty_f\leq y_t,最小化 iVi(yi)\sum_i V_i(y_i),其中所有 VV 是上凸函数。

可以发现这相当于一个保序回归问题:

我们定义整数上的保序回归问题为:你需要决定 nn 个整数权值 yiy_i,满足若干条限制,每条限制为 yfyty_f\leq y_t。最小化 iVi(yi)\sum_i V_i(y_i),其中所有 VV 是上凸函数。

可以发现经典的 LiL_i 保序回归在只能取整数权值时是这一情况的一个特例:取 V=xiyikV=|x_i-y_i|^k 即满足上凸。(实数上的问题也可以类似解决,在此不考虑)

对于这类问题,有通用的整体二分做法:

考虑取任意整数 vv,限制所有数只能取 {v,v+1}\{v,v+1\} 权值,求出最优解。此时有如下结论:

如果一个数在此时的最优解中取 vv,则全局最优解中其取 v\leq v 的权值,否则其取 >v>v 的权值。

证明:考虑一个不满足这一条件的解,找到所有在此时最优解中取 vv,但该解中 >v>v 的点,设其构成集合 SS。考虑将这些值全部减一。则有此时最优解的合法性这样改变权值后仍然合法,且代价改变了 iSVi(vi1)Vi(vi)\sum_{i\in S} V_i(v_i-1)-V_i(v_i),由凸性这小于等于 iSVi(v1)Vi(v)\sum_{i\in S}V_i(v-1)-V_i(v),由当前解的最优性这小于等于 00,因此减一后不会变差。

对两个方向同时使用该操作,即可得到满足条件的最优解,

那么对于原问题,可以对权值整体二分,求出最优解。对于一个限制的问题, \leq 的限制相当于如果 ffv+1v+1,那么 ttv+1v+1,从而这相当于一个最大权闭合子图的形式,可以转化为最小割求出解。

实数的情况可以取 {v,v+ϵ}\{v,v+\epsilon\} 类似考虑。

本问题中,整体二分后求最大权闭合子图即可。最优权值显然在 [0,maxv+maxa][0,\max v+\max a] 之间,因此复杂度为 O(nlog(v+a)flow(n,m))O(n\log(v+a)*flow(n,m))

使用 dinic 求最大流,复杂度难以计算,但能过(1s)。

Code
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
#define N 2050
int n,m,v[N],s[N*10][2],a,b,as[N];
vector<int> s0[N],s1[N];
long long a1;

int head[N],cnt,cur[N],dis[N],vis[N];
struct edge{int t,next,v;}ed[N*59];
void adde(int f,int t,int v)
{
	ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;
	ed[++cnt]=(edge){f,head[t],0};head[t]=cnt;
}
bool bfs(int s,int t)
{
	queue<int> qu;
	for(int i=1;i<=n+2;i++)dis[i]=-1,cur[i]=head[i];
	qu.push(s);dis[s]=0;
	while(!qu.empty())
	{
		int u=qu.front();qu.pop();
		for(int i=head[u];i;i=ed[i].next)if(ed[i].v&&dis[ed[i].t]==-1)
		{
			dis[ed[i].t]=dis[u]+1;qu.push(ed[i].t);
			if(ed[i].t==t)return 1;
		}
	}
	return 0;
}
int dfs(int u,int t,int f)
{
	if(u==t||!f)return f;
	int as=0,tp;
	for(int &i=cur[u];i;i=ed[i].next)
	if(ed[i].v&&dis[ed[i].t]==dis[u]+1&&(tp=dfs(ed[i].t,t,min(f,ed[i].v))))
	{
		ed[i].v-=tp;ed[i^1].v+=tp;
		as+=tp;f-=tp;
		if(!f)return as;
	}
	return as;
}
void dfs1(int u)
{
	if(vis[u])return;vis[u]=1;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].v)dfs1(ed[i].t);
}
void solve(vector<int> si,int l,int r)
{
	if(si.empty())return;
	if(l==r)
	{
		for(int i=0;i<si.size();i++)as[si[i]]=l;
		return;
	}
	int mid=(l+r)>>1;
	for(int i=1;i<=n+2;i++)head[i]=0;cnt=1;
	for(int i=0;i<si.size();i++)
	{
		int u=si[i];
		int rv=s0[u].end()-lower_bound(s0[u].begin(),s0[u].end(),mid+1)-(lower_bound(s1[u].begin(),s1[u].end(),mid+1)-s1[u].begin());
		if(rv>=0)adde(u,n+2,rv);else adde(n+1,u,-rv);
	}
	for(int i=1;i<=m;i++)adde(s[i][1],s[i][0],1e9);
	while(bfs(n+1,n+2))dfs(n+1,n+2,1e9);
	for(int i=1;i<=n+2;i++)vis[i]=0;
	dfs1(n+1);
	vector<int> s1,s2;
	for(int i=0;i<si.size();i++)
	if(vis[si[i]])s1.push_back(si[i]);
	else s2.push_back(si[i]);
	solve(s1,l,mid);solve(s2,mid+1,r);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=m;i++)scanf("%d%d",&s[i][0],&s[i][1]);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		while(a--)scanf("%d",&b),s0[i].push_back(v[i]-b);
		sort(s0[i].begin(),s0[i].end());
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		while(a--)scanf("%d",&b),s1[i].push_back(v[i]+b);
		sort(s1[i].begin(),s1[i].end());
	}
	vector<int> vi;
	for(int i=1;i<=n;i++)vi.push_back(i);
	solve(vi,0,1.1e8);
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<s0[i].size();j++)if(s0[i][j]>as[i])a1+=s0[i][j]-as[i];
		for(int j=0;j<s1[i].size();j++)if(s1[i][j]<as[i])a1+=as[i]-s1[i][j];
	}
	printf("%lld\n",a1);
}
D4 T3 #48 数列重排
Problem

nn 个数字,每个数字都是 [0,m1][0,m-1] 间的整数。保证存在一个正整数 xx,使得每种数字出现的次数为 xxx+1x+1

给定 l,rl,rk[l,r]\forall k\in[l,r],求如下问题的答案,输出 k=lr(ansk233kmod998244353)\oplus_{k=l}^r (ans_k*233^k\bmod 998244353)

你可以任意排列数字组成序列,定义序列的一个子区间是好的当且仅当区间内元素的 mexkmex\geq k,求出好的区间数量的最大值。

n109,m107n\leq 10^9,m\leq 10^7

0.6s,256MB0.6s,256MB

partial: l=r=ml=r=m

Sol

首先考虑 k=mk=m 的问题,即最大化所有数全部出现的区间数量。

此时只有长度大于等于 mm 的区间可能合法,因此答案上界为 nn 个数中长度大于等于 mm 的区间数量。

在本题限制下,可以发现这个值是能达到的:记出现次数为 x+1x+1 的元素构成集合 SS,剩余元素构成集合 TT,则考虑构造 S,T,S,T,,SS,T,S,T,\cdots,S,同一集合使用相同顺序,可以发现这样满足条件。

然后考虑 kk 任意的问题,称 <k<k 的数为关键数,则一个区间合法当且仅当所有 kk 种关键数都出现过。

首先考虑关键数如何排列。显然最优方案为只要一个区间包含了 kk 个关键数它就合法。那么使用上述构造方式即可达到这一点。

接着考虑加入非关键数。设关键数一共有 ss 个,设第一个数前以及每一个数后分别插入了 c0,,csc_0,\cdots,c_s 个非关键数。此时考虑计算不合法的区间数,有三种不合法的区间:

  1. 区间两端都是非关键数,不合法当且仅当中间关键数数量小于 kk。考虑中间是否有关键数,可以得到这部分贡献为 iCci2+i<ji+k1cicj\sum_i C_{c_i}^2+\sum_{i<j\leq i+k-1}c_ic_j
  2. 一端是非关键数,另外一端是关键数。此时不合法当且仅当关键数到非关键数中间其它关键数数量小于 k1k-1,可以发现非关键数插入到每个位置的代价分别是 k1,k,k+1,,2k2,2k2,,2k2,2k3,,k,1k-1,k,k+1,\cdots,2k-2,2k-2,\cdots,2k-2,2k-3,\cdots,k,1,贡献为 civi\sum c_iv_i,其中 viv_i 为对应贡献。
  3. 两端都是关键数。这种情况的贡献在之前已经求出且为定值。

问题即为你需要找一组非负整数 cc,满足 ci\sum c_i 等于剩余的非关键数数量,并最小化上述式子贡献的区间数量。

考虑只有第一种贡献的情况。可以发现如下结论:

存在一种最优方案,只有 c0,ck,c2k,c_0,c_k,c_{2k},\cdots 处非零。

证明:调整。考虑将 c1,,ck1c_1,\cdots,c_{k-1} 全部加给 c0c_0,可以发现原先 [0,k1][0,k-1] 部分的贡献不变,而 [0,k1][k,2k1][0,k-1]-[k,2k-1] 部分的贡献都消失了,因此这样不会变差。接着调整 [k,2k1],[2k,3k1],[k,2k-1],[2k,3k-1],\cdots 即可得到。

再考虑第二种贡献,可以发现 [0,k1][0,k-1] 的调整不会出现问题:调整到 00 显然更优。之后贡献全部为 2k22k-2 的部分也不会出现问题,唯一的问题在于最后一个区间。但可以将最后一个区间改为全部移到结尾 csc_s,这样就最优了。

因此记 l=sk1l=\lfloor\frac sk\rfloor-1, 则存在一组最优解,只在 c0,ck,,clk,csc_0,c_k,\cdots,c_{lk},c_s 有值。此时第一种代价中不同区间的部分就不存在了,因此变为如下代价:

Cc02+(k1)c0+Ccs2+(k1)cs+i=1lCcik2+(2k2)cikC_{c_0}^2+(k-1)c_0+C_{c_s}^2+(k-1)c_s+\sum_{i=1}^lC_{c_{ik}}^2+(2k-2)c_{ik}

考虑将方案描述为若干次给某个数加一的过程。可以发现对 c0,csc_0,c_s 加一的首次代价为 k1k-1,之后每次加一,即 k1,k,k+1,k-1,k,k+1,\cdots,其它位置首次代价为 2k22k-2,之后每次加一。相当于需要进行固定次数的加一操作,最小化总代价。

此时容易得到贪心策略:先依次操作开头结尾两个位置:k1,k1,k,k,k+1,k+1,k-1,k-1,k,k,k+1,k+1,\cdots,直到这部分代价到达 2k22k-2,接下来所有位置轮流操作。因为操作后代价增加,显然这样是最优的。

这样答案容易求出:依次模拟每一段的操作,每一段内一定是若干个数加了 t+1t+1 次,剩余数加了 tt 次,分别算出贡献即可。

因此在知道了 <k,k<k,\geq k 的数的个数后,通过上述过程可以 O(1)O(1) 求出答案。那么扫过去即可。

复杂度 O(m)O(m)

Code
#include<cstdio>
using namespace std;
#define N 10050000
#define mod 998244353
#define ll long long
int n,m,x,l,r,tp,as;
char s[N];
int main()
{
	scanf("%d%d%d%d%s",&m,&l,&r,&x,s+1);
	n=m*x;for(int i=1;i<=m;i++)n+=s[i]-'0';
	tp=1;
	int fg=1,su=0;
	if(l==0)as=1ll*n*(n+1)/2%mod;
	for(int i=1;i<=m;i++)
	{
		tp=233ll*tp%mod;
		su+=x+s[i]-'0',fg&=s[i]=='1';
		int rs=n-su,ci=x+fg+1;
		ll ri=1ll*(su*2-i+2)*(i-1)/2;
		int ti=2*(i-1);if(ti>rs)ti=rs;
		ri+=1ll*(i*2-1+ti/2)*(ti/2)/2+1ll*(i*2-1+ti-ti/2)*(ti-ti/2)/2;
		rs-=ti;
		int rv=rs%ci;
		ri+=1ll*(ci-rv)*(i*4-3+rs/ci)*(rs/ci)/2+1ll*rv*(i*4-3+rs/ci+1)*(rs/ci+1)/2;
		ri=1ll*n*(n+1)/2-ri;
		if(l<=i&&i<=r)as^=ri%mod*tp%mod;
	}
	printf("%d\n",as);
}
D5 T1 #14 Speike & Tom
Problem

给一棵 nn 个点的树,除去树边外有 mm 条额外边。

AB 两个人进行如下博弈:两个人在树上走,A 可以经过树边和额外边,B 只能经过树边。A 先走。每个人每次可以走一步或者不动。

如果两人走到同一个位置,则 B 获胜。如果游戏能无限进行下去则 A 获胜。

n(n1)n(n-1) 种使得两人初始位置不同的初始状态。求在这些状态中有多少个使得双方最优操作下 A 获胜。

n,m105n,m\leq 10^5

2s,512MB2s,512MB

Sol

考虑一条额外边的情况。这条额外边在树上加入了一个只有一条额外边的环。考虑该环,可以发现当环长 4\geq 4 时,只要 A 走到环上后还没有被抓住,则他一定能获胜:考虑从环上移动到额外边两侧,然后每步操作走到环上距离 B 更远的部分,这样距离至少为 22B 不能一步走过来。

记这样的环上的点为特殊点,则 A 走到特殊点且不被抓住就能获胜。

此时还可以发现,如果从一个点出发向两个不同的树边方向都能走到特殊点,则走到该点之后 A 一定能获胜:向远离 B 的一个方向走即可。

因此如果一个点向两个方向走都有特殊点,则它也可以成为特殊点,这样特殊点一定是连通的。那么可以对每条满足环长 4\geq 4 的非树边标记端点,再 dfs 一次找出所有的特殊点。

首先考虑没有特殊点的情况,这相当于每条额外边的两端在树上距离小于等于 22。此时可以发现 B 必胜:考虑 B 向着 A 走,如果 A 想跳出 B 当前位置的子树,则只能从 B 当前所在位置的某个儿子跳到 B 当前所在位置的父亲,但这样下一次 A 就会输。从而 A 无法跳出去,因此 B 必胜,答案为 00

这同时说明,如果 A 不走到某个特殊点,则 A 只在所有额外边的两端在树上距离小于等于 22 的部分移动,这样 B 即可获胜。因此 A 能获胜当且仅当他能在被 B 抓到之前走到某个特殊点。

考虑一组起始点的情况,如果 A 在特殊点则 A 直接获胜,考虑剩余情况,此时 A 在某个非特殊点构成的子树内,这个子树的父亲节点为特殊点。A 移动到特殊点且不被抓住即可获胜。此时考虑 B 的位置:

  1. B 也在同一个子树中。

A 初始在 uuB 初始在 vv,两点 LCA 为 ll。此时有如下结论:

A 获胜当且仅当,如果 A 到达 ll 后,B 在下一步不能到达 ll,或者 A 到达 ll 的父亲节点时 B 还没有到达 ll

证明:如果 B 可以更早到达 ll,则 B 停在 ll 处,A 不能离开 ll 的子树。而如果 A 先到达,那么接下来即使 A 只走树边向上,B 也无法追上。

同时,由于子树内额外边端点树上距离不超过 22,因此 A 向上必定经过 ll 或者 ll 的父亲节点。因此上述条件为充要条件。

因此,这种情况下 A 的策略是尽可能向上走,如果有能向上的额外边就用,B 的策略则是尽早来到 ll 处。即如果 A 一直向上的过程中跳过了 ll 直接到 ll 的父亲节点,则 B 需要在这一步之前到达 ll,否则 B 需要用不多于 A 的步数到达 ll

  1. B 不在这个子树中。

设子树根为 rr,根的父亲 ff 为特殊点。此时可以发现 B 只要先到达 ff,就能抓住 A

此时 A 在子树内的策略仍然是尽量向上,B 的策略是向 ff 走。

如果 rr 不再向外连出关键边,则 A 必定经过 ffB 能获胜当且仅当 Bff 需要的步数小于等于 Aff 需要的步数。

但还有一种特殊情况:如果 A 走到了 rr,下一步可能可以走到子树外距离 ff11 的点。

首先,走到非关键点是没有用的:这样也不能做到绕开 B。只需要考虑走到另外的关键点的情况。

uu 连向了另外一个关键点 gg,则 f,gf,g 由树边相连。如果 A 选择走这条边,则如果 B 初始位置不在以 ff 为根时 gg 的子树内,则 A 选择走这条边,可以相当于少走一步:B 获胜当且仅当 Bff 需要的步数小于等于 Auu 需要的步数。但如果 B 在这一子树内,则无法进行操作。

因此如果 uu 连出了两条连向不同关键点的额外边,则无论 B 在哪个子树内,A 都可以使用上述操作,变为 B 获胜当且仅当 Bff 需要的步数小于等于 Auu 需要的步数。如果没有这样的额外边,则是之前的情况。而如果正好有一条这样的额外边,则如果 B 在以 ff 为根对应点的子树内,A 就不能减少一步,否则 A 可以减少一步。

这样完成了所有情况的讨论,考虑如何优化。

一种方式是点分治,每次求出 a,ba,b 在分治中心不同子树内的情况,对于每条路径,需要记录路径上的关键点情况,沿着额外边走的情况,以及若干情况下的距离。然后即可直接按照距离合并,但是细节特别多。

另外一种方式是长链剖分。首先考虑第一种情况。考虑 A 向上走的过程,设 fu,if_{u,i} 表示 uu 子树内有多少个点满足 A 从该点出发,尽可能向上走(使用额外边),经过 uu 且使用了 ii 条边的方式,gu,ig_{u,i} 表示跳过了 uu,使用了 ii 条边的方式。考虑 B 向上走的过程,设 du,id_{u,i} 表示 uu 子树内距离 uuii 的点数。考虑 ff 的转移,如果 uu 向上连有额外边,则转移为 fg,gff\to g,g\to f,否则为 f,gff,g\to f。因为 f,gf,g 都不超过子树内深度,因此这部分合并可以和正常长链剖分一样合并:将数组反过来存储,向上时相当于向 ff 开头加一个 00,即翻转后一次 push_back,合并枚举小的一侧合并。

然后考虑计算 B 获胜的方案数,一点上的贡献为对于每一对不同子树 a,ba,bB 先到达 ll 的方案数,即形如 ijfa,idb,j\sum_{i\geq j}f_{a,i}d_{b,j},考虑在合并中计算贡献,则需要在长链剖分中对两个数组 a,ba,b 计算形如 ijaibj\sum_{i\leq j}a_ib_j 的结果。如果 bb 是长度小的一侧,则容易解决:枚举 jj,同时维护前缀和即可。如果 bb 是长度大的一侧,考虑维护总和,用 (a)(b)(\sum a)*(\sum b) 去减即可得到上一种情况。从而可以在 O(min(a,b))O(\min(|a|,|b|)) 中计算结果,从而第一类情况合并并计算的复杂度为 O(n)O(n)。(这里代码使用了维护后缀和的实现方式)

考虑第二类情况,对于第二类情况中的每个子树分开考虑。直接的想法是枚举 A 走上来需要多少步,以及走到 uu 还是 ff,然后 B 能获胜的起始点范围相当于查询子树外距离 ff 不超过某个距离的点数,以及某个方向上距离额外 +1+1 的点数,那么可以直接点分治(场上做法)。

但也存在完全使用长链剖分的做法。首先考虑 Bff 子树内的情况。此时如果没有 ff 某个子树方向上额外 +1+1 的限制,那么相当于统计有多少种 Bff 子树中但不在 uu 子树中的方式,使得 BA 先到达 ff。那么分别合并 f,g,df,g,d 即可得到结果。

考虑额外加一的情况,此时考虑枚举 Auu 需要的步数,然后与对应子树的 dd 算出额外贡献。由于每个子树只会被考虑一次(只有当这个子树内是非特殊点,但父亲是特殊点的时候需要考虑),因此这样复杂度还是线性。

剩下部分为 Bff 子树外的情况,此时相当于 Bff 的距离小于或小于等于 A 走到 ff 的距离(考虑特殊边是否指向 ff 父亲)。考虑将 A 子树内的情况看成若干个询问,每个询问为询问 Bff 子树外,且到 ff 的距离小于等于 kk 的方案数,每个询问同时有系数 qf,kq_{f,k}。那么每向上一步,可以发现要求的距离限制会减少 11,即 qq 向前一位,这可以看成翻转后的 pop_back。

考虑在关键点上继续合并,合并询问系数 qqB 的距离 dd,在一点时考虑 AB 的 LCA 为该点的情况,可以发现贡献为对 q,dq,d 做类似于第一种情况下的合并算贡献,向上合并时类似操作即可。这样解决了所有问题。

复杂度 O(n+m)O(n+m)

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 100500
#define ll long long
int n,m,a,b,head[N],cnt;
struct edge{int t,next;}ed[N*2];
void adde(int f,int t)
{
	ed[++cnt]=(edge){t,head[f]};head[f]=cnt;
	ed[++cnt]=(edge){f,head[t]};head[t]=cnt;
}
int f[N];
void dfs0(int u,int fa)
{
	f[u]=fa;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs0(ed[i].t,u);
}
int is[N],rt=0;
void dfs1(int u,int fa)
{
	f[u]=fa;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u),is[u]|=is[ed[i].t];
}
vector<int> rs[N];
ll as;
vector<int> su[N*4];//f g d q
int id[N][4],le[N],fg[N],sn[N];
void merge(int a,int s,int b,int t)
{
	int sa=su[id[a][s]].size(),sb=su[id[b][t]].size();
	if(sa<sb)sa^=sb^=sa^=sb,id[a][s]^=id[b][t]^=id[a][s]^=id[b][t];
	for(int i=0;i<sb;i++)su[id[a][s]][i+sa-sb]+=su[id[b][t]][i];
}
ll calc(vector<int> &sf,vector<int> &sg)
{
	ll as=0,a=sf.size(),b=sg.size();
	if(!a||!b)return 0;
	if(b<=a)for(int i=0;i<b;i++)as+=1ll*sf[i+a-b]*(sg[i]-(i?sg[i-1]:0));
	else
	{
		as=1ll*sf[a-1]*sg[b-1];
		for(int i=0;i<a;i++)as-=1ll*sg[i+b-a-1]*(sf[i]-(i?sf[i-1]:0));
	}
	return as;
}
void append(int x,int y)
{
	if(su[id[x][y]].size())su[id[x][y]].push_back(su[id[x][y]].back());
	else su[id[x][y]].push_back(0);
}
void dfs2(int u)
{
	for(int i=0;i<4;i++)id[u][i]=u*4-i;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=f[u])
	{
		dfs2(ed[i].t);
		if(le[ed[i].t]>le[sn[u]])sn[u]=ed[i].t;
	}
	le[u]=le[sn[u]]+1;
	su[id[u][2]].push_back(1);
	if(is[u])
	{
		for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=f[u]&&!is[ed[i].t])
		{
			int t=ed[i].t;
			for(int j=0;j<rs[t].size();j++)
			{
				int v=rs[t][j];
				if(is[v])fg[t]=fg[t]?-1:v;
			}
			if(fg[t]>0&&fg[t]!=f[u])
			{
				int sa=su[id[t][0]].size(),sb=su[id[fg[t]][2]].size();
				for(int j=0;j<sa&&j<sb;j++)
				as+=1ll*(su[id[t][0]][sa-j-1]-(sa-j-1?su[id[t][0]][sa-j-2]:0))*(su[id[fg[t]][2]][sb-j-1]-(sb-j-1?su[id[fg[t]][2]][sb-j-2]:0));
			}
			su[id[t][3]].resize(le[t]+1);
			if(!fg[t])append(t,0);
			merge(t,3,t,1);merge(t,3,t,0);
		}
		else if(ed[i].t!=f[u])
		{
			int t=ed[i].t;
			if(su[id[t][3]].size())su[id[t][3]].pop_back();
		}
		for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=f[u])
		{
			int t=ed[i].t;append(t,2);
			as+=calc(su[id[t][3]],su[id[u][2]]);
			as+=calc(su[id[u][3]],su[id[t][2]]);
			merge(u,2,t,2);merge(u,3,t,3);
		}
		for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=f[u]&&!is[ed[i].t]&&fg[ed[i].t]==f[u]&&f[u])
		{
			int t=ed[i].t;
			for(int j=0;j<su[id[t][0]].size();j++)
			su[id[u][3]][j+su[id[u][3]].size()-su[id[t][0]].size()-1]+=su[id[t][0]][j]-(j?su[id[t][0]][j-1]:0);
		}
	}
	else
	{
		su[id[u][0]].push_back(1);
		for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=f[u])
		{
			int fg=0,t=ed[i].t;
			append(t,2);
			for(int j=0;j<rs[t].size();j++)if(rs[t][j]==f[u])fg=1;
			if(fg)
			{
				as+=calc(su[id[t][1]],su[id[u][2]]);
				as+=calc(su[id[t][0]],su[id[u][2]]);
				as+=calc(su[id[u][1]],su[id[t][2]]);
				as+=calc(su[id[u][0]],su[id[t][2]]);
				merge(u,0,t,1);
				merge(u,1,t,0);
				merge(u,2,t,2);
			}
			else
			{
				append(t,0);
				as+=calc(su[id[t][1]],su[id[u][2]]);
				as+=calc(su[id[t][0]],su[id[u][2]]);
				as+=calc(su[id[u][1]],su[id[t][2]]);
				as+=calc(su[id[u][0]],su[id[t][2]]);
				merge(u,0,t,1);
				merge(u,0,t,0);
				merge(u,2,t,2);
			}
		}
		append(u,1);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs0(1,0);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&a,&b);
		if(f[a]==b||f[b]==a)continue;
		if(f[f[a]]==b||f[f[b]]==a||f[a]==f[b])rs[a].push_back(b),rs[b].push_back(a);
		else is[a]=is[b]=1,rt=a;
	}
	dfs1(rt,0);
	dfs2(rt);
	if(!rt)as=1ll*n*(n-1);
	printf("%lld\n",1ll*n*(n-1)-as);
}
D5 T2 #38 聚会
Problem

给定 nn,满足 n1,3(mod6)n\equiv 1,3(\bmod 6)

现在有一张 nn 个点的完全图,你需要将其分为若干个三元环,使得每条边正好出现一次。构造方案。

n3000n\leq 3000

1s,1024MB1s,1024MB

partial: n=6k+1n=6k+1 | n=6k+3n=6k+3

Sol

Google 一下 Steiner Triple System 找到一个 sts.pdf 你就会做了。

然后是翻译构造环节:

  1. n=6k+3n=6k+3

考虑将点每三个分为一组,组编号为 0,1,,2k0,1,\cdots,2k

S={0,1,,2k}S=\{0,1,\cdots,2k\},我们希望找到一个 S×SSS\times S\to S 的运算,满足如下性质:

  1. ab=baa*b=b*a
  2. aa=aa*a=a
  3. ababaca\neq b\to a*b\neq a*c

即如果写成乘法表的形式,这个矩阵需要满足对称,对角线上为 0,1,,2k0,1,\cdots,2k 且每一行每个元素正好出现一次。

考虑构造满足条件的运算。可以发现取 ab=12(a+b)(mod2k+1)a*b=\frac12(a+b)(\bmod 2k+1) 即可。

考虑存在该运算时如何构造,将点标号为 (i,0),(i,1),(i,2)(i,0),(i,1),(i,2),连接以下两类三元环:

  1. (i,0)(i,1)(i,2),0i2k(i,0)-(i,1)-(i,2),0\leq i\leq 2k
  2. (i,v)(j,v)(ij,(v+1)mod3),0i<j2k,0v<3(i,v)-(j,v)-(i*j,(v+1)\bmod 3),0\leq i<j\leq 2k,0\leq v<3

考虑两个点 (i,a),(j,b)(i,a),(j,b) 之间是否有连边,如果 i=ji=j 则通过第一种方式连了边,否则如果 a=ba=b 则通过第二种方式的 (i,v)(j,v)(i,v)-(j,v) 连了边。否则,aba\neq b,如果 b=a+1b=a+1,则连边一定由 (i,v)(ij,v+1)(i,v)-(i*j,v+1) 连出,而乘法表中每一行每个元素正好出现一次,因而这样可以正好将每条边连上一次。另外一种情况类似。

  1. n=6k+1n=6k+1

此时考虑分 2k2k 组:0,1,,2k10,1,\cdots,2k-1,加上一个特殊点 ss

注意到 S|S| 为偶数时,上述运算一定不存在:每种元素出现偶数次,对角线外对称,这说明对角线上每种元素出现偶数次,但这不行。

考虑上述形式的替代物。每种元素必须在对角线上出现偶数次,因此考虑让对角线上为 0,1,,k1,0,1,,k10,1,\cdots,k-1,0,1,\cdots,k-1,剩余限制不变。

一种构造方式是考虑计算 a+ba+b,然后将最后一位移到最高位:ab=k((a+b)mod2)+a+b2a*b=k((a+b)\bmod 2)+\lfloor\frac{a+b}2\rfloor,这满足条件。

此时考虑如下构造:

  1. (i,v)(j,v)(ij,(v+1)mod3),0ij<2k,0v<3(i,v)-(j,v)-(i*j,(v+1)\bmod 3),0\leq i\leq j<2k,0\leq v<3

此时由于 ik,ii=ik\forall i\geq k,i*i=i-k,因此与之前不同的是 ik,(i,0)(i,1)(i,2)\forall i\geq k,(i,0)-(i,1)-(i,2) 被连上了,而 (i,0)(ik,1),(i,0)-(i-k,1),\cdots 这三条边没有被连上。因此考虑加入以下两类边:

  1. (i,0)(i,1)(i,2),0i<k(i,0)-(i,1)-(i,2),0\leq i< k
  2. s(i,(v+1)mod3)(i+k,v),0i<k,0v<3s-(i,(v+1)\bmod 3)-(i+k,v),0\leq i<k,0\leq v<3

这样就完成了构造。

复杂度 O(n2)O(n^2)

Code
//http://math.ucdenver.edu/~wcherowi/courses/m6406/sts.pdf
#include<cstdio>
using namespace std;
int n;
int main()
{
	scanf("%d",&n);
	if(n%6==3)
	{
		n/=3;
		for(int i=0;i<n;i++)printf("%d %d %d\n",3*i+1,3*i+2,3*i+3);
		for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)
		{
			int rv=(i+j)*(n+1)/2%n;
			for(int k=0;k<3;k++)printf("%d %d %d\n",i*3+k+1,j*3+k+1,rv*3+(k+1)%3+1);
		}
	}
	else
	{
		n/=3;
		for(int i=0;i<n/2;i++)printf("%d %d %d\n",3*i+1,3*i+2,3*i+3);
		for(int i=0;i<n/2;i++)for(int k=0;k<3;k++)printf("%d %d %d\n",3*n+1,3*i+k+1,3*(n/2+i)+(k+2)%3+1);
		for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)
		{
			int rv=(i+j)%n;rv=(rv&1)*n/2+rv/2;
			for(int k=0;k<3;k++)printf("%d %d %d\n",i*3+k+1,j*3+k+1,rv*3+(k+1)%3+1);
		}
	}
}
D5 T3 #79 细菌
Problem

有一个 n×m×kn\times m\times k 的三维网格。初始时每个位置权值均为 11

每经过一个时刻,网格中每个位置的权值会变为与其相邻(公共面)的六个位置上一时刻的权值和(如果一个位置不存在,则对应权值为 00)。

求出 tt 时刻后,位置 (a,b,c)(a,b,c) 的权值,答案模 998244353998244353

n,m,k,t1.2×105n,m,k,t\leq 1.2\times 10^5

2s,512MB2s,512MB

Sol

倒过来考虑,问题相当于你从 (a,b,c)(a,b,c) 开始走 tt 步,每一步可以走到一个相邻位置,但不能出界,求走正好 tt 步的方案数。

此时可以发现三维的移动和限制是互相独立的。因此考虑求出 fxtfx_{t} 表示在 xx 方向上走 tt 步的合法方案数,fy,fzfy,fz 同理,则答案为:

i+j+k=tCti,j,kfxifyjfzk\sum_{i+j+k=t}C_{t}^{i,j,k}fx_ify_jfz_k

那么求出 fx,fy,fzfx,fy,fz 后只需要做 EGF 卷积即可得到答案。接下来考虑求单个 fxfx 的过程。

此时相当于从 (0,a)(0,a) 出发走 kk 步,每步只能向右上或右下,要求不碰到 y=0,y=n+1y=0,y=n+1 的方案数。可以发现这是一个 "多次翻折" 模型:

多次翻折问题定义为上述形式:求从 (0,a)(0,a) 出发走 kk 步,每步只能向右上或右下,要求不碰到 y=0,y=n+1y=0,y=n+1 的方案数。

如果只有一条边界限制,要求最后走到 y>0y>0 的部分,则经典做法是翻折:考虑碰到了边界的方案,对于这些方案在第一次碰到边界时翻折,可以对应走到了 y<0y<0 的所有方案。同时可以发现,所有走到 y<0y<0 的方案可以通过在第一次碰到边界时翻折,对应一种碰到边界的上述方案,因此两者一一对应,答案为不考虑限制,走到 y>0y>0 的方案数减去走到 y<0y<0 的方案数。

现在有两条折线,要求最后走到 y[1,n]y\in[1,n] 的部分。考虑各翻折一次,减去走到 y[n,1],y[n+2,2n+1]y\in[-n,-1],y\in[n+2,2n+1] 的方案数。这样分别减去了经过上边界,下边界的方案数,但可以发现如果一个路线同时经过了两个边界,则会被减两次。

那么考虑加上同时经过两个边界的情况。首先考虑先经过了上边界,接着又经过了下边界的方案。首先考虑在第一次经过上边界处翻折(这样接下来的下边界会被翻上去),然后再翻折下边界,可以发现这对应了 y[2n+3,3n+2]y\in[2n+3,3n+2] 的情况。而对于一条 y[2n+3,3n+2]y\in[2n+3,3n+2] 的曲线,先沿着 y=2n+2y=2n+2 翻折,再沿着 y=n+1y=n+1 翻折,就得到了经过上边界后经过下边界的方案,同理,另外一种情况会对应 y[2n1,n2]y\in[-2n-1,-n-2] 的路径。

但还存在问题:如果一条路径首先经过上边界,再经过下边界,再经过上边界,则上面两种情况都可以对应到它,因此它又被加了两次。那么考虑再减去翻折三次的情况,即 y[3n+4,4n+3],y[3n2,2n3]y\in[3n+4,4n+3],y\in[-3n-2,-2n-3]

接下来类似地 +4,5,+4,-5,\cdots,可以发现如果一条路径来回触碰边界 kk 次,则它的系数为 12+22+21=01-2+2-2+2\cdots1=0(每次翻折都是找到第一个位置,因此一种情况不会重复计数一条路径),因此这样可以解决多次翻折问题。翻折次数为 O(tn)O(\frac tn)

回到本题,多次翻折后,相当于不考虑限制,但在每个 yy 处停止有一个系数,系数为 0,1,1,,1,0,1,1,,10,1,1,\cdots,1,0,-1,-1,\cdots,-1 组成的长度为 2(n+1)2(n+1) 的循环。记这个循环为 vv,则需要对于每个 ii,求出 j=0iCijvai+2j\sum_{j=0}^iC_i^jv_{a-i+2j}。从多项式角度考虑,设 f(x)=vixif(x)=\sum v_ix^i,则需要对于每个 ii 求出 [x0]f(x)(x+1x)i[x^0]f(x)(x+\frac 1x)^i

考虑分治,如果需要求 [0,n][0,n] 的答案,则只有 [n,n][-n,n] 项是有用的。那么 [0,n2][0,\frac n2] 部分可以保留中间项继续做,[n2,n][\frac n2,n] 部分可以先求出 f(x)(x+1x)n2f(x)(x+\frac 1x)^{\frac n2},然后变为一个 n2\frac n2 的问题。分治的复杂度为 O(nlogn)O(n\log n),因此总复杂度为 O(tlog2t)O(t\log^2 t),可以奇偶分开做减少常数。

好像多项式也能做到 O(tlogt)O(t\log t),但是鸽子了。

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 263001
#define mod 998244353
int n,l1,l2,l3,s1,s2,s3;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int fr[N],ifr[N],gr[2][N*2],rev[N*2];
void init(int l=18)
{
	fr[0]=1;for(int i=1;i<=1<<l;i++)fr[i]=1ll*i*fr[i-1]%mod;
	ifr[1<<l]=pw(fr[1<<l],mod-2);for(int i=1<<l;i>=1;i--)ifr[i-1]=1ll*i*ifr[i]%mod;
	for(int s=2;s<=1<<l;s<<=1)for(int i=1;i<s;i++)rev[i+s]=(rev[(i>>1)+s]>>1)+((i&1)*(s>>1));
	for(int t=0;t<2;t++)
	for(int s=2;s<=1<<l;s<<=1)
	{
		int tp=pw(3,(mod-1)/s);
		if(!t)tp=pw(tp,mod-2);
		int vl=1;
		for(int i=0;i<s>>1;i++)gr[t][s+i]=vl,vl=1ll*vl*tp%mod;
	}
}
int f[N],g[N],ntt[N];
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)ntt[rev[i+s]]=a[i];
	for(int l=2;l<=s;l<<=1)
	for(int i=0;i<s;i+=l)
	for(int j=0;j<l>>1;j++)
	{
		int v1=ntt[i+j],v2=1ll*ntt[i+j+(l>>1)]*gr[t][j+l]%mod;
		ntt[i+j]=(v1+v2)%mod;ntt[i+j+(l>>1)]=(v1+mod-v2)%mod;
	}
	int tp=t?1:pw(s,mod-2);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*tp%mod;
}

int v1[N],v2[N],v3[N];
int as[N];
void solve(int n,int lb,vector<int> si)
{
	if(n<=64)
	{
		for(int i=0;i<=n;i++)
		{
			as[i+lb]=si[n];
			for(int i=n*2;i>=1;i--)si[i]=(si[i]+si[i-1])%mod;
			for(int i=0;i<n*2;i++)si[i]=(si[i]+si[i+1])%mod;
		}
		return;
	}
	int mid=n>>1,l=1;
	while(l<=n*3)l<<=1;
	for(int i=0;i<l;i++)f[i]=g[i]=0;
	for(int i=0;i<=mid*2;i++)g[i]=1ll*fr[mid*2]*ifr[i]%mod*ifr[mid*2-i]%mod;
	for(int i=0;i<=n*2;i++)f[i]=si[i];
	dft(l,f,1);dft(l,g,1);for(int i=0;i<l;i++)f[i]=1ll*f[i]*g[i]%mod;dft(l,f,0);
	vector<int> s1,s2;
	for(int i=0;i<=(n-mid)*2;i++)s1.push_back(f[mid*2+i]);
	for(int i=0;i<=(mid-1)*2;i++)s2.push_back(si[n-(mid-1)+i]);
	solve(mid-1,lb,s2);
	solve(n-mid,lb+mid,s1);
}
void calc(int n,int m,int s,int *v)
{
	vector<int> s1,s2;
	int tp=n/2;
	for(int i=0;i<=tp*2;i++)
	{
		int rt=s+tp*2-i*2;
		rt=(rt%(2*m+2)+2*m+2)%(2*m+2);
		if(rt%(m+1)==0)s1.push_back(0);
		else if(rt<m+1)s1.push_back(1);
		else s1.push_back(mod-1);
		int rv=0;
		rt=(rt+2*m+1)%(2*m+2);
		if(rt%(m+1)!=0)rv+=rt<m+1?1:-1;
		rt=(rt+2)%(2*m+2);
		if(rt%(m+1)!=0)rv+=rt<m+1?1:-1;
		s2.push_back((mod+rv)%mod);
	}
	solve(tp,0,s1);for(int i=0;i<=tp;i++)v[i*2]=as[i];
	solve(tp,0,s2);for(int i=0;i<=tp;i++)v[i*2+1]=as[i];
}
int main()
{
	init();
	scanf("%d%d%d%d%d%d%d",&n,&l1,&l2,&l3,&s1,&s2,&s3);
	calc(n,l1,s1,v1);
	calc(n,l2,s2,v2);calc(n,l3,s3,v3);
	for(int i=0;i<=n;i++)v1[i]=1ll*ifr[i]*v1[i]%mod,v2[i]=1ll*ifr[i]*v2[i]%mod,v3[i]=1ll*ifr[i]*v3[i]%mod;
	int l=1;while(l<=n*2)l<<=1;
	dft(l,v1,1);dft(l,v2,1);
	for(int i=0;i<l;i++)v1[i]=1ll*v1[i]*v2[i]%mod;dft(l,v1,0);
	int rs=0;
	for(int i=0;i<=n;i++)rs=(rs+1ll*v3[i]*v1[n-i])%mod;
	printf("%d\n",1ll*rs*fr[n]%mod);
}
D6 T1 #11 圆滚滚的算术占卜
Problem

nn1010 进制下表示的数位和为 s(n)s(n),对于正整数 nn,使用如下方式定义 f(n)f(n)

考虑如下过程:有一个数 kk,初始 k=nk=n,接下来不断令 k:=ks(k)k:=k-s(k),直到 k=0k=0 停止。

将过程中每一步之前的 kk 按顺序写下来并拼接(包含开始的 nn,不包含结束的 00),得到的结果即为 f(n)f(n)

TT 组询问,每次给定 l,rl,r,求出 i=lrf(i)\sum_{i=l}^rf(i),答案对 998244353998244353 取模。

T5×104,1lr1018T\leq 5\times 10^4,1\leq l\leq r\leq 10^{18}

3s,2048MB3s,2048MB

partial: l=rl=r

Sol

以下记 BB 为进制数,ww 为数字的位数。但为了便于理解,以下在处理数时全部带入了 B=10B=10,即所有的 1010 都应该被认为是 BB99 应该是 B1B-1

首先考虑如何求出一个数的变化,即 l=rl=r 的情况。

此时数位和不超过 w(B1)w(B-1),即每次减去的数不超过这个值。那么连续 w(B1)w(B-1) 个位置中必定有一个位置被变化过程经过。

同时可以发现,如果一段操作过程中没有向某一位及之上的部分借位,那么高位对这段变化过程的影响只与高位数位和有关。

因此考虑将状态记为,当前低 kk 位的值为 10ka(a[1,w(B1)])10^k-a(a\in[1,w(B-1)]),高位的数位和为 ss,一直操作直到需要向 kk 位之上借位时停止,停止时低 kk 位的值变成了负多少(在借位前)。

然后考虑在状态中维护 ff,拼接过程与数的位数有关,因此需要再记录总的位数 ll。然后考虑一段操作对 ff 的影响,一段操作相当于在原先的 ff 之后拼接一段数字串,即将之前的 ff 乘上某个 10x10^x,再加上某个 yy。这里的 yy 不仅与低 kk 位有关,也与高位有关,但可以发现它与高位的关系是一个一次函数:一个数的值与其高位是线性关系。

因此,我们设状态为 dpl,k,s,adp_{l,k,s,a} 表示当前数总共有 ll 位,低 kk 位为 10ka(a[1,w(B1)])10^k-a(a\in[1,w(B-1)]),高位数位和为 ss,此时一直操作到需要向 kk 位之上借位为止,该状态记录如下结果:

  1. 减到需要借位时,低位差的值 ntnt,显然 nt[1,l(B1)]nt\in[1,l(B-1)]。可以发现 ntnt 即为转移到的下一个状态的 aa
  2. 这一段拼接的数字串对数字的影响,该影响可以用三个数 a,b,ca,b,c 表示,含义为如果之前数字串值为 vv,当前高位值为 hihi,则拼接后数字串值为 va+hib+cv*a+hi*b+c。两个 (a,b,c)(a,b,c) 顺序合并的形式显然,但注意这个合并没有交换律。

考虑如何转移一个状态。对于一般的情况,可以从 dpl,k1dp_{l,k-1}dpl,kdp_{l,k} 转移:考虑一个 dpl,kdp_{l,k} 的问题,此时低 kk 位为 10ka10^k-a,考虑将第 kk 位看成高位,通过低 k1k-1 位的情况完成转移。首先第 kk 位为 99,低位为 10k1a10^{k-1}-a,因此低位状态为 dpl,k1,s+9dp_{l,k-1,s+9},但向上转移后第 kk 位不再是高位,因此需要处理在这一段内部第 kk 位作为高位时的影响,即在状态中给 cc 加上 b10k19b*10^{k-1}*9,然后向上拼接转移。接下来的情况为第 kk 位为 88,因此依次考虑 dpl,k1,s+8,dp_{l,k-1,s+8},\cdots,考虑每一个低位即可完成转移。

kk 小时,可能出现 10ka<010^k-a<0 的情况,此时约定如果 10ka<010^k-a<0,则直接转移到下一个 (a10k)-(a-10^k) 的状态。当 k=0,a=0k=0,a=0 时,进行一次拼接操作,令 a=10l,b=0,c=1a=10^l,b=0,c=1,此时数变为 100s10^0-s,因此向下转移到的状态为 a=s1a=s-1。在 k=0k=0 时这样处理,k>0k>0 时使用之前的转移即可。

如果 l=kl=k,则减到 10k110^{k-1} 之下时情况与之前不同:没有高位了,此时需要特殊处理。一种方式是额外记录状态 dp1l,adp1_{l,a} 表示数为 10la10^l-a 时,转移的结果。那么 dp1ldp1_l 可以首先通过上述转移处理最高位为 9,8,,19,8,\cdots,1 的部分,最后的情况由 dp1l1dp1_{l-1} 得到。

注意到操作一次后,数一定是 B1B-1 的倍数,因此对于每个 dpl,k,sdp_{l,k,s} 只有 ll 个可能的 aa,而不是 l(B1)l(B-1) 个。只记录合法状态,按照 ss 从小到大,ll 从小到大的顺序转移,复杂度为 O(w4B2)O(w^4B^2)

对于一个 l=rl=r 的询问,先通过 dpdp 依次转移每个低位,然后用 dp1ldp1_l 解决。每个低位因为当前位不一定从 99 开始而是可能从中间某个值开始,因此枚举低位做,单组询问的复杂度为 O(wB)O(wB)。这样可以解决 l=rl=r 的问题。

但询问也可以做到更优,这样可以避免下一部分过不去的问题。考虑将询问中需要用到的状态也处理出来,即当前低 kk 位值为 x10k1ax*10^{k-1}-a 的情况,记这个状态为 dpsl,k,x,s,adps_{l,k,x,s,a},上述转移可以看成 x=10x=10 的特例。

之前的转移相当于从 x=10x=10 的状态开始,先设高位为 99,直到转移到 x=9x=9 的状态,然后依次转移到 x=8,,1,0x=8,\cdots,1,0 的状态完成转移。可以发现将这个过程反过来做,即可在不影响复杂度的情况求出所有 dpsdpsdpsl,k,0dps_{l,k,0} 为结束(不进行操作)的状态,每个 dpsl,k,idps_{l,k,i} 可以通过一步转移从 dpsl,k,i1dps_{l,k,i-1} 推出来,最后 dpl,k=dpsl,k,10dp_{l,k}=dps_{l,k,10}。这样预处理复杂度不变,询问直接使用 dpsdps,复杂度变为 O(w)O(w)

然后考虑 l,rl,r 任意的情况。此时问题可以转化为询问 ff 的前缀和。

在上述转移过程中,一个数的转移只和 l,k,s,al,k,s,a 有关,因此考虑将状态相同或类似的数一起处理。因此设 sul,k,s,asu_{l,k,s,a} 表示考虑总共 ll 位,低 kk 位任意,高位和为 ss 的情况中,这 10k10^k 个数中满足转移到离开这个 10k10^k 区间时状态为 aa 的数的转移和,记录如下权值:

  1. 所有数字串的 ff 之和。由上一部分,每个串的 ff 都可以表示为 bihi+cib_i*hi+c_i,其中 hih_i 为高位权值,因此可以记录 b,cb,c 表示 bi,cib_i,c_i 分别的和。
  2. 此时只会向这些 ff 后面加数字串,因此不需要再记录 aa 或者类似值。

考虑 susu 的转移,枚举第 kk 位的值,同样进行加一位的处理,然后通过 dpsdps 转移即可得到这一部分 sul,k1,s+isu_{l,k-1,s+i}sul,ksu_{l,k} 的贡献。这样完成了转移。

然后考虑询问 i=0rf(i)\sum_{i=0}^rf(i),同样从低位开始转移,首先考虑最低位,处理所有与 rr 除去最低位外全部相同的元素,这些元素处理后得到 k=1k=1,高位与 rr 的高位相同的状态,然后考虑第 22 位,处理第 22 位之上全部与 rr 相同,第 22 位小于 rr 的这些元素,同时将上一部分得到的 k=1k=1 的状态通过这一位转移到 k=2k=2,两部分都会变为 k=2k=2,高位与 rr 的高位相同的状态,向上类似处理即可。最后处理到 kkrr 的位数相同时,最后的转移使用 dp1dp1 即可。

但此时位数小于 rr 位数的数需要特殊处理。考虑在预处理时令 su1lsu1_l 表示 i=010l1f(i)\sum_{i=0}^{10^l-1}f(i),那么 su1lsu1_l 可以通过 su1l1su1_{l-1},以及高位为 1,2,,91,2,\cdots,9,低位任意的 sul,l1su_{l,l-1} 通过 dpsdps 的转移得到。

这样预处理复杂度为 O(w4B2)O(w^4B^2),询问时对于每一位,需要枚举当前位取值进行转移,复杂度为 O(w2B)O(w^2B),总复杂度 O(w4B2+Tw2B)O(w^4B^2+Tw^2B),应该能通过(但如果不压状态或者其它原因导致了 O(Tw2B2)O(Tw^2B^2),则完全无法通过)

这里仍然可以使用类似之前的优化,预处理 susl,k,x,s,asus_{l,k,x,s,a} 表示这一状态中所有第 kk 位小于 xx 的数的转移之和。与之前类似, susl,k,xsus_{l,k,x} 可以通过 susl,k,x1sus_{l,k,x-1} 加上当前位为 xx 的数得到,sul,k=susl,k,10su_{l,k}=sus_{l,k,10}。这样就可以再除以一个 BB

复杂度 O(w4B2+Tw2)O(w^4B^2+Tw^2)

Code
#include<cstdio>
using namespace std;
#define ll long long
#define mod 998244353
struct sth{int a,b,c;};
struct st1{int a,b,c;};
st1 operator +(st1 a,st1 b){return (st1){(a.a+b.a)%mod,(a.b+b.b)%mod,(a.c+b.c)%mod};}
st1 operator *(st1 a,sth b){return (st1){a.a,(1ll*a.b*b.a+1ll*b.b*a.a)%mod,(1ll*a.c*b.a+1ll*b.c*a.a)%mod};}
sth operator *(sth a,sth b){return (sth){1ll*a.a*b.a%mod,(1ll*a.b*b.a+b.b)%mod,(1ll*a.c*b.a+b.c)%mod};}
sth vl[19][19][167][19],rv[19][19],tr[19][19][11][167][19];
st1 dp[19][19][167][19],su[19][19][11][167][19],rs[19];
int nt[19][19][167][19],pw[19],st[19][19][11][167][19];
int id[167][167],rid[167][19];
void init_dp()
{
	pw[0]=1;for(int i=1;i<=18;i++)pw[i]=10ll*pw[i-1]%mod;
	for(int r=0;r<=162;r++)
	{
		int ci=0;
		for(int j=1;j<=162;j++)if((j+162-r)%9==1)id[r][j]=++ci,rid[r][ci]=j;
	}
	rv[0][1]=(sth){1,0,0};
	for(int i=1;i<=18;i++)
	{
		for(int r=1;r<=9*i;r++)
		for(int e=1;e<=i;e++)
		if(rid[r][e]>1)vl[i][0][r][e]=(sth){1,0,0},nt[i][0][r][e]=id[r-1][rid[r][e]-1];
		else vl[i][0][r][e]=(sth){pw[i],0,1},nt[i][0][r][e]=id[r-1][r];
		for(int d=1;d<i;d++)
		for(int r=1;r<=9*(i-d);r++)
		{
			for(int e=1;e<=i;e++)tr[i][d][0][r][e]=(sth){1,0,0},st[i][d][0][r][e]=e;
			for(int u=0;u<=9;u++)
			for(int e=1;e<=i;e++)
			{
				sth r1=vl[i][d-1][r+u][e];r1.b=(r1.b+1ll*u*pw[d-1]*r1.c)%mod;
				tr[i][d][u+1][r][e]=r1*tr[i][d][u][r][nt[i][d-1][r+u][e]],
				st[i][d][u+1][r][e]=st[i][d][u][r][nt[i][d-1][r+u][e]];
			}
			for(int e=1;e<=i;e++)vl[i][d][r][e]=tr[i][d][10][r][e],nt[i][d][r][e]=st[i][d][10][r][e];
		}
		for(int e=1;e<=i;e++)
		{
			int nw=e;
			sth tp=(sth){1,0,0};
			for(int u=9;u>0;u--)
			{
				sth r1=vl[i][i-1][u][nw];r1.b=(r1.b+1ll*u*pw[i-1]*r1.c)%mod;
				tp=tp*r1;nw=nt[i][i-1][u][nw];
			}
			rv[i][e]=tp*rv[i-1][nw];
		}
	}
	for(int i=1;i<=18;i++)
	{
		for(int r=1;r<=9*i;r++)dp[i][0][r][id[r-1][r]]=(st1){1,0,1};
		for(int d=1;d<i;d++)
		for(int r=1;r<=9*(i-d);r++)
		{
			for(int u=0;u<=9;u++)
			{
				for(int e=1;e<=i;e++)su[i][d][u+1][r][e]=su[i][d][u][r][e];
				for(int e=1;e<=i;e++)
				{
					st1 si=dp[i][d-1][r+u][e];si.b=(si.b+1ll*u*pw[d-1]*si.c)%mod;
					su[i][d][u+1][r][st[i][d][u][r][e]]=su[i][d][u+1][r][st[i][d][u][r][e]]+si*tr[i][d][u][r][e];
				}
			}
			for(int e=1;e<=i;e++)dp[i][d][r][e]=su[i][d][10][r][e];
		}
		rs[i]=rs[i-1];
		for(int u=1;u<=9;u++)
		for(int e=1;e<=i;e++)
		{
			st1 si=dp[i][i-1][u][e];si.b=(si.b+1ll*u*pw[i-1]*si.c)%mod;
			int nw=e;
			for(int y=u-1;y>0;y--)
			{
				sth r1=vl[i][i-1][y][nw];r1.b=(r1.b+1ll*y*pw[i-1]*r1.c)%mod;
				si=si*r1;nw=nt[i][i-1][y][nw];
			}
			rs[i]=rs[i]+si*rv[i-1][nw];
		}
	}
}
st1 s1[19],s2[19];
int query(ll rb)
{
	if(rb<=9)return rb*(rb+1)/2;
	int as=0;
	if(rb==1000000000000000000ll)rb--,as=753831110;
	int le=0;ll tp=1,t1=rb;
	while(tp<=rb)tp*=10,le++;
	for(int i=1;i<=18;i++)s1[i]=s2[i]=(st1){0,0,0};
	int sr=0;while(t1)sr+=t1%10,t1/=10;
	for(int d=1;d<le;d++)
	{
		int ri=rb%10;rb/=10;sr-=ri;
		for(int i=1;i<=18;i++)
		{
			st1 r1=s1[i];r1.b=(r1.b+1ll*ri*pw[d-1]*r1.c)%mod;
			s2[st[le][d][ri][sr][i]]=s2[st[le][d][ri][sr][i]]+r1*tr[le][d][ri][sr][i];
		}
		for(int i=1;i<=18;i++)s2[i]=s2[i]+su[le][d][ri+(d==1)][sr][i];
		for(int i=1;i<=18;i++)s1[i]=s2[i],s2[i]=(st1){0,0,0};
	}
	for(int i=1;i<=18;i++)s1[i].b=(s1[i].b+1ll*rb*pw[le-1]*s1[i].c)%mod;
	for(int u=rb-1;u>=1;u--)
	{
		for(int i=1;i<=18;i++)
		{
			sth r1=vl[le][le-1][u][i];r1.b=(r1.b+1ll*u*pw[le-1]*r1.c)%mod;
			s2[nt[le][le-1][u][i]]=s2[nt[le][le-1][u][i]]+s1[i]*r1;
		}
		for(int i=1;i<=18;i++)
		{
			st1 si=dp[le][le-1][u][i];si.b=(si.b+1ll*u*pw[le-1]*si.c)%mod;
			s2[i]=s2[i]+si;
		}
		for(int i=1;i<=18;i++)s1[i]=s2[i],s2[i]=(st1){0,0,0};
	}
	for(int i=1;i<=18;i++)as=(as+(s1[i]*rv[le-1][i]).b)%mod;
	as=(as+rs[le-1].b)%mod;
	return as;
}
int T;
ll l,r;
int main()
{
	init_dp();
	scanf("%d",&T);
	while(T--)scanf("%lld%lld",&l,&r),printf("%lld\n",(query(r)+mod-query(l-1))%mod);
}
D6 T2 #18 交朋友
Problem

提交答案。

给定一张 nn 个点的无向图。你需要在这个图中选择一些团,使得每条边都至少在这些团中出现过一次。

给定 limlim,如果你构造的团数量为 ss,则你的分数为 ptsmin(1,(lims)3)pts*\min(1,(\frac{lim}{s})^3)

以下除去#3外,图为随机生成,边生成的概率为 pp

idid 11 22 33 44 55 66 77 88 99 1010
nn 66 1010 5050 100100 100100 500500 500500 10001000 10001000 10001000
pp 12\frac 12 12\frac 12 二分图 13\frac 13 12\frac 12 15\frac 15 12\frac 12 15\frac 15 13\frac 13 12\frac 12
limlim 44 99 =m=m 288288 208208 39353935 26212621 1238612386 1119811198 84868486
ptspts 44 88 66 99 99 1111 1111 1313 1313 1616
Sol

考虑一个一个加入团,每次随机一条没有被覆盖的边,随机扩展到一个极大团然后加入。这样能过前三个显然的点,但后面非常不行,得分大概在 2626 左右。

考虑优化上述做法,覆盖更多没有被覆盖过的边应当更优,因此考虑扩展极大团时,每次选择加入后覆盖到没有覆盖过的边数量最多的点,相同的情况随机一个加入。这样即可得到大约 5050 分。

注意到这样的贪心很难找到最优的团,但也没有很好的办法找到真正的最优解。考虑一种折中方式:每次扩展进行 kk 次上述贪心,取额外覆盖到的边数量最多的团加入。

可以发现这样在 n,rn,r 大的时候非常优秀。取 k=103k=10^3,可以在五分钟内通过 #9 和 #10,且 #10 能做到 s8000s\leq 8000。进一步取 k=104k=10^4 可以在 #10 做到 s7780s\leq 7780。但该做法在小数据情况下表现不好打不过调整,大约可以在二十分钟内获得 859085\sim 90 分。

然后考虑调整:在之前的方案上,每次删去一个团,重新加入一个团,要求重新加入的团必须覆盖那些之前只被删去团覆盖到的边。

同时,维护每条边的覆盖情况,维护每个团唯一覆盖的边数,每次选择唯一覆盖边数最小的团调整。

结合上一算法的初始解,实现较为正常的调整可以在半小时到一小时内通过 #4~#8,获得 100100 分。其中 #4,#5 可能需要多随机几次初始解避免卡死在 lim+1,lim+2lim+1,lim+2 或者类似情况。但这两组都能做到lim-1

本来调整跑 #10 要四五个小时,但是随机贪心一分钟秒了,所以很快。

Code

贪心:

#include<bits/stdc++.h>
using namespace std;
#define N 1059
mt19937 rnd(591821425+time(0)+(unsigned long long)new char);
int n,m,a,b,si[N][N],is[N][N],ci,ri,tp[N][N],vl[233333],v2[233333];
vector<int> as[233333];
int main()
{
	int id=6;
	char fu[233];
	sprintf(fu,"friends%d.in",id);
	freopen(fu,"r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),si[a][b]=si[b][a]=1;
	while(ri<m*2)
	{
		int mx=0;ci++;
		for(int t=1;t<=2333;t++)
		{
			int u=rnd()%n+1,v=rnd()%n+1,sv=1;
			vector<int> t1;
			while(1)
			{
				if(is[u][v]<si[u][v])break;
				u=rnd()%n+1,v=rnd()%n+1;
			}
			t1.push_back(u);t1.push_back(v);
			vector<int> rs;
			for(int i=1;i<=n;i++)if(si[u][i]&&si[v][i])rs.push_back(i);
			while(!rs.empty())
			{
				int mx=0;
				vector<int> s2;
				for(int i=0;i<rs.size();i++)
				{
					int v=rs[i],tp=0;
					for(int j=0;j<t1.size();j++)if(!is[t1[j]][v])tp++;
					if(tp>mx)mx=tp,s2.clear();
					if(tp==mx)s2.push_back(v);
				}
				sv+=mx;
				int a=s2[rnd()%s2.size()];
				t1.push_back(a);
				vector<int> sr;
				for(int i=0;i<rs.size();i++)if(si[a][rs[i]])sr.push_back(rs[i]);
				rs=sr;
			}
			if(sv>mx)mx=sv,as[ci]=t1;
		}
		for(int i=0;i<as[ci].size();i++)for(int j=0;j<as[ci].size();j++)
		{
			int u=as[ci][i],v=as[ci][j];
			if(u!=v&&!is[u][v])is[u][v]=1,ri++;
		}
	}
	cerr<<ci<<endl;
	sprintf(fu,"friends%d.out",id);
	freopen(fu,"w",stdout);
	printf("%d\n",ci);
	for(int i=1;i<=ci;i++,printf("\n"))
	{
		printf("%d ",as[i].size());
		for(int j=0;j<as[i].size();j++)printf("%d ",as[i][j]);
	}
}

调整:

#include<bits/stdc++.h>
using namespace std;
#define N 1059
mt19937 rnd(591821425+time(0)+(unsigned long long)new char);
int n,m,a,b,si[N][N],is[N][N],ci,ri,tp[N][N],v2[233333];
vector<int> as[233333];
int su[N][N];
set<int> st[N][N];
set<pair<int,int> > fu1;
void doit(int x,int y,int id)
{
	if(su[x][y]==1)v2[*st[x][y].begin()]--;
	st[x][y].insert(id);su[x][y]++;
	if(su[x][y]==1)v2[*st[x][y].begin()]++,ri++,fu1.erase(make_pair(x,y));
}
void doit1(int x,int y,int id)
{
	if(su[x][y]==1)v2[*st[x][y].begin()]--,ri--,fu1.insert(make_pair(x,y));
	st[x][y].erase(id);su[x][y]--;
	if(su[x][y]==1)v2[*st[x][y].begin()]++;
}
void expand()
{
	while(ri<m*2)
	{
		int mx=0;ci++;
		while(mx<m-ri/2)
		{
			int u=rnd()%n+1,v=rnd()%n+1,sv=1;
			int st=rnd()%fu1.size();
			set<pair<int,int> >::iterator it=fu1.begin();
			while(st--)++it;
			u=(*it).first,v=(*it).second;
			vector<int> t1;
			t1.push_back(u);t1.push_back(v);
			vector<int> rs;
			for(int i=1;i<=n;i++)if(si[u][i]&&si[v][i])rs.push_back(i);
			while(!rs.empty())
			{
				int mx=0;
				vector<int> s2;
				for(int i=0;i<rs.size();i++)
				{
					int v=rs[i],tp=0;
					for(int j=0;j<t1.size();j++)if(!su[t1[j]][v])tp++;
					if(tp>mx)mx=tp,s2.clear();
					if(tp==mx)s2.push_back(v);
				}
				sv+=mx;
				int a=s2[rnd()%s2.size()];
				t1.push_back(a);
				vector<int> sr;
				for(int i=0;i<rs.size();i++)if(si[a][rs[i]])sr.push_back(rs[i]);
				rs=sr;
			}
			if(sv>mx)mx=sv,as[ci]=t1;
		}
		for(int i=0;i<as[ci].size();i++)for(int j=0;j<as[ci].size();j++)
		{
			int u=as[ci][i],v=as[ci][j];
			if(u!=v)doit(u,v,ci);
		}
	}
}
void erase(int x)
{
	for(int i=0;i<as[x].size();i++)for(int j=0;j<as[x].size();j++)if(i!=j)doit1(as[x][i],as[x][j],x);
	if(x==ci){ci--;return;}
	for(int i=0;i<as[ci].size();i++)for(int j=0;j<as[ci].size();j++)if(i!=j)doit1(as[ci][i],as[ci][j],ci);
	as[x]=as[ci];as[ci].clear();
	for(int i=0;i<as[x].size();i++)for(int j=0;j<as[x].size();j++)if(i!=j)doit(as[x][i],as[x][j],x);
	ci--;
}
int main()
{
	int id=10;
	char fu[233];
	sprintf(fu,"friends%d.in",id);
	freopen(fu,"r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),si[a][b]=si[b][a]=1,fu1.insert(make_pair(a,b)),fu1.insert(make_pair(b,a));
	sprintf(fu,"friends%d.out",id);
	freopen(fu,"r",stdin);
	scanf("%d",&ci);
	for(int i=1;i<=ci;i++)
	{
		scanf("%d",&a);
		while(a--)scanf("%d",&b),as[i].push_back(b);
		for(int j=0;j<as[i].size();j++)
		for(int k=0;k<as[i].size();k++)
		if(j!=k)doit(as[i][j],as[i][k],i);
	}
	int c2=0;
	while(1)
	{
		c2++;
		for(int t=0;t<=0;t++)
		{
			vector<int> tp;
			int mn=1e9;
			for(int i=1;i<=ci;i++)
			{
				if(mn>v2[i])mn=v2[i],tp.clear();
				if(mn<=v2[i]+2)tp.push_back(i);
			}
			int id=tp[rnd()%tp.size()];
			erase(rnd()%2?rnd()%ci+1:id);
		}
		expand();
		if(c2%200000==0)
		{
			cerr<<ci<<endl;
			freopen(fu,"w",stdout);
			printf("%d\n",ci);
			for(int i=1;i<=ci;i++,printf("\n"))
			{
				printf("%d ",as[i].size());
				for(int j=0;j<as[i].size();j++)printf("%d ",as[i][j]);
			}
			fclose(stdout);
		}
	}
}
D6 T3 #19 球球

本来有整活题面,但是咕了

如果想要更换这里的题面请联系某个号

Problem

你在数轴的整点上运动,时刻 00 时你在位置 00,每个时刻你可以移动到相邻位置或者不移动。

你可以在任意时刻在当前所在位置放一个分身,分身不能移动。你可以任意次放分身,但放了一个分身后上一个放的分身会在 ϵ\epsilon 时刻后消失。

nn 个小球,第 ii 个小球在 tit_i 时刻从位置 xix_i 落下。你希望每个小球都被你或者你的分身接住,即时刻 tit_i 时两者至少有一者在位置 xix_i

输出是否可以接住全部小球。

n106,xi,ti109,titi+1n\leq 10^6,x_i,t_i\leq 10^9,t_i\leq t_{i+1}

1s,128MB1s,128MB

Sol

设当前人在位置 xx,分身在位置 yy,下一个球在位置 aa,剩余时间为 tt,则此时有几种可能情况:(这里用 (x,y)(x,y) 表示接到球时,人和分身的位置)

  1. 分身接球,人任意移动,合法当且仅当 y=ay=a,转移到 ([xt,x+t],y)([x-t,x+t],y)
  2. 分身不动,人接球。合法当且仅当 xat|x-a|\leq t,转移到 (a,y)(a,y)
  3. aa 放一个分身接球,然后任意移动。合法当且仅当 xat|x-a|\leq t,转移到 ([xxa,x+xa],a)([x-|x-a|,x+|x-a|],a)
  4. 先移动放一个分身,然后去接球。合法当且仅当 xat|x-a|\leq t。分身可以放置的位置 yy' 满足 xy+ayt|x-y'|+|a-y'|\leq t,即 y[min(a,x)txa2,max(a,x)+txa2]y'\in[\min(a,x)-\lfloor\frac{t-|x-a|}2\rfloor,\max(a,x)+\lfloor\frac{t-|x-a|}2\rfloor]

在第 ii 个小球落下时,人和分身中至少有一个在 xix_i。考虑设 SiS_i 表示如果当前人在 ii,分身可以在的位置集合,TiT_i 表示如果分身在 ii,人可以在的位置集合。

依次考虑 S,TS,T 的转移。首先考虑前两种转移。令 t=ti+1tit=t_{i+1}-t_i,对于 SS

  1. 对于第一种转移,存在当且仅当 xi+1Six_{i+1}\in S_i,可以转移到的情况相当于向 Ti+1T_{i+1} 中加入区间 [xit,xi+t][x_i-t,x_i+t]
  2. 对于第二种转移,存在当且仅当 xixi+1t|x_i-x_{i+1}|\leq t,转移相当于将 SiS_i 整体加入 Si+1S_{i+1}

对于 TT

  1. 对于第一种转移,存在当且仅当 xi=xi+1x_i=x_{i+1},此时相当于向 Ti+1T_{i+1} 中加入从 TiT_i 开始任意移动 tt 时刻能到达的位置集合,即 {x+kxTi,kt}\{x+k|x\in T_i,|k|\leq t\}
  2. 对于第二种转移,存在当且仅当 TiT_i 中存在距离 xi+1x_{i+1} 不超过 TT 的元素,转移到的状态为 (xi+1,xi)(x_{i+1},x_i),即向 Si+1S_{i+1} 加入 xix_i

对于后两种转移,它们和分身位置无关,因此只需要考虑当前人可以在的位置 Ri=Ti[Si]{xi}R_i=T_i\cup[S_i\neq\emptyset]\{x_i\}。此时:

  1. 对于第三种转移,可以发现人距离 xi+1x_{i+1} 越近越好,因此求出 RiR_i 中距离 xi+1x_{i+1} 最小的点的距离 d1d_1 即可判断是否存在合法转移,转移相当于向 Ti+1T_{i+1} 中加入一个区间。
  2. 对于第四种转移。设人当前位置为 bb,则可以进行转移的区间为 [xi+1t,xi+1+t][x_{i+1}-t,x_{i+1}+t]。考虑上述转移形式可以发现,随着 bb 增大,能转移到的区间的最优端点一定增大。同时所有能转移到的区间都包含 xi+1x_{i+1} 从而两两相交。因此考虑求出合法区间中最小和最大的 bb,前者贡献了能转移到的最小位置,后者贡献了最大位置,它们中的每个位置都能被转移到,从而所有情况中之后人能到达的位置构成一个区间,相当于向 Si+1S_{i+1} 中加入一个区间。

从而每次转移中除去可能的 SiSi+1,TiTi+1S_i\to S_{i+1},T_i\to T_{i+1} 的复制外,剩余转移为加入 O(1)O(1) 个区间,那么只需要维护两个集合 S,TS,T,每次转移先求出所有对 S,TS,T 的修改操作,然后一起修改即可。

考虑对单个集合 SS 的操作,可以发现有如下类型:

  1. 加入一个区间,SS[l,r]S\leftarrow S\cup[l,r]
  2. 扩展 tt 长度,S{x+kxS,kt}S\leftarrow\{x+k|x\in S,|k|\leq t\}
  3. 清空 SSSS\leftarrow \emptyset
  4. 查询是否非空,res=[S]res=[S\neq\emptyset]
  5. 查询到某点的最小距离,res=minxSxares=\min_{x\in S}|x-a|
  6. 查询值在某个区间内的最大最小值,res=min/maxx[l,r]xres=\min/\max_{x\in[l,r]}x

考虑将 SS 维护为若干区间 [l,r][l,r] 的并,同时记录标记 tt 表示整体扩展了多少长度,加入区间时改为加入 [l+t,rt][l+t,r-t]。如果对每一对相邻区间记录它们的距离(即再增加多少 tt 会导致合并),则可以完整地用不交区间表示出 SS,这样修改/询问容易回答,使用 set 即可做到 O(nlogn)O(n\log n)

这里给出另外一种方式,同样将 SS 维护为若干区间 [l,r][l,r] 的并,但不再要求不交,而只要求 l,rl,r 严格递增。这样 22 操作只需要改变标记,11 操作时简单处理(删掉一些区间)。询问时可以类似之前的方式,找到询问点左右第一个 ll,讨论两个区间即可。这样的复杂度不变,但因为不需要合并区间常数更小。

复杂度 O(nlogn)O(n\log n)

注意负数。

Code
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
int n,x,t,lx,lt;
struct yijan{
	int li;
	set<pair<int,int> > si;
	void insert(int l,int r)
	{
		l+=li;r-=li;
		int fg=0;
		set<pair<int,int> >::iterator it=si.lower_bound(make_pair(l+1,-2e9));
		if(it!=si.begin())
		{
			it--;
			if((*it).second>=r)fg=1;
		}
		if(!fg)
		{
			while(1)
			{
				it=si.lower_bound(make_pair(l,-2e9));
				if(it==si.end()||(*it).second>r)break;
				si.erase(it);
			}
			si.insert(make_pair(l,r));
		}
	}
	int query(int x,int f1=1,int f2=1)
	{
		int as=2e9;
		set<pair<int,int> >::iterator it=si.lower_bound(make_pair(x+li,-2e9));
		if(it!=si.end())
		{
			int vl=max((*it).first-li-x,0);
			if(vl&&!f2)vl=2e9;
			as=min(as,vl);
		}
		if(it!=si.begin())
		{
			it--;
			int vl=max(x-(*it).second-li,0);
			if(vl&&!f1)vl=2e9;
			as=min(as,vl);
		}
		return as;
	}
}s0,s1;
int main()
{
	scanf("%d",&n);
	s1.insert(0,0);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&t,&x);
		int ds=2e9,lb=s0.query(x-(t-lt),0,1),rb=s0.query(x+(t-lt),1,0);
		if(s1.si.size())
		{
			ds=min(ds,lx>x?lx-x:x-lx);
			if(lx>=x-(t-lt))lb=min(lb,lx-x+t-lt);
			if(lx<=x+(t-lt))rb=min(rb,x+t-lt-lx);
		}
		ds=min(ds,s0.query(x));
		int f0=s1.query(x)==0,f1=s0.query(x)<=t-lt;
		if((lx>x?lx-x:x-lx)>t-lt)s1.si.clear();
		if(lx!=x)s0.si.clear();
		s0.li+=t-lt;
		if(f0)s0.insert(lx-(t-lt),lx+(t-lt));
		if(f1)s1.insert(lx,lx);
		if(ds<=t-lt)s0.insert(x-(t-lt-ds),x+(t-lt-ds));
		if(lb<=(t-lt)*2)
		{
			int lx=x-(t-lt)+lb,rx=x;
			if(lx>rx)swap(lx,rx);
			int ri=t-lt-rx+lx;
			s1.insert(lx-ri/2,rx+ri/2);
		}
		if(rb<=(t-lt)*2)
		{
			int lx=x+(t-lt)-rb,rx=x;
			if(lx>rx)swap(lx,rx);
			int ri=t-lt-rx+lx;
			s1.insert(lx-ri/2,rx+ri/2);
		}
		lx=x;lt=t;
	}
	if(s0.si.size()||s1.si.size())printf("YES\n");
	else printf("NO\n");
}
D7 T1 #2 djq 学生物
Problem

给定 nn,现在有 nn 个三阶排列 s1,,sns_1,\cdots,s_n,初始时 si=(1,2,3)s_i=(1,2,3)(这里是序列不是置换)

考虑用 0,1,2,3,4,50,1,2,3,4,5 表示 (1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1)(1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1),对于 nn 个排列 s1,,sns_1,\cdots,s_n,设排列 sis_i 对应整数 viv_i,则可以使用 i=1n6i1vi\sum_{i=1}^n6^{i-1}*v_i 表示排列的状态。类似的,对于 nn 个有序置换,也可以得到整数表示。

定义对排列组 (s1,,sn)(s_1,\cdots,s_n) 使用置换组 (t1,,tn)(t_1,\cdots,t_n) 表示对第 ii 个排列使用第 ii 个置换,即将排列变为 (t1s1,,tnsn)(t_1\circ s_1,\cdots,t_n\circ s_n)

给定长度为 6n6^n 的序列 vv,重复进行操作,每次操作有 vi1+vi\frac{v_i}{1+\sum v_i} 的概率对排列使用 ii 对应的置换组,有 11+vi\frac 1{1+\sum v_i} 的概率结束过程。

求结束时,排列状态对应 0,1,,6n10,1,\cdots,6^n-1 的概率,模 998244353998244353,保证 vi1(mod998244353)\sum v_i\not\equiv -1(\bmod 998244353),如果模意义下不存在输出 1-1

n8n\leq 8

2s,512MB2s,512MB

Sol

记答案构成序列 ff,进行非停止操作的方式构成序列 gg,使用如下方式定义 (S3)n(S_3)^n 下的卷积:

(f×g)(s1,,sn)=aibi=sif(a1,,an)g(b1,,bn)(f\times g)_{(s_1,\cdots,s_n)}=\sum_{a_i\circ b_i=s_i}f_{(a_1,\cdots,a_n)}g_{(b_1,\cdots,b_n)}

那么转移形式相当于:

f=g×f+11+viIf=g\times f+\frac 1{1+\sum v_i}I

对于 Zn\Z_n (即变换为循环卷积)或者 Zl1××Zln\Z_{l_1}\times\cdots\times \Z_{l_n} (高维循环卷积)的情况,经典的处理方式是构造变换 TT,满足如下性质:

  1. 线性,T(ax+y)=aT(x)+T(y)T(ax+y)=aT(x)+T(y)
  2. T(x×y)=T(x)T(y)T(x\times y)=T(x)\cdot T(y)
  3. 可逆

此时上述形式的方程变为 T(f)=T(g)T(f)+aT(f)T(f)=T(g)\cdot T(f)+aT(f),可以对每个元素分别求解。在 Zn\Z_n 情况下,这一变换即为 DFT:T(f)i=jfjωnijT(f)_i=\sum_jf_j\omega_n^{ij}

考虑将这一做法扩展到 S3S_3 上,但此时最大的问题在于之前的点值是 Zp\Z_p 上的元素,其满足交换律,因此使用若干值构成的序列构造的变换一定满足交换律。但 S3S_3 不满足交换律,因此点值类构造一定无法满足要求。

此时需要构造不满足交换律的变换。一个最简单的形式是矩阵 Mn×n(Zp)M_{n\times n}(\Z_p),由于 S3=6=4+1+1|S_3|=6=4+1+1,考虑构造 M2×2(Zp)×Zp×ZpM_{2\times 2}(\Z_p)\times \Z_p\times \Z_p。下面给出一种构造过程。

首先考虑构造 2×22\times 2 部分,显然 (1,2,3)(1,2,3) 对应 II,令 (2,1,3)(2,1,3) 对应 AA(1,3,2)(1,3,2) 对应 BB,则 (2,3,1)(2,3,1) 对应 ABAB(3,1,2)(3,1,2) 对应 BABA,由此得到:

A2=I,B2=I,(AB)3=(BA)3=IA^2=I,B^2=I,(AB)^3=(BA)^3=I

同时可以发现,(3,2,1)(3,2,1) 对应 ABA=BABABA=BAB。可以发现只要满足上述四个条件,则所有置换乘法都满足条件(例如可以得到 BAAB=IBAAB=I,从而 ABAB=BAABAB=BA),进而这是一个满足乘法的矩阵表示。

枚举可以发现,取 A=[0110],B=[1011]A=\begin{bmatrix}0&1\\1&0\end{bmatrix},B=\begin{bmatrix}1&0\\-1&-1\end{bmatrix} 即可满足条件,从而可以构造出所有表示。

再考虑最后两个整数,一个简单的构造是 (1)inv(p)(-1)^{inv(p)},这显然满足条件。

考虑让另外一个直接取 11,可以发现这样的变换可逆,因此完成了构造。

最后的结果为:

(1,2,3)\to (\begin{bmatrix}1&0\\0&1\end{bmatrix},1,1)\ (1,3,2)\to (\begin{bmatrix}1&0\\-1&-1\end{bmatrix},-1,1)\\ (2,1,3)\to (\begin{bmatrix}0&1\\1&0\end{bmatrix},-1,1)\ (2,3,1)\to (\begin{bmatrix}-1&-1\\1&0\end{bmatrix},1,1)\\ (3,1,2)\to (\begin{bmatrix}0&1\\-1&-1\end{bmatrix},1,1)\ (3,2,1)\to (\begin{bmatrix}-1&-1\\0&1\end{bmatrix},-1,1)\\

然后考虑 (S3)n(S_3)^n 的情况,之前的点值构造中,多维进行直和的过程是直接使 si,j=ai×bjs_{i,j}=a_i\times b_j,其中两个数的直和为直接相乘。可以发现两个矩阵 A,BA,B 的直和应当为一个 (n1n2)×(m1m2)(n_1*n_2)\times(m_1*m_2) 的矩阵,其中 (A×B)(i1,i2),(j1,j2)=Ai1,j1×Bi2,j2(A\times B)_{(i_1,i_2),(j_1,j_2)}=A_{i_1,j_1}\times B_{i_2,j_2}

使用这一方式构造,最后 (S3)n(S_3)^n 中的变换 T(f)T(f) 得到的结果为 3n3^n 个不同大小的矩阵。可以验证这一变换满足之前给出的所有限制。

回到上述方程,变换后得到 T(f)i=T(g)iT(f)i+11+viIT(f)_i=T(g)_iT(f)_i+\frac 1{1+\sum v_i}I,从而 T(f)i=((1+vi)(IT(g)i))1T(f)_i=((1+\sum v_i)*(I-T(g)_i))^{-1},得到 T(f)T(f) 后逆变换回去(一维上 6×66\times 6 的转移矩阵可逆)即可。如果矩阵不可逆则结果为 1-1(虽然也不一定,因为可能分子加起来又消掉了 modmod,但是先不管这种情况)

变换的过程可以将矩阵看成四个数,做形式上等同于 6n6^n 上高维dft的东西,然后通过变换结果找出所有矩阵,求逆后放回去逆变换,这样比变换时维护 3n3^n 个矩阵快很多。

变换复杂度 O(n6n)O(n6^n),求逆复杂度 O((8+1+1)n)O((8+1+1)^n),因此总复杂度 O(10n)O(10^n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 1680001
#define M 259
#define mod 998244353
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int f[M][M],g[M][M],fg=0;
void getinv(int n)
{
	for(int i=0;i<n;i++)for(int j=0;j<n;j++)g[i][j]=i==j;
	for(int i=0;i<n;i++)
	{
		int fr=-1;
		for(int j=i;j<n;j++)if(f[j][i])fr=j;
		if(fr==-1){fg=1;return;}
		for(int j=0;j<n;j++)swap(f[i][j],f[fr][j]),swap(g[i][j],g[fr][j]);
		int ir=pw(f[i][i],mod-2);
		for(int j=0;j<n;j++)f[i][j]=1ll*f[i][j]*ir%mod,g[i][j]=1ll*g[i][j]*ir%mod;
		for(int j=0;j<n;j++)if(j!=i)
		{
			int tp=mod-f[j][i];
			for(int k=0;k<n;k++)f[j][k]=(f[j][k]+1ll*tp*f[i][k])%mod,g[j][k]=(g[j][k]+1ll*tp*g[i][k])%mod;
		}
	}
}
int n,su,si,vi[N],as;
int trs[6][6]={{1,0,0,1,1,1},{1,0,-1,-1,-1,1},{0,1,1,0,-1,1},{-1,-1,1,0,1,1},{0,1,-1,-1,1,1},{-1,-1,0,1,-1,1}},rtrs[6][6];
void trans(int su,int *f,int tr[][6])
{
	int t1[6],t2[6];
	for(int i=1;i<su;i*=6)
	for(int j=0;j<su;j+=i*6)
	for(int k=0;k<i;k++)
	{
		for(int p=0;p<6;p++)t1[p]=f[j+k+p*i],t2[p]=0;
		for(int p=0;p<6;p++)for(int q=0;q<6;q++)t2[q]=(t2[q]+1ll*t1[p]*tr[p][q])%mod;
		for(int p=0;p<6;p++)f[j+k+p*i]=(t2[p]+mod)%mod;
	}
}
void solve(int n,vector<vector<int> > sr)
{
	int l=sr.size();
	if(!n)
	{
		for(int i=0;i<l;i++)for(int j=0;j<l;j++)f[i][j]=(mod+(i==j)*(si+1)-vi[sr[i][j]])%mod;
		getinv(l);
		for(int i=0;i<l;i++)for(int j=0;j<l;j++)vi[sr[i][j]]=g[i][j];
		return;
	}
	for(int i=0;i<l;i++)for(int j=0;j<l;j++)sr[i][j]=sr[i][j]*6+4;
	solve(n-1,sr);
	for(int i=0;i<l;i++)for(int j=0;j<l;j++)sr[i][j]++;
	solve(n-1,sr);
	vector<vector<int> > nr;
	for(int i=0;i<l;i++)
	{
		vector<int> tp;
		for(int j=0;j<l;j++)tp.push_back(sr[i][j]-5),tp.push_back(sr[i][j]-4);
		nr.push_back(tp);
		for(int j=0;j<l*2;j++)tp[j]+=2;
		nr.push_back(tp);
	}
	solve(n-1,nr);
}
int main()
{
	scanf("%d",&n);
	su=1;for(int i=1;i<=n;i++)su*=6;
	for(int i=0;i<su;i++)scanf("%d",&vi[i]),si=(si+vi[i])%mod;
	trans(su,vi,trs);solve(n,{{0}});
	for(int i=0;i<6;i++)for(int j=0;j<6;j++)f[i][j]=trs[i][j];
	getinv(6);
	for(int i=0;i<6;i++)for(int j=0;j<6;j++)rtrs[i][j]=g[i][j];
	trans(su,vi,rtrs);
	for(int i=0;i<su;i++)as^=vi[i];
	printf("%d\n",fg?-1:as);
}
D7 T2 #3 完全表示
Problem

给定一个大小为 kk 的环 RR

环是一类包含两种运算(乘法 \otimes,加法 \oplus)的代数系统,满足:

  1. 加法结合律,加法交换律,乘法结合律,乘法分配律
  2. 存在加法单位元,乘法单位元
  3. 对于任意元素存在加法逆元

例如,对于任意 2\geq 2 的正整数 nn,模 nn 意义加法和乘法构成了环 Zn\Z_n

定义环 RR 上的 nn 维向量为 u=(u1,,un)\mathbf u=(u_1,\cdots,u_n),其中 uiRu_i\in R

定义向量加法,数乘:

u+v=(u1v1,,unvn)au=(au1,,aun)\mathbf u+\mathbf v=(u_1\oplus v_1,\cdots,u_n\oplus v_n)\\ a\cdot \mathbf u=(a\otimes u_1,\cdots,a\otimes u_n)

称向量集合 {u1,,un}\{\mathbf u_1,\cdots,\mathbf u_n\} 能表示 v\mathbf v,当且仅当 a1,,anR,v=a1u1++anun\exists a_1,\cdots,a_n\in R,\mathbf v=a_1\otimes \mathbf u_1+\cdots+a_n\otimes \mathbf u_n

称一个向量集合是完全表示,当且仅当它能表示所有 nn 维向量。

求出所有 nn 维向量集合的完全表示的大小的 mm 次方之和,答案模 164511353164511353

给出 tp{1,2}tp\in\{1,2\},如果 tp=1tp=1R=ZkR=\Z_k,否则输入 RR 的运算表。

n105,m103n\leq 10^5,m\leq 10^3

对于 tp=1tp=1k105k\leq 10^5 (partial: kk 是质数)

对于 tp=2tp=2k20k\leq 20

1s,1024MB1s,1024MB

Sol

抽代警告.jpg

您可能至少需要一些基础的线代知识

首先考虑 tp=1tp=1。根据 CRT,可以将 Zk\Z_k 分解为 Zp1q1××Zplql\Z_{p_1}^{q_1}\times\cdots\times \Z_{p_l}^{q_l}。那么首先考虑 k=pqk=p^q 的问题如何求解。

如果 q=1q=1,对于 Zp\Z_p 的问题有很多方式,例如枚举能表示的向量集合的基等等,但在 Zpq\Z_{p^q} 下很多问题难以解决,因为此时 pqp^q 进制线性基的形式非常奇怪。

从完全表示的角度考虑问题。首先可以发现,如果一个 nn 维向量集合是完全表示,那么删去每个向量的最后一维,一定能得到一个 n1n-1 维的完全表示。

那么考虑一个 n1n-1 维的完全表示能对应多少个 nn 维完全表示。可以发现,如果一个向量集合去掉最后一维后是 n1n-1 维完全表示,那么它是 nn 维完全表示当且仅当它能表示出 (0,0,,0,1)(0,0,\cdots,0,1)。因此考虑计算在某个 n1n-1 维完全表示的基础上,有多少种情况能表示 (0,0,,0,1)(0,0,\cdots,0,1)

首先考虑一个向量集合能生成多少 (0,0,,0,x)(0,0,\cdots,0,x),考虑能这样生成的 xx 构成的集合 S(0)S(\mathbf 0),那么该集合满足 0S(0),a,bS(0),cZpq,a+b,baS(0)0\in S(\mathbf 0),\forall a,b\in S(\mathbf 0),c\in \Z_{p^q},a+b,ba\in S(\mathbf 0)。在 Zpq\Z_{p^q} 下,这可以看成由若干个数线性组合能得到的数的集合,那么容易发现可能的集合只有 {pixxZpq}\{p^i*x|x\in\Z_{p^q}\},其中 i[0,q]i\in[0,q]。此时只有 i=0i=0 满足能表示 (0,0,,0,1)(0,0,\cdots,0,1),因此考虑计算 S(0){xxZpq}S(\mathbf 0)\subset\{x|x\in \Z_{p^q}\} 的方案数,减去 S(0){pxxZpq}S(\mathbf 0)\subset\{px|x\in \Z_{p^q}\} 的方案数。

首先考虑前者,其相当于对最后一维没有任何限制,可以发现此时相当于一个 n1n-1 维的问题,但每一个 n1n-1 维向量对应 pqp^qnn 维向量,因此情况变为 n1n-1 维问题,但每个向量有 2pq12^{p^q}-1 种方式被选择。

然后考虑后者。类似 S(0)S(\mathbf 0) 的方式,记 S(v)S(\mathbf v) 为所有满足 (v;x)(\mathbf v;x) 可以被表示的 xx 组成的集合。此时显然有如下结论:a,bS(v),abS(0)\forall a,b\in S(\mathbf v),a-b\in S(\mathbf 0)。那么 S(v)S(\mathbf v) 一定可以被表示为 {a+xxS(0)}\{a+x|x\in S(\mathbf 0)\}。此时可以发现一定有 S(0)=S(v)=pq1|S(\mathbf 0)|=|S(\mathbf v)|=p^{q-1}。同时,这种情况中每个 n1n-1 维向量 v\mathbf v 的最后一维只能选择 S(v)S(\mathbf v) 中元素。那么如果确定了所有的 S(v)S(\mathbf v),则变为每个向量有 2pq112^{p^{q-1}}-1 种方式被选择的问题。

再考虑确定 S(v)S(\mathbf v),显然有 pp 种可能的 S(v)S(\mathbf v)(令 a=0,,p1a=0,\cdots,p-1),记{a+xxS(0)}=a+S(0)\{a+x|x\in S(\mathbf 0)\}=a+S(\mathbf 0)。可以发现,如果确定了 S(u)=a+S(0),S(v)=b+S(0)S(\mathbf u)=a+S(\mathbf 0),S(\mathbf v)=b+S(\mathbf 0),则有 S(u+v)=(a+b)+S(0),S(cu)=ca+S(0)S(\mathbf u+\mathbf v)=(a+b)+S(\mathbf 0),S(c\mathbf u)=ca+S(\mathbf 0)。这样的运算是良定义的。

考虑 n1n-1 维向量空间的一组基,这组基的 SS 可以任意确定,这样剩余的 SS 可以唯一确定,从而 SS 的方案数为 pn1p^{n-1},因此答案为 pn1p^{n-1} 乘上 n1n-1 维中每个向量有 2pq112^{p^{q-1}}-1 种方式被选择的问题。

(一个 naive 的看法是,我们选一组基,这组基的最后一维可以任意,剩余每个元素只有 pq1p^{q-1} 种选择,那么需要额外乘上 pn1p^{n-1}。虽然这样结果是对的,但是这个证明显然是完全错误的)

考虑多维时容斥多次的情况,之前的 22 实际上为 1+11+1,其中第一个 11 为选择该向量的方案数,第二个为不选择的方案数,因此变为 2pq12^{p^q}-1 后继续容斥的形式非常简单。最后可以发现如下结果:

如果第 ii 次容斥,则系数为 pni-p^{n-i},不容斥系数为 11。设最后容斥了 kk 次,则最后满足该容斥条件的集合为某 2pnqk2^{p^{nq-k}} 个元素的集合的任意子集。

容斥部分相当于 (1x)(1px)(1pn1x)(1-x)(1-px)\cdots(1-p^{n-1}x),可以使用各种方式解决。例如 q-binomial 可以直接 O(n)O(n) 求出所有系数。

然后考虑多个部分合并的问题。因为两部分完全独立,合并时可以看成,每部分选择一个容斥次数,最后满足所有条件的集合为某个 2pinqiki2^{\prod p_i^{nq_i-k_i}} 个元素的集合的子集。

但这个乘积是非常难解决的。即使对于 m=0m=0 的情况,求和 2pinqiki2^{\prod p_i^{nq_i-k_i}} 通常也是困难的。但此时我们发现:

2411=133671645113532^{41}-1=13367*164511353

进一步,ord(2)=41ord(2)=41。那么对于 m=0m=0 的情况,只需要维护指数乘积部分对 4141 取模的结果即可,进而合并可以做到 O(ord(2)2)O(ord(2)^2)

然后考虑 m0m\geq 0 的问题。在最后一步,需要对于一个大小为 pinqiki\prod p_i^{nq_i-k_i} 的集合的所有子集求和大小的 mm 次方和。

根据经典做法,考虑将 kmk^m 拆成下降幂的形式,即:

km=i=0mSmikik^m=\sum_{i=0}^mS_m^ik^{\underline i}

其中 SS 表示第二类斯特林数。而 kik^{\underline i} 有很好的组合意义:选出 kk 个不同的数,顺序有关。

求和选 kk 个数的方案,则相当于在原先集合中选出 kk 个数,剩余数可以任选是否加入集合,因此答案为 jSmj(pinqiki)j2pinqikij\sum_{j}S_m^j(\prod p_i^{nq_i-k_i})^{\underline j}2^{\prod p_i^{nq_i-k_i}-j}

乘积的下降幂仍然难以处理,考虑将下降幂拆开,变成若干 (pinqiki)a2j2pinqiki(\prod p_i^{nq_i-k_i})^{a}*2^{-j}*2^{\prod p_i^{nq_i-k_i}} 的形式(实际上系数是第一类斯特林数乘 (1)a(-1)^a),变为如下问题:

对于每个 k[0,m]k\in[0,m] 求和所有选择方案的 (pinqiki)k2pinqiki(\prod p_i^{nq_i-k_i})^{k}*2^{\prod p_i^{nq_i-k_i}}。这在相乘下是容易合并的,在处理一个 pqp^q 时,对于每个 k[0,m],r[0,ord(2)1]k\in[0,m],r\in[0,ord(2)-1] 求出所有满足 pinqikir(modord(2))p_i^{nq_i-k_i}\equiv r(\bmod ord(2)) 的方案的方案数乘上 (pinqiki)k(p_i^{nq_i-k_i})^k 的和,合并时 rr 一维使用之前的直接合并方式,kk 一维直接对应位置相乘即可。

现在回到一个 pqp^q 的情况,一种做法是先 q-binomial 或者其它方式求出容斥部分 (1x)(1px)(1pn1x)(1-x)(1-px)\cdots(1-p^{n-1}x) 的系数,再枚举 kk。但这样处理的复杂度即为 O(nmc(k))O(nm*c(k)),其中 cc 指不同质因子数量,差一点通过。

重新考虑之前的过程,容斥了多少次实际上不是重要的,重要的是大小模 ord(2)ord(2) 的值,每一步不容斥让大小乘以 pqp^q,容斥让大小乘以 pq1p^{q-1},系数 pni-p^{n-i},因此可以设 fi,jf_{i,j} 表示前 ii 步后,当前大小模 ord(2)ord(2)jj 的方案数,这样复杂度为 O(nord(2))O(n*ord(2)),不需要 q-binomial。

然后考虑一个 kk 次方的答案。此时第 ii 步不容斥系数为 pqkp^{qk},容斥系数为 pnik+qk-p^{n-i-k+qk},提取 pqkp^{qk} 后可以发现这变为一个第 i+ki+k 步的容斥过程。

因此相当于有 n+mn+m 步可能的容斥过程,其唯一区别为容斥系数中 pp 的指数,kk 次方的答案相当于只使用 [1+k,n+k][1+k,n+k] 步容斥过程的结果乘以某个系数。

显然不同步的容斥过程是可以交换的,任意步过程合并后都可以表示为“这一段使大小乘上 0,1,,ord(2)10,1,\cdots,ord(2)-1 的方案数分别为 v0,v1,v_0,v_1,\cdots”,此时有若干做法:

  1. ii 步会作用到一个 kk 的区间上,线段树分治,单次复杂度 O((n+mlogm)ord(2))O((n+m\log m)*ord(2))
  2. 注意到询问为所有长度为 nn 的区间和,将序列按照长度为 kk 分块,预处理块内前后缀和,询问为一个后缀和一个前缀合并。大力合并复杂度为 O(nord(2)+mord(2)2)O(n*ord(2)+m*ord(2)^2),也可以通过找原根换成循环卷积做到理论复杂度 O(nord(2)+mord(2)logord(2))O(n*ord(2)+m*ord(2)\log ord(2)),注意 pord(2)p|ord(2) 的特例。

最后的总复杂度为 O(m2+c(k)(F+mord(2)2))O(m^2+c(k)*(F+m*ord(2)^2))FF 为上一步做法的复杂度,后者为合并复杂度。因为合并复杂度的存在,可以取第二种做法,做到O(m2+c(k)ord(2)(n+mord(2)))O(m^2+c(k)*ord(2)*(n+m*ord(2)))

接下来考虑 tp=2tp=2,可以发现上述推论对于一般的抽代情况大都适用:

  1. S(0)S(\mathbf 0)\to 左理想(这里表示是左数乘),若干数能左数乘表示的集合即为包含它的最小左理想。
  2. a+S(0)a+S(\mathbf 0)\to 陪集

定义 RR 的子集 II 是左理想,当且仅当:

  1. 0I0\in I
  2. a,bI,a+bI\forall a,b\in I,a+b\in I
  3. aI,bR,baI\forall a\in I,b\in R,ba\in I

陪集的定义与之前相同:a+I={a+bbI}a+I=\{a+b|b\in I\}

容易证明对于一个 II,其任意两个陪集一定相同或不交,任意元素显然都在一个陪集中。可以证明有限情况下两个陪集大小相等,从而一定有 I|I| 整除 R|R|

仍然可以使用类似的方式定义陪集相加,左数乘,证明与之前相同。因此后半部分推导不变。

唯一的变化在于,在 Zpq\Z_{p^q} 下,所有的左理想间包含关系形成了一条链,容斥后的结果非常简单,但一般环下这可能非常复杂(例如,Z2×Z3\Z_2\times \Z_3)。

考虑对每个左理想 II 确定一个容斥系数 vIv_I,统计 S(0)IS(\mathbf 0)\subset I 的方案数,乘上 vIv_I,容斥系数要求 RR 被统计一次,其它 II 都被统计零次(如果左理想包含 11,则由乘法单位元其包含所有元素)。由于 II 之间的包含关系构成 DAG,系数一定存在。

正常方式:枚举出左理想,算系数。

大力方式:枚举所有子集,计算包含它的最小左理想,如果是 RR 的系数为 11 否则系数为 00,然后逆变换得到系数。由于权值之和包含自身的最小左理想有关,逆变换回去后只有左理想位置有值。

复杂度均为 O(k22k)O(k^22^k),但常数都可以做到非常小(左理想数量非常少)。

然后考虑之前的容斥,此时对于每一步容斥,不再只有两种选择,而是对于每一个左理想 II,可以选择将大小乘上 I|I|,系数 v(I)(kI)niv(I)*(\frac k{|I|})^{n-i},此时只和 I|I| 有关,可以在上一步对于每个大小算出该大小的左理想容斥系数之和。根据陪集的性质有 Ik|I||k,因此理论上次数不多

可以发现如果需要乘上大小的 aa 次方,则相当于系数乘以 kak^a 后指数变为 nikn-i-k,因此仍然可以使用上一部分中的方式,变成对于每个 aa 考虑第 [1+a,n+a][1+a,n+a] 步的乘积,然后使用之前的做法。

此时单次乘式子是 O(ord(2)d(k))O(ord(2)*d(k)) 的,其中 dd 为质因子个数当然还可以是 min(d(k),ord(2))。使用之前的第二种做法可以做到 O((nd(k)+mord(2))ord(2))O((n*d(k)+m*ord(2))*ord(2))

复杂度 O(k22k+ord(2)(nd(k)+mord(2)))O(k^22^k+ord(2)*(n*d(k)+m*ord(2)))

Code
#include<cstdio>
using namespace std;
#define N 105900
#define M 1059
#define K 42
#define mod 164511353
int n,k,m,tp,ri=41,as,rp;
int v1[M][K],sv[M][K],c[M][M],t2[K],rv[M],s[M][M],f[M];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int vf[N][K],vg[N][K],pr[K];
void solve(int p,int q)
{
	for(int i=0;i<ri;i++)for(int j=0;j<=m;j++)v1[j][i]=0;
	for(int i=0;i<=ri;i++)vf[0][i]=vg[n+m+1][i]=i==1;
	for(int i=1;i<=n+m;i++)
	{
		int vl=mod-pw(p,n-i+mod-1);
		for(int j=0;j<ri;j++)vf[i][j]=0;
		for(int j=0;j<ri;j++)vf[i][j*p%ri]=(vf[i][j*p%ri]+vf[i-1][j])%mod;
		for(int j=0;j<ri;j++)vf[i][j]=(vf[i][j]+1ll*vl*vf[i-1][j])%mod;
		if(i%n==0)
		{
			for(int j=0;j<ri;j++)vf[i][j]=0;
			vf[i][p%ri]=1;vf[i][1]+=vl;
		}
	}
	for(int i=n+m;i>=1;i--)
	{
		int vl=mod-pw(p,n-i+mod-1);
		for(int j=0;j<ri;j++)vg[i][j]=0;
		for(int j=0;j<ri;j++)vg[i][j*p%ri]=(vg[i][j*p%ri]+vg[i+1][j])%mod;
		for(int j=0;j<ri;j++)vg[i][j]=(vg[i][j]+1ll*vl*vg[i+1][j])%mod;
		if(i%n==0)for(int j=0;j<=ri;j++)vg[i][j]=j==1;
	}
	int s1=1;for(int i=1;i<=n*(q-1);i++)s1=s1*p%ri;
	int s2=pw(p,n*q);
	for(int i=0;i<=m;i++)
	{
		int l=i+1,r=i+n,s3=pw(s2,i);
		for(int p=0;p<ri;p++)for(int q=0;q<=ri;q++)
		{
			int nt=p*q*s1%ri;
			v1[i][nt]=(v1[i][nt]+1ll*vf[r][p]*vg[l][q]%mod*s3)%mod;
		}
	}
}
int r1[K][K],r2[K][K];
int is[N*10],si[K];
void dfs(int nw,int si,int pr)
{
	if(nw==k){is[si]=pr==(1<<k)-1;return;}
	dfs(nw+1,si,pr);
	si|=1<<nw;
	int p1=pr;
	for(int i=0;i<k;i++)p1|=1<<r2[i][nw];
	while(p1>pr)
	{
		int u=0;
		for(int i=0;i<k;i++)if(((p1^pr)>>i)&1)u=i;
		pr+=1<<u;
		for(int j=0;j<=k;j++)if((p1>>j)&1)p1|=1<<r1[u][j];
	}
	dfs(nw+1,si,pr);
}
void solve_1()
{
	for(int i=0;i<=m;i++)sv[i][1]=1;
	for(int i=2;i<=k;i++)if(k%i==0)
	{
		int su=0;while(k%i==0)su++,k/=i;
		solve(i,su);
		for(int p=0;p<=m;p++)
		{
			for(int j=0;j<ri;j++)t2[j]=0;
			for(int a=0;a<ri;a++)for(int b=0;b<ri;b++)
			{
				int nt=1ll*a*b%ri;
				t2[nt]=(t2[nt]+1ll*v1[p][a]*sv[p][b])%mod;
			}
			for(int j=0;j<ri;j++)sv[p][j]=t2[j];
		}
	}
}
void solve_2()
{
	for(int i=0;i<k;i++)for(int j=0;j<k;j++)scanf("%d",&r1[i][j]);
	for(int i=0;i<k;i++)for(int j=0;j<k;j++)scanf("%d",&r2[i][j]);
	dfs(0,0,0);
	for(int i=1;i<1<<k;i<<=1)
	for(int j=0;j<1<<k;j+=i*2)
	for(int k=0;k<i;k++)
	is[j+k]-=is[j+k+i];
	for(int i=0;i<1<<k;i++)
	{
		int ci=0;
		for(int j=0;j<k;j++)if((i>>j)&1)ci++;
		si[ci]+=is[i];
	}
	for(int i=0;i<ri;i++)vf[0][i]=vg[n+m+1][i]=i==1;
	for(int i=1;i<=n+m;i++)
	{
		for(int j=0;j<ri;j++)vf[i][j]=0;
		for(int l=1;l<=k;l++)if(si[l])
		{
			int vl=1ll*pw(l,i-n+mod-1)*(si[l]+mod)%mod;
			for(int j=0;j<ri;j++)vf[i][j*l%ri]=(vf[i][j*l%ri]+1ll*vl*vf[i-1][j])%mod;
		}
		if(i%n==0)
		{
			for(int j=0;j<ri;j++)vf[i][j]=0;
			for(int l=1;l<=k;l++)if(si[l])vf[i][l]=1ll*pw(l,i-n+mod-1)*(si[l]+mod)%mod;
		}
	}
	for(int i=n+m;i>=1;i--)
	{
		for(int j=0;j<ri;j++)vg[i][j]=0;
		for(int l=1;l<=k;l++)if(si[l])
		{
			int vl=1ll*pw(l,i-n+mod-1)*(si[l]+mod)%mod;
			for(int j=0;j<ri;j++)vg[i][j*l%ri]=(vg[i][j*l%ri]+1ll*vl*vg[i+1][j])%mod;
		}
		if(i%n==0)for(int j=0;j<ri;j++)vg[i][j]=j==1;
	}
	int s1=pw(k,1ll*n*(n-1)/2%(mod-1));
	for(int i=0;i<=m;i++)
	{
		int l=i+1,r=i+n;
		for(int p=0;p<=ri;p++)for(int q=0;q<=ri;q++)
		{
			int nt=p*q%ri;
			sv[i][nt]=(sv[i][nt]+1ll*vf[r][p]*vg[l][q]%mod*s1)%mod;
		}
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&k,&m,&tp);
	for(int i=0;i<=m;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=m;i++)for(int j=1;j<i;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	s[0][0]=1;
	for(int i=1;i<=m;i++)for(int j=1;j<=i;j++)s[i][j]=1ll*j*(s[i-1][j-1]+s[i-1][j])%mod;
	if(tp==1)solve_1();
	else solve_2();
	for(int j=0;j<=m;j++)for(int i=0;i<ri;i++)rv[j]=(rv[j]+1ll*sv[j][i]*pw(2,i))%mod;
	f[0]=1;
	for(int i=0;i<=m;i++)
	{
		for(int j=0;j<=m;j++)as=(as+1ll*s[m][i]*f[j]%mod*rv[j])%mod;
		for(int j=m;j>=0;j--)f[j+1]=(f[j+1]+f[j])%mod,f[j]=1ll*f[j]*(mod-i)%mod;
		int tp=pw(2*(i+1),mod-2);for(int j=m;j>=0;j--)f[j]=1ll*f[j]*tp%mod;
	}
	printf("%d\n",as);
}
D7 T3 #29 《关于因为与去年互测zjk撞题而不得不改题这回事》
Problem

给一棵 nn 个点的树,点有点权 viv_i

qq 次询问,每次给出 x,y,kx,y,k,你需要在 xxyy 的简单路径上选出 kk 个点,最大化选出点点权按位与的结果。求出该最大值,如果选不出 kk 个点则答案为 00

强制在线。

n106,vi[0,2621],q105,k[2,10]n\leq 10^6,v_i\in[0,2^{62}-1],q\leq 10^5,k\in[2,10]kk 随机生成。

3s,512MB3s,512MB

Sol

首先考虑给一个序列如何求最大值,考虑逐位贪心,可以得到如下做法:

从高到低考虑每一位,如果当前位为 11 的数大于等于 kk 个,则可以使得答案在1这一位为 11,且这样一定最优,因此可以答案加上这一位的值,然后只保留这一位为 11 的数。

否则,这一位不能为 11,跳过这一位继续。

因此可以得到一个 O(nqlogv)O(nq\log v) 的暴力,它甚至能过 n=105,q=104n=10^5,q=10^4也就是3s 1e10

考虑将序列排序,这样在处理最高位时,只需要判断第 kk 大的数这一位是否为 11,此时:

  1. 如果这一位可以为 11,则只需要保留最高位为 11 的数,可以发现保留这些数后,判断下一位时与当前情况相同,只有最大的若干个数这一位为 11
  2. 如果这一位不能为 11,则除去最高位为 11 的数(小于 kk 个)后,剩余数在除去最高位后满足原先的大小关系。此时情况变为除去若干个被选出来的数外,其余数低位按照大小顺序排序。此时可以对其余数重复上述过程。

可以发现,此时的每一步中,上述过程会拿出前 kk 大的数,接着可能删去剩余数中较小的一些(最高位不是 11 的),保证了每一步后剩余的数在只保留低位时按照大小关系排列,从而顺序不变。因此可以发现如下结论:

对于任意一个询问,只有前 klogvk*\log v 个数有用。

考虑先求出链上前 klogvk\log v 大的数,重链剖分后变为 O(logn)O(\log n) 段区间中求出前 klogvk\log v 大。考虑每次取一个最大值,记录每一段的最大值,取时找到最大的一段,取出最大值,将这一段剩余部分分成两个区间,重复这一过程 klogvk\log v 次。区间最大值可以使用 ST 表实现,复杂度 O(klogvlog(logn+klogv))O(k\log v\log(\log n+k\log v))。也可以不用 ST 表,这样最后变成 logn\log n

考虑对上述数实现之前的暴力,则总复杂度为 O(nlogn+qklog2v)O(n\log n+qk\log^2 v),虽然看起来不太行但是能跑过去。

一种常数优化方式是不直接取出 klogvk\log v 个数,而是处理到需要取数时再取,发现无法满足当前位就跳过,这样可以减少很多常数。

更好的做法是在情况 22 时,将拿出的若干个数的高位删去再放回去,使用 set 维护顺序,这样判断满足条件只需要单次 O(klog(k+logv))O(k\log(k+\log v)),删除不合法数时可以从小到大删,一次询问这部分的总复杂度为 O(klogvlog(k+logv))O(k\log v\log(k+\log v))

从而总复杂度为 O(nlogn+qklogvlog(logn+klogv))O(n\log n+qk\log v\log(\log n+k\log v)),但这东西完全跑不过加了常数优化的 qklog2vqk\log^2 v(可能因为只要不构造,上述优化后复杂度就非常接近 qklogvqk\log v,而且构造了也有 14\frac14 常数,非常快)

Code

qklog2vqk\log^2 v 跑到了rk1,所以这里放 qklog2vqk\log^2 v

#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
#define N 1005000
#define ll long long
int n,a,b,head[N],cnt,sz[N],sn[N],id[N],tp[N],dep[N],f[N],ci;
struct edge{int t,next;}ed[N*2];
void adde(int f,int t)
{
	ed[++cnt]=(edge){t,head[f]};head[f]=cnt;
	ed[++cnt]=(edge){f,head[t]};head[t]=cnt;
}
void dfs0(int u,int fa)
{
	sz[u]=1;dep[u]=dep[fa]+1;f[u]=fa;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		dfs0(ed[i].t,u);sz[u]+=sz[ed[i].t];
		if(sz[ed[i].t]>sz[sn[u]])sn[u]=ed[i].t;
	}
}
void dfs1(int u,int fa,int v)
{
	tp[u]=v;id[u]=++ci;
	if(sn[u])dfs1(sn[u],u,v);
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])dfs1(ed[i].t,u,ed[i].t);
}
ll c,vi[N];
int st[N][20],lg[N];
int doit(int x,int y){return vi[x]>vi[y]?x:y;}
void init_st()
{
	for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;i++)st[i][0]=i;
	for(int d=1;d<=19;d++)
	for(int i=1;i+(1<<d)-1<=n;i++)
	st[i][d]=doit(st[i][d-1],st[i+(1<<d-1)][d-1]);
}
int query(int l,int r)
{
	int d=lg[r-l+1];
	return doit(st[l][d],st[r-(1<<d)+1][d]);
}
struct sth{int l,r,x;};
bool operator <(sth a,sth b){return vi[a.x]==vi[b.x]?a.l<b.l:vi[a.x]>vi[b.x];}
set<sth> si;
void ins(int l,int r)
{
	if(l>r)return;
	si.insert((sth){l,r,query(l,r)});
}
void init_qu(int x,int y)
{
	si.clear();
	while(tp[x]!=tp[y])
	{
		if(dep[tp[x]]<dep[tp[y]])swap(x,y);
		ins(id[tp[x]],id[x]),x=f[tp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	ins(id[x],id[y]);
}
ll getnxt()
{
	if(si.empty())return 0;
	sth ri=*si.begin();si.erase(ri);
	ins(ri.l,ri.x-1);ins(ri.x+1,ri.r);
	return vi[ri.x];
}
ll query(int k)
{
	vector<ll> sr;
	ll as=0,fg=1;
	for(int i=61;i>=0;i--)
	{
		int ci=0;
		for(int j=0;j<sr.size();j++)if((sr[j]>>i)&1)ci++;
		while(ci<k&&fg)
		{
			ll u=getnxt();
			if((u&as)<as){fg=0;break;}
			sr.push_back(u);
			if((u>>i)&1)ci++;
			else break;
		}
		if(ci>=k)
		{
			as+=(ll)1<<i;
			vector<ll> nt;
			for(int j=0;j<sr.size();j++)if((sr[j]>>i)&1)nt.push_back(sr[j]);
			sr=nt;
		}
	}
	return as;
}
int q,k;
ll ls,x,y;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs0(1,0);dfs1(1,0,1);
	for(int i=1;i<=n;i++)scanf("%lld",&c),vi[id[i]]=c;
	init_st();
	scanf("%d",&q);
	while(q--)
	{
		scanf("%lld%lld%d",&x,&y,&k);
		x=(x^ls)%n+1;y=(y^ls)%n+1;
		init_qu(x,y);ls=query(k);
		printf("%lld\n",ls);
	}
}
D8 T1 #1 Numbers
Problem

给定 nnnn 个正整数 lil_inn 个正整数 viv_i

考虑如下过程:

nn 个整数 x1,,xnx_1,\cdots,x_n,重复如下步骤:

  1. 随机一个 [1,n][1,n] 间整数 kk。这里不使用等概率随机,而是通过 viv_i 决定概率:生成 ii 的概率为 pi=vivip_i=\frac{v_i}{\sum v_i}
  2. xkx_k 变为 (xk+1)modlk(x_k+1)\bmod l_k
  3. 记录当前所有数的值组成的序列 (x1,,xn)(x_1,\cdots,x_n)
  4. 如果 (x1,,xn)=(0,0,,0)(x_1,\cdots,x_n)=(0,0,\cdots,0),则结束过程,否则返回第一步。

求该过程结束时,记录的序列的种类数(即有多少种不同序列)的期望。

答案对给定质数 modmod 取模,同时对于每个 lil_i,给出正整数 wiw_i,保证 wili1(modmod)w_i^{l_i}\equiv 1(\bmod mod),同时 k[1,li1],wik1(modmod)\forall k\in[1,l_i-1],w_i^k\not\equiv 1(\bmod mod)

除此之外,保证 viv_i 随机,保证 k1,,kn,ki[0,li1],piwiki1(modmod)\forall k_1,\cdots,k_n,k_i\in[0,l_i-1],\sum p_iw_i^{k_i}\equiv 1(\bmod mod) 当且仅当 i,ki=0\forall i,k_i=0。保证答案在模意义下存在。

m=lim=\prod l_i,则 m5×105,li2,vi106,mod[9×108,1.05×109]m\leq 5\times 10^5,l_i\geq 2,v_i\leq 10^6,mod\in[9\times 10^8,1.05\times 10^9]

4s,1024MB4s,1024MB

Sol

根据期望线性性,答案等于对所有可能的 mm 种状态求和结束前该状态出现过的概率。

(0,,0)(0,\cdots,0) 显然必定出现,考虑一个不全 00 的状态 (a1,,an)(a_1,\cdots,a_n) 出现的概率。以下讨论均对于一个 aa 进行。

f(b1,,bn)f_{(b_1,\cdots,b_n)} 表示当前所有数的状态为 (b1,,bn)(b_1,\cdots,b_n),进行上述过程直到到达 (0,,0)(0,\cdots,0),这个过程中经过状态 (a1,,an)(a_1,\cdots,a_n) 的概率。则可以发现转移为:

  1. f(a1,,an)=1f_{(a_1,\cdots,a_n)}=1
  2. f(0,,0)=0f_{(0,\cdots,0)}=0
  3. 对于其它位置,f(b1,,bn)=i=1npif(b1,,bi1,(bi+1)modli,bi+1,,bn)f_{(b_1,\cdots,b_n)}=\sum_{i=1}^np_if_{(b_1,\cdots,b_{i-1},(b_i+1)\mod l_i,b_{i+1},\cdots,b_n)}

可以发现除去特殊转移外, ff 的转移是高维循环卷积的形式。定义高维循环卷积为:

(f×g)(c1,,cn)=i,ai+bici(modli)f(a1,,an)g(b1,,bn)(f\times g)_{(c_1,\cdots,c_n)}=\sum_{\forall i,a_i+b_i\equiv c_i(\bmod l_i)}f_{(a_1,\cdots,a_n)}g_{(b_1,\cdots,b_n)}

可以发现这就是 nn 个循环卷积的直和,因此可以使用 DFT 的直和构造点值,即:

T(f)(c1,,cn)=bi=1nωlicibif(b1,,bn)T(f)_{(c_1,\cdots,c_n)}=\sum_b\prod_{i=1}^n\omega_{l_i}^{c_ib_i}f_{(b_1,\cdots,b_n)}

此时有:T(f×g)=T(f)T(g)T(f\times g)=T(f)\cdot T(g),后面的点乘为对应位置相乘。由该变换的点值意义容易证明这一点。

同时,根据单位根反演:

i=0n1ωnxi=[nx]n\sum_{i=0}^{n-1}\omega_n^{xi}=[n|x]*n

x=ijx=i-j,得到 i=0n1ωnixωniy=[nxy]n\sum_{i=0}^{n-1}\omega_n^{ix}\omega_n^{-iy}=[n|x-y]*n,由此容易发现 DFT 的逆变换为:

T1(f)(c1,,cn)=1libi=1nωlicibif(b1,,bn)T^{-1}(f)_{(c_1,\cdots,c_n)}=\frac 1{\prod l_i}\sum_b\prod_{i=1}^n\omega_{l_i}^{-c_ib_i}f_{(b_1,\cdots,b_n)}

考虑使用卷积形式表示转移,对于特殊转移,考虑将其写成如下形式:

f(0,,0)=x+i=1npif(δ1i,,δni)f(a1,,an)=y+i=1npif(a1,,ai1,(ai+1)modli,ai+1,,an)f_{(0,\cdots,0)}=x+\sum_{i=1}^np_if_{(\delta_{1i},\cdots,\delta_{ni})}\\ f_{(a_1,\cdots,a_n)}=y+\sum_{i=1}^np_if_{(a_1,\cdots,a_{i-1},(a_i+1)\mod l_i,a_{i+1},\cdots,a_n)}

其中 x,yx,y 为未知变量,则转移可以写成如下卷积形式:

f=f×p+cp(δ1i,,δni)=pic(0,,0)=x,c(a1,,an)=yf=f\times p+c\\ p_{(\delta_{1i},\cdots,\delta_{ni})}=p_i\\ c_{(0,\cdots,0)}=x,c_{(a_1,\cdots,a_n)}=y

没有涉及到的位置值均为 00

同时可以发现,从 (0,,0)(0,\cdots,0) 出发,第一次回到 (0,,0)(0,\cdots,0) 停止的过程相当于进行一次操作,接下来到达 (0,,0)(0,\cdots,0) 时停止,从而答案为 i=1npif(δ1i,,δni)\sum_{i=1}^np_if_{(\delta_{1i},\cdots,\delta_{ni})},而这等于 x-x。因此求出 xx 即可得到答案。

使用 DFT,得到 T(f)(1T(p))=T(c)T(f)(1-T(p))=T(c),即如下形式:

(cfciωlibici)(1ipiωlibi)=x+yiωliaici,b(\sum_cf_{c}\prod_i \omega_{l_i}^{b_ic_i})*(1-\sum_ip_i\omega_{l_i}^{b_i})=x+y*\prod_{i}\omega_{l_i}^{a_ic_i},\forall b

b=(0,,0)b=(0,\cdots,0),则得到 0=x+y0=x+y,从而 y=xy=-x,因此得到:

(cfciωlibici)(1ipiωlibi)=x(1iωliaici)T(f)b=x1iωliaibi1ipiωlibi(b(0,,0))(\sum_cf_{c}\prod_i \omega_{l_i}^{b_ic_i})*(1-\sum_ip_i\omega_{l_i}^{b_i})=x(1-\prod_{i}\omega_{l_i}^{a_ic_i})\\ T(f)_b=x*\frac{1-\prod_{i}\omega_{l_i}^{a_ib_i}}{1-\sum_ip_i\omega_{l_i}^{b_i}}(b\neq (0,\cdots,0))

xx 右侧的系数是定值,因此对于 b(0,,0)b\neq (0,\cdots,0),可以使用 xx 表示 T(f)bT(f)_b,这样差一点可以通过 IDFT,用 xx 表示所有 ff

fc=1ilibT(f)biωlibicif_{c}=\frac 1{\prod_i l_i}\sum_bT(f)_b\prod_i\omega_{l_i}^{-b_ic_i}

但可以发现 T(f)(0,,0)T(f)_{(0,\cdots,0)} 对任何 fcf_c 系数都是 11,因此如果取两个 ff 相减,就消去了这一项。而之前的限制有:

f(a1,,an)=1,f(0,,0)=0f_{(a_1,\cdots,a_n)}=1,f_{(0,\cdots,0)}=0

相减,带入 IDFT 的结果,得到:

1ilib(0,,0)T(f)b(1+iωlibiai)=1b(0,,0)x1iωliaibi1ipiωlibi(1+iωlibiai)=ilix=ilib(0,,0)(1iωliaibi)(1iωliaibi)1ipiωlibi\frac 1{\prod_i l_i}\sum_{b\neq(0,\cdots,0)}T(f)_b(-1+\prod_i\omega_{l_i}^{-b_ia_i})=1\\ \sum_{b\neq(0,\cdots,0)}x*\frac{1-\prod_{i}\omega_{l_i}^{a_ib_i}}{1-\sum_ip_i\omega_{l_i}^{b_i}}*(-1+\prod_i\omega_{l_i}^{-b_ia_i})=\prod_i l_i\\ -x=\frac{\prod_i l_i}{\sum_{b\neq(0,\cdots,0)}\frac{(1-\prod_{i}\omega_{l_i}^{a_ib_i})(1-\prod_{i}\omega_{l_i}^{-a_ib_i})}{1-\sum_i p_i\omega_{l_i}^{b_i}}}

这解决了一个 aa 的情况,因此只需要对于所有 a(0,,0)a\neq (0,\cdots,0),求和上述式子,再加一即可得到答案。

考虑求出所有分母,即:

b(0,,0)(1iωliaibi)(1iωliaibi)1ipiωlibi=b(0,,0)2iωliaibiiωliaibi1ipiωlibi\sum_{b\neq(0,\cdots,0)}\frac{(1-\prod_{i}\omega_{l_i}^{a_ib_i})(1-\prod_{i}\omega_{l_i}^{-a_ib_i})}{1-\sum_i p_i\omega_{l_i}^{b_i}}\\ =\sum_{b\neq(0,\cdots,0)}\frac{2-\prod_{i}\omega_{l_i}^{a_ib_i}-\prod_{i}\omega_{l_i}^{-a_ib_i}}{1-\sum_i p_i\omega_{l_i}^{b_i}}

注意到题目给出的 wiw_i 相当于单位根 ωli\omega_{l_i},因此带入 wiw_i 计算,首先求出所有 11ipiωlibi\frac 1{1-\sum_i p_i\omega_{l_i}^{b_i}},由题目限制一定逆存在。然后考虑转移得到所有 aa 的上式值,常数部分可以求和整体加,对于一个 iωliaibi\prod_i \omega_{l_i}^{a_ib_i} 的部分,可以发现这个转移相当于一个高维 DFT,对每一维分别 Bluestein 即可。后一部分可以使用对称性快速解决。再算一次可以慢速解决

考虑计算任意长度 DFT:bj=i=0l1aiωijb_j=\sum_{i=0}^{l-1}a_i\omega^{ij},这不能直接使用 NTT 求出。

Bluestein's Algorithm 为如下方式:

考虑 ij=Ci+j2Ci2Cj2ij=C_{i+j}^2-C_i^2-C_j^2,从而整理得到:

bjωCj2=iaiωCi2ωCi+j2b_j*\omega^{C_j^2}=\sum_i a_i*\omega^{-C_i^2}*\omega^{C_{i+j}^2}

那么右侧可以分为和 ii 相关的部分与和 i+ji+j 相关的部分,做一个差卷积即可。

复杂度 O(llogl)O(l\log l),与 NTT 相同,且不需要考虑 ω\omega 是不是真正的单位根,对于任意 ω\omega 都能做。

转移复杂度为 O(mlogm)O(m\log m)li=m,logli=logm\prod l_i=m,\sum\log l_i=\log m),需要使用任意模数多项式卷积。

最后再求逆求和即可得到答案。由于答案在模意义下存在,唯一可能的问题在于可能若干式子求和后分母抵消,但随机情况下出现这次求逆无法表示的概率近似为 nmod\frac n{mod},此时抵消的概率不超过 1mod\frac 1{mod},由于答案模意义下存在,这种情况的条件概率为 nmod2\frac n{mod^2},因此可以不考虑该情况直接做。

复杂度 O(mlogm)O(m\log m)

fun fact: 原题面有一句话:“小 Z 不关心游戏持续的时间”,因为步骤重复次数的期望正好是 li\prod l_i,与 pp 无关(只要 p0p\neq 0

Code
#include<cstdio>
#include<cmath>
using namespace std;
#define N 1059000
#define M 23
int n,mod,su=1,l[M],w[M],p[M],sp,as=1,sv;
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
struct comp{double a,b;};
comp operator +(comp a,comp b){return (comp){a.a+b.a,a.b+b.b};}
comp operator -(comp a,comp b){return (comp){a.a-b.a,a.b-b.b};}
comp operator *(comp a,comp b){return (comp){a.a*b.a-a.b*b.b,a.a*b.b+a.b*b.a};}
comp gr[2][N*2],c1[N],c2[N],c3[N],c4[N],fft[N];
int rev[N*2];
void init(int d=20)
{
	double pi=acos(-1);
	for(int l=2;l<=1<<d;l<<=1)for(int i=0;i<l;i++)rev[i+l]=(rev[(i>>1)+l]>>1)|((i&1)*(l>>1));
	for(int t=0;t<2;t++)
	for(int l=2;l<=1<<d;l<<=1)for(int i=0;i<l>>1;i++)
	gr[t][l+i]=(comp){cos(2*pi/l*i),sin(2*pi/l*i*(t?1:-1))};
}
void dft(int s,comp *a,int t)
{
	for(int i=0;i<s;i++)fft[rev[i+s]]=a[i];
	for(int l=2;l<=s;l<<=1)
	for(int i=0;i<s;i+=l)
	for(int j=0;j<l>>1;j++)
	{
		comp v1=fft[i+j],v2=fft[i+j+(l>>1)]*gr[t][l+j];
		fft[i+j]=v1+v2;fft[i+j+(l>>1)]=v1-v2;
	}
	double tp=t?1:1.0/s;
	for(int i=0;i<s;i++)a[i]=(comp){fft[i].a*tp,fft[i].b*tp};
}
void mtt(int l,int *a,int *b,int *c)
{
	for(int i=0;i<l;i++)
	{
		c1[i]=(comp){a[i]>>15,0};c2[i]=(comp){a[i]&32767,0};
		c3[i]=(comp){b[i]>>15,0};c4[i]=(comp){b[i]&32767,0};
	}
	dft(l,c1,1);dft(l,c2,1);dft(l,c3,1);dft(l,c4,1);
	for(int i=0;i<l;i++)
	{
		comp v1=c1[i]*c3[i],v2=c1[i]*c4[i]+c2[i]*c3[i],v3=c2[i]*c4[i];
		c1[i]=v1,c2[i]=v2,c3[i]=v3;
	}
	dft(l,c1,0);dft(l,c2,0);dft(l,c3,0);
	for(int i=0;i<l;i++)
	{
		long long f1=c1[i].a+0.4,f2=c2[i].a+0.4,f3=c3[i].a+0.4;
		c[i]=(((f1%mod)<<30)+((f2%mod)<<15)+f3)%mod;
	}
}
int a[N],b[N],c[N];
int v1[N],v2[N],f[N],g[N],rw[N];
void bluestein(int l,int *f,int *g,int w)
{
	if(l<=64)
	{
		for(int i=0;i<l;i++)g[i]=0;
		for(int i=0,a=1;i<l;i++,a=1ll*a*w%mod)
		for(int j=0,b=1;j<l;j++,b=1ll*b*a%mod)
		g[j]=(g[j]+1ll*f[i]*b)%mod;
		return;
	}
	for(int i=0,a=1,b=1;i<=l*2;i++,a=1ll*a*b%mod,b=1ll*b*w%mod)rw[i]=a;
	int le=1;while(le<l*2)le<<=1;
	for(int i=0;i<le;i++)a[i]=b[i]=0;
	for(int i=0;i<l*2;i++)a[i]=rw[i];
	for(int i=1;i<=l;i++)b[i]=1ll*pw(rw[l-i],mod-2)*f[l-i]%mod;
	mtt(le,a,b,c);
	for(int i=0;i<l;i++)g[i]=1ll*c[i+l]*pw(rw[i],mod-2)%mod;
}
void trs(int *v)
{
	for(int i=1,le=1;i<=n;le*=l[i],i++)
	for(int j=0;j<su;j+=le*l[i])
	for(int k=0;k<le;k++)
	{
		for(int p=0;p<l[i];p++)f[p]=v[j+k+p*le];
		bluestein(l[i],f,g,w[i]);
		for(int p=0;p<l[i];p++)v[j+k+p*le]=g[p];
	}
}
void dfs(int d,int x,int v)
{
	if(d==0){v1[x]=v;return;}
	x*=l[d];
	for(int i=0,q=1;i<l[d];i++,q=1ll*q*w[d]%mod)dfs(d-1,x,(v+mod-1ll*q*p[d]%mod)%mod),x++;
}
void dfs2(int d,int x,int y)
{
	if(d==0){v2[x]=(2ll*(sv+mod)-v1[x]-v1[y])%mod;return;}
	x*=l[d],y*=l[d];
	for(int i=0;i<l[d];i++)dfs2(d-1,x+i,y+(l[d]-i)%l[d]);
}
int main()
{
	int mx=0,li=1;
	scanf("%d%d%*d",&n,&mod);
	for(int i=1;i<=n;i++)scanf("%d%d%d",&l[i],&p[i],&w[i]),su*=l[i],sp+=p[i],mx=mx<l[i]?l[i]:mx;
	for(int i=1;i<=n;i++)p[i]=1ll*p[i]*pw(sp,mod-2)%mod;
	while((1<<li)<mx*2)li++;
	init(li);
	dfs(n,0,1);
	for(int i=0;i<su;i++)v1[i]=pw(v1[i],mod-2),sv=(sv+v1[i])%mod;
	trs(v1);
	dfs2(n,0,0);
	for(int i=0;i<su;i++)as=(as+1ll*su*pw(v2[i],mod-2))%mod;
	printf("%d\n",as);
}
D8 T2 #5 造数据
Problem

对于一张连通且无重边无自环的无向图 GG,使用如下方式生成一张有向图 GG'

GG' 的每个点对于 GG 的每条边,使用如下方式得到 GG' 的边:

对于 GG 中的每个点,将其出边按照连向的点的编号排序,然后在这些出边间按照顺序连一条链:第一条出边在 GG' 中向第二条连有向边,第二条向第三条连边,以此类推。

定义有向图 GG' 的权值为如下问题的方案数:

GG' 中每一个点一个 {0,1,2}\{0,1,2\} 之间的权值,每条边一个 {0,1}\{0,1\} 的权值,满足如下条件:

  1. 如果点权为 00,则相邻边(包含入边出边)边权均为 00
  2. 如果点权为 22,则相邻边边权均为 11
  3. 如果一点入度为 22,则其点权等于两条入边边权和。
  4. 如果一点出度为 22,则其点权等于两条出边边权和。
  5. 对于任意一条有向边 sts\to tss 的权值不能小于 tt 的权值。

可以发现 GG' 的权值与点标号无关,因此不需要考虑 GG' 的标号,但 GG 的标号是需要考虑的。

给定 nn,你需要找到一个 nn 个点且弱连通(看成无向边后连通)的有向图 GG',使得其能被一张 nn 条边的无向图生成,且 GG' 的权值最大。输出最大权值以及生成 GG' 的无向图 GG

n400n\leq 400,输入给定

1s,512MB1s,512MB

partial: n7n\leq 7 | n11n\leq 11

Sol

考虑无向图变为有向图的过程,每个点会向有向图中加入一条链,可以发现这些链至少满足如下限制:

  1. 每个点正好被链经过两次,对应 GG 中边的两个端点。(链可以只有一个点,进而没有边)
  2. 不存在环。(考虑 GG 中每条边两端点编号和,连边一定从和更小的连向和更大的)

可以发现如果 GG' 合法,则它的边必须能被划分成若干条这样的链,使得这些链满足上述限制。但这样的限制不是充分条件。例如,考虑不能有重边的限制,可以得到上述划分中任意两条链最多只能有一个交点的限制,考虑点编号顺序可以得到更多限制,但这些限制难以描述,因此难以处理。

如果只考虑前两个限制,则限制非常简单:图 GG' 是弱连通的 DAG,且任意点入度出度不超过 22。合法的 GG' 一定满足该条件。考虑求出满足这一条件的图中权值最大的图,再判断该图是否合法。

现在考虑求一个给定的 GG' 的权值。考虑枚举点的点权,则点权需要满足限制 55,即如果 sts\to tss 的点权不小于 tt 的点权,进一步如果 ss 能到达 tt 则上一条件也被满足。

此时考虑限制 1,21,2,那么点权为 22 的点相邻的边边权全部为 11,点权为 00 的点相邻的边边权全部为 00。此时可以发现点权为 0,20,2 的点的限制 3,43,4 直接被满足了。同时,如果有点权为 22 的点连向点权为 00 的点,则这种取点权的方式一定不合法(边权无法满足限制)。在去除这两种情况后,只需要再考虑所有点权为 11 的点以及它们间连边的情况。

对于点权为 11 的点,考虑限制 3,43,4 可以得到,如果它有两条入边则两条入边边权必须不同,如果它有两条出边则出边边权必须不同,除此之外没有其它限制。此时还没有确定的边权为点权为 11 的点之间的边,此时有如下情况:

  1. 两个点权为 22 的点连向一个点权为 11 的点(或者连出到两个点权为 00 的点),此时无解。
  2. 点权分别为 2,12,1 的点连向点权为 11 的点,则 1,11,1 之间的边边权必须为 00,另外一种情况类似,要求边权必须为 11
  3. 连向它的两条边边权均为 11,此时要求两条边边权不同。

如果只有第三种情况,考虑将 GG' 中此时还没有决定边权(两侧点权均为 11)的边看作点,限制看成点之间连边,则相当于求将点染色使得任意限制造成的边两侧颜色不同的方案数。此时可以发现:

  1. GG' 中每条边只会在连入连出的边分别有一次限制,因此在新的图中,任意点度数不超过 22

从而图由环和链组成,此时可以发现链方案数为 22,偶环方案数为 22,但奇环方案数为 00。但还可以发现如下性质:

  1. 图中没有奇环。

证明:考虑一个此时度数为 22 的点,它连出的两条边对应两个限制,其中一定有一个是两条连入边的限制,另外一个是两条连出边的限制,从而两种限制必定交错,因此环长为偶数。

再考虑加入第二种情况,可以发现第二种情况只会影响到新图中某些链的端点,要求它们只能取某种颜色。在一般情况下,被这种情况限制到的连通块方案数会从 22 变为 11,但矛盾的限制可能导致无解。特判这种情况即可。(实际上这种情况是一个点权 0/20/2 的点,通过交错方向的边经过点权为 11 的点,到达一个点权为 0/20/2 的点,情况 11 是它的特例)事实上这里忘了判也能通过,原因是交错方向的长链过于不优秀

这样可以在 O(m)O(m) 的复杂度内求出 GG' 在一种点权情况下的边权方案数,进而可以得到 O(3nm)O(3^n*m) 的求权值方式,进一步确定一点权值后相邻点最多有两种选择,因此复杂度为 O(2nm)O(2^n*m),结合暴力搜图可以解决 n7n\leq 7。可以发现得到的 GG' 都是一条链,这显然容易构造原图:在原图中也构造一条 n+1n+1 个点的链,GG' 中第 ii 条边的方向由链中第 i,i+2i,i+2 个点的大小关系决定。此时极其容易构造:一种简单方式是每次让剩余第一个位置取最大值或者最小值。

观察求权值的方式,可以发现如下结论:

最优的 GG' 将边看成无向边后一定是一棵树。

证明:如果不是树,找到一个环,环上一定存在相邻两条边指向同一个点。(否则不是 DAG)

考虑删去这两条边中的一条,则图仍然连通,且:

  1. 对于之前在点权部分合法的点权方案(即 sts\to tss 点权大于等于 tt 点权,且不能 ss 点权为 22tt 点权为 00),可以发现删边不会使方案变得不合法,因此之前合法的点权方案现在仍然合法,
  2. 考虑每一种之前的点权方案,在边权部分,之前两条边被限制直接相连,在一个连通块中,删去一条边后可能剩余一个连通块,也可能分裂为两个连通块,但两种情况下方案数都不会变少:连通块数量增加了(如果合法,答案为 2c2^c,其中 cc 为连通块数),可能的不合法情况变少了。

因此权值不会变小,从而删边更优。由于需要保证弱连通,因此得到的为一棵树。

此时将搜图换为搜树,可以提升一些速度。

但可以发现,在 nn 小的情况下,得到的都是一条链。因此猜想答案的 GG' 是一条链,可以得到 O(n4n)O(n4^n) 的暴力,可以打表通过 n15n\leq 15 且答案正确。

但是完全不会证,应该可以调整但是调整不动

考虑链上如何算权值,链上所有限制只和相邻的三个值(点和边)有关(点边点,边点边的限制),因此可以设 dpi,0/1/2,0/1dp_{i,0/1/2,0/1} 表示考虑了前 ii 个点,点 ii 点权为 0/1/20/1/2,连向 i+1i+1 的边边权为 0/10/1 的方案数。

考虑类似 dp of dp 的方式求最优解,设 fi,0/1f_{i,0/1} 表示考虑了前 ii 个点,ii 连向 i+1i+1 的边方向为 0/10/1 时,之前所有可能连边方式的内层 dpdp 状态(3×23\times 2)组成的集合。

这样状态数可能极大,但有一个显然的剪枝:如果一种状态的每个值都不超过另外一个状态的对应值,则这个状态显然不优,可以被替代。可以发现使用这一剪枝后,剩余状态数非常少:n=400n=400 时总状态数只有 10410^4 级别。虽然值得维护需要高精度,但也能在 1s 内得到解。

然后 submit 可以发现,这个解确实是最优的,记录转移输出方案即可通过。注意一下常数,最暴力的 O(size2)O(size^2) 剪枝也能在 1s 内跑完 n=400n=400,甚至不需要打表。

事实上可以发现解的形式如下:

a1a2a3a4a5b1b2b3b4b5a1a2a_1\rightarrow a_2\rightarrow a_3\rightarrow a_4\rightarrow a_5\rightarrow b_1\leftarrow b_2\leftarrow b_3\leftarrow b_4\leftarrow b_5\leftarrow a_1\rightarrow a_2\cdots

其中由于 nmod5n\bmod 5 的问题,端点上可能有几段长度为 44,甚至 n=7n=7 的解是两段长度为 33n=8n=83+43+4,但剩余情况都是中间 55,两侧尽量少的 44

证明:别想了

事实上不猜想 GG' 是链也能做,注意到对于 GG' 是树的情况可以树形 dp 求解,那么做树形 dp 的 dp of dp 即可,但这样细节极其多,跑得应该非常慢。std 是这样然后打表的,我不知道

Code

高精度板子是从后面某两个题抄过来的

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 413
struct integer{
	typedef long long s64;
	typedef unsigned long long u64;
	typedef unsigned __int128 u128;
	vector<u64> v;
	integer(){}
	integer(u64 x){if(x)v={x};}
	explicit operator bool()const{return !v.empty();}
	explicit operator u64()const{return v.empty()?0:v[0];}
	bool operator ==(const integer &a)const{return v==a.v;}
	bool operator !=(const integer &a)const{return v!=a.v;}
	bool operator <(const integer &a)const
	{
		s64 l1=v.size(),l2=a.v.size();
		if(l1!=l2)return l1<l2;
		while(l1)
		{
			l1--;
			if(v[l1]!=a.v[l1])return v[l1]<a.v[l1];
		}
		return 0;
	}
	bool operator >(const integer &a)const{return a<(*this);}
	bool operator <=(const integer &a)const{return !(a<*this);}
	bool operator >=(const integer &a)const{return !(*this<a);}
	integer &operator +=(const integer &a)
	{
		u64 s1=v.size(),s2=a.v.size(),vi=0;
		if(s1<s2)v.resize(s2);
		for(u64 i=0;i<s1||i<s2||vi;i++)
		{
			u128 rs=vi;
			if(i<s1)rs+=v[i];if(i<s2)rs+=a.v[i];
			if(i>=s1&&i>=s2)v.push_back(0);
			vi=(rs>>32)>>32;
			v[i]=rs;
		}
		return *this;
	}
	integer &operator -=(const integer &a)
	{
		u64 s1=v.size(),s2=a.v.size(),vi=0;
		for(u64 i=0;i<s1||i<s2||vi;i++)
		{
			u128 rs=vi;if(vi==-1)rs=-1;
			if(i<s1)rs+=v[i];if(i<s2)rs-=a.v[i];
			if(i>=s1&&i>=s2)v.push_back(0);
			vi=(rs>>32)>>32;
			v[i]=rs;
		}
		while(v.size()&&v.back()==0)v.pop_back();
		return *this;
	}
	integer operator +(const integer &a){return integer(*this)+=a;}
	integer operator -(const integer &a){return integer(*this)-=a;}
};
#define ll long long
ll bs=1e18;
struct integer2{
	vector<ll> v;
	explicit operator bool()const{return !v.empty();}
	void add(ll a)
	{
		ll rs=a;
		for(int i=0;rs;i++)
		{
			if(i>=v.size())v.push_back(0);
			rs+=v[i];
			v[i]=rs%bs;
			rs/=bs;
		}
	}
	void mul(ll a)
	{
		__int128 rs=0;
		for(int i=0;i<v.size()||rs;i++)
		{
			if(i>=v.size())v.push_back(0);
			rs+=(__int128)a*v[i];
			v[i]=rs%bs;
			rs/=bs;
		}
	}
};
void output(integer a)
{
	integer2 s;
	if(a.v.size())
	for(int i=a.v.size()-1;i>=0;i--)
	{
		s.mul(1ll<<32);s.add(a.v[i]>>32);
		s.mul(1ll<<32);s.add(a.v[i]&((1ll<<32)-1));
	}
	if(!s){printf("0\n");return;}
	printf("%lld",s.v.back());
	for(int i=(int)s.v.size()-2;i>=0;i--)printf("%018lld",s.v[i]);
	printf("\n");
}
struct sth{
	integer v[3][2];
};
bool operator ==(sth a,sth b)
{
	for(int i=0;i<3;i++)for(int j=0;j<2;j++)if(a.v[i][j]!=b.v[i][j])return 0;
	return 1;
}
bool operator <(sth a,sth b)
{
	int fg=0;
	for(int i=0;i<3;i++)for(int j=0;j<2;j++)
	{
		if(a.v[i][j]>b.v[i][j])return 0;
		if(a.v[i][j]<b.v[i][j])fg=1;
	}
	return fg;
}
struct sta{
	sth rv,lv;
	int lx;
};
vector<sta> dp[N][2];
vector<sta> reduce(vector<sta> a)
{
	vector<sta> as;
	for(int i=0;i<a.size();i++)
	{
		int fg=1;
		for(int j=0;j<as.size()&&fg;j++)if(a[i].rv<as[j].rv||a[i].rv==as[j].rv)fg=0;
		for(int j=i+1;j<a.size()&&fg;j++)if(a[i].rv<a[j].rv)fg=0;
		if(fg)as.push_back(a[i]);
	}
	return as;
}
int n,st[N];
int main()
{
	scanf("%d",&n);
	sth fr;for(int i=0;i<3;i++)for(int j=0;j<2;j++)fr.v[i][j]=1;
	sta f1=(sta){fr,fr,0};dp[1][0].push_back(f1);dp[1][1].push_back(f1);
	for(int i=1;i<=n;i++)for(int j=0;j<2;j++)
	{
		dp[i][j]=reduce(dp[i][j]);
		for(int p=0;p<dp[i][j].size();p++)
		{
			sta ri=dp[i][j][p];
			for(int r=0;r<2;r++)
			{
				sta nt;nt.lv=ri.rv;nt.lx=j;
				for(int sx=0;sx<3;sx++)for(int sy=0;sy<2;sy++)
				for(int tx=0;tx<3;tx++)for(int ty=0;ty<2;ty++)
				{
					if(j&&sx<tx)continue;
					if(!j&&sx>tx)continue;
					if(j!=r&&tx!=sy+ty)continue;
					if(sx==0&&sy)continue;
					if(tx==0&&sy+ty)continue;
					if(sx==2&&1-sy)continue;
					if(tx==2&&2-sy-(i+1==n?1:ty))continue;
					if((sx^tx)==2)continue;
					nt.rv.v[tx][ty]+=nt.lv.v[sx][sy];
				}
				dp[i+1][r].push_back(nt);
			}
		}
	}
	integer as=0;
	sta rx;
	for(int t=0;t<2;t++)for(int i=0;i<dp[n][t].size();i++)
	{
		sta r1=dp[n][t][i];
		integer si=r1.rv.v[0][0]+r1.rv.v[1][0]+r1.rv.v[2][0];
		if(si>as)as=si,rx=r1;
	}
	output(as);
	int lb=1,rb=n+1;
	for(int i=n-1;i>=1;i--)
	{
		int ri=rx.lx;
		if(ri)st[n-i]=rb--;else st[n-i]=lb++;
		for(int j=0;j<dp[i][ri].size();j++)if(dp[i][ri][j].rv==rx.lv){rx=dp[i][ri][j];break;}
	}
	st[n]=lb;st[n+1]=rb;
	printf("%d\n",n+1);
	for(int i=1;i<=n;i++)printf("%d %d\n",st[i],st[i+1]);
}
D8 T3 #16 WereYouLast
Problem

交互。

实现一个函数,满足如下条件:

  1. 函数传入两个参数 n,mn,m,返回值为 {0,1}\{0,1\} 中的一个。
  2. 函数只能访问 nn 个外部 bit,可以对这些 bit 进行修改。初始时所有 bit 为 00
  3. 其需要满足如下功能:从初始状态开始,连续调用 2m2^m 次该函数,其在最后一次调用返回 11,其余调用返回 00

在此基础上,设函数单次调用读 bit 的次数最大值为 c1c_1,写 bit 的次数最大值为 c2c_2,则 max(c1,c2)\max(c_1,c_2) 应当尽量小,得分按照 max(c1,c2)\max(c_1,c_2) 给出,max(c1,c2)lim\max(c_1,c_2)\leq lim 时获得满分。

在测试时,函数总共会被调用 2m+12^{m+1} 次。

part 1: n=10,m=10,lim=10n=10,m=10,lim=10

part 2: n=105,m=26,lim=6n=10^5,m=26,lim=6

14s,1024MB14s,1024MB

Sol

对于 part 1,注意到需要操作 2m2^m 次,有 n=mn=m 个 bit 且没有访问限制,因此考虑直接使用 bit 记录当前操作次数,每次给操作次数加一,溢出时返回 11 即可。

part 2 的访问次数有限制,因此不能这样做。此时访问固定的位置显然不可能达成目标(状态数只有 262^6),因此必须通过之前访问的值决定接下来访问的位置,才可能超过 262^6 的状态数。

最完全的方式是每一步都由之前的访问结果决定,即一个 66 层的决策树,但这样的形式过于复杂,考虑如下的简化形式:

使用 5+325+32 个 bit,每次先访问前 55 个 bit,根据结果组合成一个二进制数,根据这个值访问后 3232 个 bit 中的一个,然后修改所有 bit。

可以发现这相当于如下模型:有 3232 个 bit,有一个指针指向某一个 bit,每次调用可以访问指针指向的 bit 的值,然后修改指针指向的位置。

考虑与 part 1 类似的操作:实现二进制每次 +1+1 的过程。但现在一次只能操作一个 bit,因此考虑模拟进位,即如下过程:

  1. 如果当前指向 bit 为 00,则将指向 bit 修改为 11,指针改为指向 00(最低位)。
  2. 否则,将指向 bit 改为 00,指针指向下一个位置(进位过程)。

可以发现在某一次回到 00 结束进位后,设当前数为 vv,则使用的步数为 vv 加上总的进位次数,这等于 i0v2i\sum_{i\geq 0}\lfloor\frac v{2^i}\rfloor

考虑加 2k2^k,这样最后一次返回 00 前会第一次经过位置 kk,这容易被识别出来,可以发现加到 2k2^k 的总操作步数为 1+2+4++2k=2k+111+2+4+\cdots+2^k=2^{k+1}-1,这可以用来构造某一个 2m2^m

因此考虑如下过程:

使用上述方式模拟加一和进位的过程,当第一次访问到 m1m-1 时,此时一定经过了 2m12^m-1 步,那么要求指向 m1m-1 时下一步指向 mm,指向 mm 时返回 11(之前返回 00)即可达成目标。

注意这样不能过 part 1 真阴间,因此需要分开写。

Code
bool query(int);
void modify(int,bool);
bool WereYouLast(int n,int m)
{
	if(n==1024)
	{
		int rv=1;
		for(int i=1;i<=10;i++)rv+=query(i),modify(i,rv&1),rv>>=1;
		return rv;
	}
	int l=1;while((1<<l)<n)l++;
	int id=0;for(int i=1;i<=5;i++)id=id*2+query(i);
	if(id==l)return 1;
	int rv=query(6+id),nt;
	if(rv||id==l-1)nt=id+1,rv=0;
	else rv=1,nt=0;
	modify(6+id,rv);
	for(int i=5;i>=1;i--)modify(i,nt&1),nt>>=1;
	return 0;
}
D9 T1 #7 子集匹配
Problem

交互。

给定正整数 n,kn,k,保证 2k>n2k>n

交互库会以某种顺序向你询问所有 {1,2,,n}\{1,2,\cdots,n\}kk 元子集,询问一个集合 SS 时,你需要在线返回 SS 的一个大小为 k1k-1 的子集。

你需要满足任意一种子集最多被返回一次。

n27n\leq 27

3s,4MB3s,4MB(不包含交互库占用空间)

Sol

这里将子集看成一个长度为 nn0101 串,第 ii 位为 11 当且仅当子集中包含 ii。上述操作相当于将串中的某一个 11 变为 00

考虑最困难的情况:n=2k1n=2k-1,此时 kk 元子集的数量和 k1k-1 元子集的数量相等,即需要构造一个一一对应。

通过乱搞,暴力 dinic,手玩等方式,可以发现一些规律。例如在某种 dinic 的结果中,删去 nn 当且仅当将 11 看成 (00 看成 ) 后前 n1n-1 个位置构成合法括号序列。然后容易看出规律

考虑将 11 看作 +1+1,将 00 看作 1-1。考虑这样操作后序列的前缀和。求出前缀和最小的位置,如果有多个选择最靠后的位置。此时这个位置一定不是最后一个(s0=0,sn=1s_0=0,s_n=1),且它的下一个字符一定是 11(否则前缀和更小),考虑将下一个字符变为 00,这样得到了一个方向的映射。

现在考虑是否存在反方向映射。在之前的映射中,上述过程选择了前缀和最小的位置中最靠右的位置,然后相当于将这个位置之后的前缀和全部 2-2。此时,这个位置的下一个位置前缀和为之前的最小值减一,由于之后的位置中不存在初始时前缀和小于等于原先最小值的位置,因此 2-2 后之后的位置中前缀和不小于原先的最小值减一。

这说明操作后,删去位置的下一个位置处的前缀和一定是当前序列前缀和的最小值,且它是第一个最小值(之前部分前缀和不变,不小于原最小值)。因此可以构造反向映射:找到前缀和最小的第一个位置(因为此时 sn=1s_n=-1,这个位置不可能是开头),将它前面的 00 改为 11

从而这是双射,因此它满足要求。且单次构造只需要 O(n)O(n) 时间 O(1)O(1) 空间。

考虑一般情况(n<2kn<2k),直接的想法是将问题拆成若干个 n=2k1n=2k-1 的子问题。如果 n<2k1n<2k-1,考虑钦定最后一位不修改。这样变成了两个 (n1,k1),(n1,k)(n-1,k-1),(n-1,k) 的子问题。这两个子问题一定满足 n<2kn<2k,因此不断对 n<2k1n<2k-1 的问题做这样的拆分,就可以得到若干 n=2k1n=2k-1 的子问题,进而可以对每个子问题求解。这样实现时找到每个子集属于的子问题即可。

更进一步,从前缀和的角度考虑,可以发现上述过程相当于如下步骤:如果当前 sn>1s_n>1,则钦定最后一位不动,考虑 sn1s_{n-1},如果仍然 >1>1 则继续钦定向前,直到找到一个 =1=1 的位置,对前面部分使用 n=2k1n=2k-1 的构造。

但结合构造取最小前缀和的步骤可以发现,后面这段不考虑的部分前缀和 >1>1,因此不影响最小前缀和。从而两部分合并起来相当于与之前一样的步骤:找到最靠右的最小前缀和,将其下一个位置变为 00。即直接使用 n=2k1n=2k-1 的构造即可满足要求。

单次询问复杂度 O(n)O(n),空间 O(1)O(1)

bonus:还有一车构造方式

bonus2:交互库 naive 地按照字典序询问了所有 0101 串,此时有一堆错误的 naive 做法可以通过。

Code
#include "hall.h"
int solve(int n,int K,int s)
{
	int nw=0,mn=0,as=0;
	for(int i=1;i<=n;i++)
	{
		nw+=(s>>i-1)&1?1:-1;
		if(nw<=mn)mn=nw,as=i;
	}
	return s-(1<<as);
}
D9 T2 #15 Slight Hope
Problem

给定一棵 nn 个点的有根树,11 为根。设点 ii 的父亲为 fif_i,则保证 fi<if_i<ifif_i 单调不降。点有点权 aia_i

qq 次询问,每次给出 l,rl,r,求所有满足如下条件的二元组 (x,y)(x,y)axaya_x*a_y 之和,答案模 998244353998244353

  1. x,y[l,r]x,y\in[l,r]
  2. LCA(x,y)[l,r]LCA(x,y)\in[l,r]

强制在线

n,q2.5×105,fi1fi<in,q\leq 2.5\times 10^5,f_{i-1}\leq f_i<i

2s,512MB2s,512MB

Sol

由于 fi<if_i<i,考虑向上跳的求 LCA 过程可以发现 LCA(x,y)LCA(x,y) 一定小于等于 min(x,y)\min(x,y),同时 xxyy 的路径上点权最小值即为 LCA(x,y)LCA(x,y)

因此点对合法当且仅当 x,yx,y 树上简单路径上点权均在 [l,r][l,r] 间。从而答案相当于只保留编号在 [l,r][l,r] 中的点,图中每个连通块点权和的平方和。

离线的 n*polylog 也是困难的,因此考虑 O(nn)O(n\sqrt n) 的做法。

首先考虑做单次询问,考虑依次将满足 fil,irf_i\geq l,i\leq r 的点合并到父亲所在的连通块中,这样合并后编号在 [l,r][l,r] 中的点就构成了只保留它们时的连通块。那么考虑每次合并时记录 2vavb2*v_a*v_b,其中 vv 为两侧连通块分别的权值和。最后合并过程的权值和即为 vi2\sum v_i^2 增加了多少,因此权值加上 i=lrai2\sum_{i=l}^r a_i^2 即为答案。(这样做是为了避免考虑其它点的点权)

由于 ff 单调不降,上述操作相当于是将编号在某个区间内的点向父亲合并。那么问题变为询问 F(l,r)F(l,r) 表示将 l,rl,r 中的点按照某种顺序(显然结果与顺序无关)向它的父亲合并,每次合并的权值为 2vavb2*v_a*v_b,求合并后的总权值。

如果固定 ll,则问题容易解决:考虑按照从小到大的顺序进行合并,维护每个点最后被合并到了哪个连通块(连通块中一定存在唯一一个编号小于 ll 的点(归纳易证),用这个点作为代表),那么一个点被合并到的位置可以通过它父亲被合并到的位置求出,这样容易算出每个点合并时的权值(2ai2a_i*连通块之前的点权),答案即为这个权值的一段前缀和。

考虑分块,取 O(n)O(\sqrt n)ll,从这些 ll 开始做。考虑此时如何计算一个询问 F(li,ri)F(l_i,r_i) 的答案。

因为连通块删点是困难的,考虑找到 lil_i 右侧的第一个 ll。为了简便,不妨设 fl>fl1f_l>f_{l-1},这可以通过在选择 ll 时特殊处理解决。

通过 ll 开始的过程,可以得到合并了 [l,ri][l,r_i] 部分后的情况,但此时还需要合并 li,li+1,,l1l_i,l_i+1,\cdots,l-1 这些点。由于 fl>fl1f_l>f_{l-1}[l,ri][l,r_i] 部分的合并没有涉及到 fli,,fl1f_{l_i},\cdots,f_{l-1} 这些点,这些点的点权仍然为初始权值。因此只需要知道当前 li,,l1l_i,\cdots,l-1 所在连通块的权值,即可对这一段暴力合并求出剩余应该加的权值。

因此对于一个 ll,记上一个选择的为 ll',则需要在过程中记录 [r(l),l1][r(l'),l-1] 中每个点所在连通块的权值。这里 r(x)r(x) 指第一个满足 fi>fxf_i>f_x 的点,因为询问转化为 F(li,ri)F(l_i,r_i) 时一定有 fli>fli1f_{l_i}>f_{l_i-1},从而 ll 在这一段内的可能询问只会从 r(l)r(l') 开始。可以发现只要每 n\sqrt n 个点选择一个,找到第一个 ff 与它相等的点作为 ll,则这样选择每一段 [r(l),l1][r(l'),l-1] 长度都不超过 O(n)O(\sqrt n),从而合并 ll 部分可以做到 O(n)O(\sqrt n)

但显然不能对于每个 ll 的每个 rr 都记录 O(n)O(\sqrt n) 个权值。因此考虑每 n\sqrt nrr 记录一次,剩下的 rr 暴力加入。因此得到如下做法:

SS 个点选择一个,找到第一个 ff 与它相等的点作为 ll,得到 O(nS)O(\frac nS)ll

从每个 ll 开始,按顺序合并右侧点,对于每个点记录它被合并到了哪个连通块,增加了多少权值。每 SS 个点记录一次当前权值和以及 [r(l),l1][r(l'),l-1] 所在连通块分别的权值。

对于询问,如果转化为 FF 后找到的第一个 l>ril>r_i 则直接暴力,否则先找到对应 ll 以及当前最后一个小于等于 rir_i 的记录点,暴力做最后 O(S)O(S) 次合并,得到权值和以及 [r(l),l1][r(l'),l-1] 所在连通块的权值,再暴力合并 [li,l1][l_i,l-1] 部分得到答案。

可以发现复杂度为 O(qS+n2S)O(qS+\frac{n^2}S),但空间复杂度为 O(n2S)O(\frac{n^2}S),且需要三个数组:对每个点记录合并到的位置和权值,每 SS 个点记录的状态,状态有 12\frac 12 的常数(只向后记录),因此空间为 3n22S\frac{3n^2}{2S}。在 S=500S=500 时这需要 768MB768MB

此时可以直接增大 SSS=1000S=1000 应该也能过),也可以进行一些压缩。考虑一个 ll 开始的过程,如果一个点合并到了 [r(l),l1][r(l'),l-1] 中,则因为维护了这些连通块的权值,不需要记录加入这个点后答案的增加量,而是可以通过连通块权值和在暴力时直接算出增加的权值。而如果一个点没有合并到 [r(l),l1][r(l'),l-1] 中,则可以发现询问时不需要关心这个点合并到了哪里,此时只需要记录权值。

因此权值与合并到的位置两者可以合并,可以使用符号位判断是哪种情况,这样空间变为 n2S\frac{n^2}{S},取 SS 略大于 500500 即可。

最优复杂度为 O(nq)O(n\sqrt q)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 250500
#define M 505
#define mod 998244353
int sz=753;
int n,q,v[N],f[N],su[N],ls,l,r;
int v2[N],fi[N],si[M],ci,lb[M],li[M],ri[M],as1[M][M];
vector<int> sr[M],vl[M][M];
void init(int id)
{
	lb[id]=lower_bound(f+1,f+n+1,si[id])-f;
	li[id]=lower_bound(f+1,f+n+1,si[id-1]+1)-f;ri[id]=lb[id]-1;
	sr[id].resize(n-lb[id]+1);for(int i=0;i<=(n-lb[id])/sz;i++)vl[id][i].resize(ri[id]-li[id]+1);
	for(int i=1;i<=n;i++)v2[i]=v[i];
	int su=0;
	for(int i=lb[id];i<=n;i++)
	{
		int fr=f[i];
		if(fr>=lb[id])fr=fi[fr];
		fi[i]=fr;
		if(fr>=li[id]&&fr<=ri[id])sr[id][i-lb[id]]=-fr;
		else sr[id][i-lb[id]]=2ll*v2[fr]*v[i]%mod;
		su=(su+2ll*v2[fr]*v[i])%mod;
		v2[fr]=(v2[fr]+v[i])%mod;
		if((i-lb[id])%sz==0)
		{
			int v=(i-lb[id])/sz;
			for(int j=li[id];j<=ri[id];j++)vl[id][v][j-li[id]]=v2[j];
			as1[id][v]=su;
		}
	}
}
int query(int l,int r)
{
	int as=(su[r]+mod-su[l-1])%mod;
	int sl=lower_bound(f+1,f+n+1,l)-f;
	if(sl>r)return as;
	int id=lower_bound(si+1,si+ci+1,l)-si;
	if(lb[id]>r||id==ci+1)
	{
		for(int i=r;i>=sl;i--)as=(as+2ll*v2[i]*v2[f[i]])%mod,v2[f[i]]=(v2[f[i]]+v2[i])%mod;
		for(int i=sl;i<=r;i++)v2[f[i]]=v[f[i]],v2[i]=v[i];
		return as;
	}
	int k=(r-lb[id])/sz;
	as=(as+as1[id][k])%mod;
	for(int i=li[id];i<=ri[id];i++)v2[i]=vl[id][k][i-li[id]];
	for(int i=k*sz+1;i<=r-lb[id];i++)
	{
		int tp=sr[id][i],u=i+lb[id];
		if(tp>=0)as=(as+tp)%mod;
		else tp=-tp,as=(as+2ll*v2[tp]*v[u])%mod,v2[tp]=(v2[tp]+v[u])%mod;
	}
	for(int i=lb[id]-1;i>=sl;i--)as=(as+2ll*v2[i]*v2[f[i]])%mod,v2[f[i]]=(v2[f[i]]+v2[i])%mod;
	for(int i=li[id];i<=ri[id];i++)v2[i]=v[i],v2[f[i]]=v[f[i]];
	return as;
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),su[i]=(su[i-1]+1ll*v[i]*v[i])%mod;
	for(int i=2;i<=n;i++)scanf("%d",&f[i]);
	si[ci=1]=1;
	for(int i=sz;i<=n;i+=sz)if(f[i]!=si[ci])si[ci+1]=f[i],ci++;
	for(int i=1;i<=ci;i++)init(i);
	for(int i=1;i<=n;i++)v2[i]=v[i];
	while(q--)
	{
		scanf("%d%d",&l,&r);l^=ls;r^=ls;
		printf("%d\n",ls=query(l,r));
	}
}
D9 T3 #26 海胆
Problem

定义一个图为海胆,当且仅当它满足如下条件:

  1. 连通,无自环,可以有重边。
  2. 存在恰好一个简单环(经过两条重边算简单环)
  3. 除去该简单环外,其余点度数不超过 22

即该图形如环向外连出若干条链。

给出 nn 条边,第 ii 条边连接 ui,viu_i,v_i

qq 次询问,每次给出 l,rl,r,求有多少个区间 [l,r][l',r'] 满足如下条件:

  1. llrrl\leq l'\leq r'\leq r
  2. 只保留 [l,r][l',r'] 中的边,保留与这些边相邻的点,得到的图为一个海胆。

n,q106n,q\leq 10^6

7s,1024MB7s,1024MB

Sol

考虑将条件分成如下几部分:

  1. 删去一条边后,图中不存在环,且删去边会形成环。
  2. 除去删去边的两端在删去后得到的树上的链外,其它点度数小于等于 22
  3. 连通。

考虑对于一个 rr,有哪些 ll 满足条件。

对于条件 11,考虑只保留 [1,r][1,r] 的边,按照编号为边权求最大生成树,此时从后往前第一条不能被加入的边即为必须被删除的边,因此考虑求出最后第一条和第二条不在最大生成树上的边,合法的 ll 只能在这两条边的编号之间(只包含右端点)。

而在 rr11nn 的过程中,这相当于需要支持加入一条边权最大的边并动态维护最大生成树,这可以使用 LCT 解决:对每条边新建一个虚点,虚点点权等于边的权值,虚点向两侧端点连边,原先的点没有权值。加入边时,首先判断是否连通(这里使用并查集可以更快),不连通则直接加入,否则查询两端点在链上路径上的最小权值,即为路径上边权最小的边,判断是否需要替换。那么需要支持 link,cut,查询路径最小值,普通 LCT 即可。同时如果替换了边,则可以使用替换边更新不在 MST 上的第一第二条边。

对于条件 22,可以发现如果环不变,则随着 rr 增大度数只会增大,因此 rr 增大时合法的 ll 一定增大。这种情况可以双指针解决:记录当前 l,rl,rrr 增大时不断增大 ll,判断是否合法。对于判断是否合法的问题,考虑记录当前哪些点度数超过了 22,则合法当且仅当度数大于 22 的点都在非树边端点的路径上。这同样可以使用 LCT 解决:给每个点另外一种权值表示是否度数大于 22,合法当且仅当非树边端点路径上所有点的这一权值和等于度数大于 22 的总点数。那么这部分同样使用之前的 LCT 维护即可,最小值与权值和都只需要在实 splay 上合并。

但环可能随着 rr 增大而改变(即条件 11 中的判断),但可以发现环改变后,条件 11 给出的 ll 下界是之前的 ll 上界加一,即如果在每次环改变后将双指针左端点变为条件 11 给出的 ll,且向右扫时到条件 11 给出的 rr 加一后停止,则这样不会影响总的合法范围,且移动的总量不会超过 O(n)O(n),这样在环改变时仍然满足了要求。(简单的实现方式是只要求双指针左端点在超过条件 11 给出的 rr(即第一条非树边)后停止,其余情况不考虑)

可以发现前两个条件会限制合法的 ll 在一段区间内。

现在考虑最后一个限制,可以发现该限制等价于在最大生成树上,[l,r][l,r] 中的边涉及到的所有点连通。而在森林中有经典结论:若干个点连通当且仅当点数减去它们间存在的边数等于 11,而不连通时这一结果大于 11。(即点减边容斥,可以发现连通块都是树,而树满足点减边等于 11,因此多个连通块点减边大于 11

因此一个 ll 满足第三个限制,当且仅当 [l,r][l,r] 中的边涉及到的点数减去 [l,r][l,r] 中在最大生成树上的边数等于 11

考虑如何维护这一差值。首先,边部分容易维护:对于每条在生成树中的边,它会使得 $l\leq $其编号的点的差值减去 11。在 LCT 维护最大生成树时判断即可。

然后考虑点,对于每个点,记在只考虑 [1,r][1,r] 中边时,最后一次涉及到它是边 rbirb_i,则点 ii 会对 [1,rbi][1,rb_i] 部分差值加上 11。可以发现随着 rr 增加,每次会将两个 rbrb 改为当前的 rr,相当于在差值上做两次区间加一。

那么对差值的操作相当于每次增大 rrO(1)O(1) 次区间加减,合法的 ll 为所有满足差值为 11 的位置。由于差值不小于 11,这容易维护。简单的方式是线段树维护节点内最小值和最小值出现次数,然后区间询问即可得到有多少个 ll 满足全部限制。

但询问为有多少个子区间合法。考虑如下处理方式:每个 ll 记录一个答案,对于每个 rr,在所有合法的 ll 处加一,询问变为在到达 rr 时询问 [l,r][l,r] 部分的答案和。可以发现这样得到的即为合法子区间数。

那么差值部分需要维护两个序列 a,ba,b,支持如下操作:

  1. aa 区间加减。
  2. 给定一个区间,将区间内所有满足 ai=1a_i=1 的点的对应 bib_i 加一,保证区间内 minai1\min a_i\geq 1

仍然使用之前的线段树,除去维护最小值和最小值出现次数外,额外维护权值和以及一个标记,标记表示该节点内需要将所有 aia_i 等于节点内最小值的位置加多少。这个标记是容易下传(判断两侧最小值)且满足结合律(整体加减不影响最小值位置)的,直接线段树维护即可。(这里不能标记为将所有 ai=1a_i=1 的点进行操作,因为这样和整体加减操作无法结合)

复杂度 O(nlogn)O(n\log n) 常数大考验

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 2005000
#define ll long long
int n,s[N][2],q,a,b;
ll as[N];
vector<pair<int,int> > qu[N];
struct segt{
	struct node{int l,r,lz,mn,ct,l1;ll s1;}e[N*2];
	void doit1(int x,int v){e[x].lz+=v;e[x].mn+=v;}
	void doit2(int x,ll v1){e[x].s1+=e[x].ct*v1;e[x].l1+=v1;}
	void pushdown(int x)
	{
		if(e[x].lz)doit1(x<<1,e[x].lz),doit1(x<<1|1,e[x].lz),e[x].lz=0;
		if(e[x].l1)
		{
			if(e[x<<1].mn==e[x].mn)doit2(x<<1,e[x].l1);
			if(e[x<<1|1].mn==e[x].mn)doit2(x<<1|1,e[x].l1);
			e[x].l1=0;
		}
	}
	void pushup(int x)
	{
		e[x].mn=min(e[x<<1].mn,e[x<<1|1].mn);
		e[x].ct=e[x<<1].ct*(e[x<<1].mn==e[x].mn)+e[x<<1|1].ct*(e[x<<1|1].mn==e[x].mn);
		e[x].s1=e[x<<1].s1+e[x<<1|1].s1;
	}
	void build(int x,int l,int r)
	{
		e[x].l=l;e[x].r=r;e[x].ct=r-l+1;e[x].mn=-1;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(x<<1,l,mid);build(x<<1|1,mid+1,r);
	}
	void modify1(int x,int l,int r,int v)
	{
		if(e[x].l>r||e[x].r<l)return;
		if(e[x].l>=l&&e[x].r<=r){doit1(x,v);return;}
		pushdown(x);
		modify1(x<<1,l,r,v);modify1(x<<1|1,l,r,v);
		pushup(x);
	}
	void modify2(int x,int l,int r)
	{
		if(e[x].l>r||e[x].r<l)return;
		if(e[x].l>=l&&e[x].r<=r){if(e[x].mn==0)doit2(x,1);return;}
		pushdown(x);
		modify2(x<<1,l,r);modify2(x<<1|1,l,r);
		pushup(x);
	}
	ll query(int x,int l,int r)
	{
		if(e[x].l>r||e[x].r<l)return 0;
		if(e[x].l>=l&&e[x].r<=r)return e[x].s1;
		pushdown(x);
		return query(x<<1,l,r)+query(x<<1|1,l,r);
	}
}tr;

int ch[N][2],fa[N],lz[N],mn[N],su[N],vi[N],st[N],cs;
bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void doit(int x){ch[x][0]^=ch[x][1]^=ch[x][0]^=ch[x][1];lz[x]^=1;}
void pushdown(int x){if(lz[x])doit(ch[x][0]),doit(ch[x][1]),lz[x]=0;}
void pushup(int x){mn[x]=min(mn[ch[x][0]],mn[ch[x][1]]);if(x>n)mn[x]=min(mn[x],x-n);su[x]=su[ch[x][0]]+su[ch[x][1]]+vi[x];}
void rotate(int x)
{
	int f=fa[x],g=fa[f],tp=ch[f][1]==x;
	if(nroot(f))ch[g][ch[g][1]==f]=x;fa[x]=g;
	ch[f][tp]=ch[x][!tp];fa[ch[x][!tp]]=f;
	ch[x][!tp]=f;fa[f]=x;
	pushup(f);pushup(x);
}
void splay(int x)
{
	st[cs=1]=x;
	while(nroot(st[cs]))st[cs+1]=fa[st[cs]],cs++;
	while(cs)pushdown(st[cs]),cs--;
	while(nroot(x))
	{
		int f=fa[x],g=fa[f];
		if(nroot(f))rotate((ch[g][1]==f)^(ch[f][1]==x)?x:f);
		rotate(x);
	}
}
void access(int x)
{
	int tp=0;
	while(x)
	{
		splay(x);
		ch[x][1]=tp;pushup(x);
		tp=x;x=fa[x];
	}
}
void makeroot(int x){access(x);splay(x);doit(x);}
void split(int x,int y){makeroot(x);access(y);splay(y);}
void link(int x,int y){makeroot(x);fa[x]=y;}
void cut(int x,int y){split(x,y);splay(x);ch[x][1]=fa[y]=0;pushup(x);}
int di[N],cr,lb=1,f[N],ls[N],lc1,lc2;
int finds(int x){return f[x]==x?x:f[x]=finds(f[x]);}
void modify(int x,int v)
{
	access(x);splay(x);
	vi[x]=v;pushup(x);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&s[i][0],&s[i][1]);
	scanf("%d",&q);
	for(int i=1;i<=q;i++)scanf("%d%d",&a,&b),qu[b].push_back(make_pair(a,i));
	for(int i=0;i<=n*2;i++)mn[i]=i>n?i-n:n+1;
	for(int i=1;i<=n;i++)f[i]=i;
	tr.build(1,1,n);
	for(int i=1;i<=n;i++)
	{
		tr.modify1(1,ls[s[i][0]]+1,i,1);ls[s[i][0]]=i;
		tr.modify1(1,ls[s[i][1]]+1,i,1);ls[s[i][1]]=i;
		if(finds(s[i][0])==finds(s[i][1]))
		{
			split(s[i][0],s[i][1]);
			int ri=mn[s[i][1]];
			if(lc1<ri)lc2=lc1,lc1=ri;
			else if(lc2<ri)lc2=ri;
			cut(ri+n,s[ri][0]);cut(ri+n,s[ri][1]);
			tr.modify1(1,1,ri,1);
		}
		link(i+n,s[i][0]);
		link(i+n,s[i][1]);
		f[finds(s[i][0])]=finds(s[i][1]);
		tr.modify1(1,1,i,-1);
		di[s[i][0]]++;if(di[s[i][0]]==3)modify(s[i][0],1),cr++;
		di[s[i][1]]++;if(di[s[i][1]]==3)modify(s[i][1],1),cr++;
		while(lb<=lc1)
		{
			if(!lc1)break;
			split(s[lc1][0],s[lc1][1]);
			if(su[s[lc1][1]]==cr)break;
			di[s[lb][0]]--;if(di[s[lb][0]]==2)modify(s[lb][0],0),cr--;
			di[s[lb][1]]--;if(di[s[lb][1]]==2)modify(s[lb][1],0),cr--;
			lb++;
		}
		int li=max(lc2+1,lb),ri=lc1;
		if(ri&&li<=ri)tr.modify2(1,li,ri);
		for(int j=0;j<qu[i].size();j++)
		as[qu[i][j].second]=tr.query(1,qu[i][j].first,i);
	}
	for(int i=1;i<=q;i++)printf("%lld\n",as[i]);
}
D10 T1 #17 抽奖机
Problem

nn 个数,初始全部为 00

mm 个非负二元组 (ai,bi)(ai+bin)(a_i,b_i)(a_i+b_i\leq n)。考虑如下操作:

  1. 选择一个二元组,设选择的为 (a,b)(a,b)
  2. {1,2,,n}\{1,2,\cdots,n\} 中选择一个大小为 aa 的集合 AA,选择一个大小为 bb 的集合 BB,使得两个集合不交。
  3. 将下标在 AA 中的数加一,将下标在 BB 中的数加二,然后所有数对 33 取模。

可以发现一次操作有 i=1mn!ai!bi!(naibi)!\sum_{i=1}^m\frac{n!}{a_i!b_i!(n-a_i-b_i)!} 种方式(即使转动相同,如果选择了不同的二元组,则认为是不同方式)。

现在连续进行 kk 次操作。对于所有可能的 (a,b)(a,b),求在 (i=1mn!ai!bi!(naibi)!)k(\sum_{i=1}^m\frac{n!}{a_i!b_i!(n-a_i-b_i)!})^k 种可能的情况中有多少种情况使得最后所有数中有 aa 个为 11bb 个为 00。答案对 109+910^9+9 取模。

n120,m105,k1018n\leq 120,m\leq 10^5,k\leq 10^{18}

1.5s,256MB1.5s,256MB

partial: n80n\leq 80

Sol

可以发现一个数的操作相当于长度为 33 的循环卷积:

(f×g)(a1,,an)=i,bi+ciai(mod3)f(b1,,bn)g(c1,,cn)(f\times g)_{(a_1,\cdots,a_n)}=\sum_{\forall i,b_i+c_i\equiv a_i(\bmod 3)}f_{(b_1,\cdots,b_n)}g_{(c_1,\cdots,c_n)}

f(a1,,an)f_{(a_1,\cdots,a_n)} 表示一次操作使得第一个数加了 a1a_1,第二个数加了 a2a_2……,的方案数,设 v(a1,,an)v_{(a_1,\cdots,a_n)} 表示答案。由上述形式容易得到:

v=f×f×f××f=fkv=f\times f\times f\times\cdots\times f=f^k

根据经典的 DFT 方式,定义:

T(f)(a1,,an)=bf(b1,,bn)iω3aibiT(f)_{(a_1,\cdots,a_n)}=\sum_{b}f_{(b_1,\cdots,b_n)}*\prod_i \omega_3^{a_ib_i}

则有:

T(f×g)a=T(f)aT(g)aT1(f)(a1,,an)=3nbf(b1,,bn)iω3aibiT(f\times g)_a=T(f)_a*T(g)_a\\ T^{-1}(f)_{(a_1,\cdots,a_n)}=3^{-n}\sum_{b}f_{(b_1,\cdots,b_n)}*\prod_i \omega_3^{-a_ib_i}

(可以参考之前 Numbers 的部分)

这样得到了 O(n3n)O(n3^n) 的暴力:对 ff 做 DFT,每个位置 kk 次方,IDFT。由于 3mod13|mod-1,单位根在模意义下存在,因此很好做(不存在可以强行扩域)。

考虑 ff 的性质,可以发现,对于一个 f(a1,,an)f_{(a_1,\cdots,a_n)},设 aa 中有 c1c_111c2c_222,则 f(a1,,an)f_{(a_1,\cdots,a_n)} 等于输入中二元组 (c1,c2)(c_1,c_2) 出现的次数。

ff 只和下标中 1,21,2 出现次数有关,出现次数相同的位置值相同。通过这一对称性,可以发现如下结论:

T(f)T(f) 也满足该对称性,即值只和下标中 1,21,2 出现次数有关。

证明:ffTT 的转移系数都与下标顺序无关。即对于任意排列 pp,有 f(a1,,an)=f(ap1,,apn)f_{(a_1,\cdots,a_n)}=f_{(a_{p_1},\cdots,a_{p_n})}

如果两个下标 b,cb,c 满足存在排列 ppbi=cpib_i=c_{p_i},则:

T(f)_{(b_1,\cdots,b_n)}&=\sum_a f_{(a_1,\cdots,a_n)}*\prod_i \omega_3^{a_ib_i}\\ &=\sum_a f_{(a_1,\cdots,a_n)}*\prod_i \omega_3^{a_ic_{p_i}}\\ &=\sum_a f_{(a_1,\cdots,a_n)}*\prod_i \omega_3^{a_{p^{-1}_i}c_i}\\ &=\sum_a f_{(a_{p^{-1}_i},\cdots,a_{p^{-1}_n})}*\prod_i \omega_3^{a_{p^{-1}_i}c_i}\\ &=\sum_d f_{(d_1,\cdots,d_n)}*\prod_i \omega_3^{d_ic_i}(d=a_{p^{-1}})\\ =T(f)_{(c_1,\cdots,c_n)}

那么变换/逆变换后都只需要记录 O(n2)O(n^2) 个位置的值(每种 1/21/2 数量对应的值),即可表示整体的结果。

考虑快速转移,只需要对于每一对 (a,b),(c,d)(a,b),(c,d),求出所有有 aa11bb22 的状态转移到某一个有 cc11dd22 的状态的系数 iω3aibi\prod_{i}\omega_3^{a_ib_i} 的权值之和,即可得到答案。

目标状态中有 ncdn-c-d00cc11dd22,考虑初始状态在对应位上的值,可以发现答案为:

[xayb](1+x+y)ncd(1+ωx+ω2y)c(1+ω2x+ωy)d[x^ay^b] (1+x+y)^{n-c-d}(1+\omega x+\omega^2 y)^c(1+\omega^2 x+\omega y)^d

其中每一项代表一位,11 代表选 00xx 代表选 11yy 代表选 22

那么枚举 c,dc,d,算出这个二元生成函数,其每一项对应了一个转移权值。对每个 c,dc,d 求出二元生成函数即可完成转移。每次暴力乘所有项即可得到 O(n5)O(n^5) 的做法。

但可以发现不需要每次重新求整个生成函数,可以发现 (c,d)(c,d)(c,d+1)(c,d+1) 的二元生成函数只差了一个 1+ω2x+ωy1+x+y\frac{1+\omega^2 x+\omega y}{1+x+y} 的乘积,乘一项可以 O(n2)O(n^2),而除一项也可以做到 O(n2)O(n^2)(考虑逆操作),因此可以 O(n4)O(n^4) 求出所有生成函数,然后就完成了转移。

逆变换部分可以重新做一遍上述过程,也可以使用对称性直接得到新结果。

复杂度 O(n4)O(n^4)

bonus: 好像有高级 O(n3)O(n^3) 做法。

Code
#include<cstdio>
using namespace std;
#define N 137
#define mod 1000000009
int n,m,a,b,f[N][N],g[N][N],w=115381398,rw=884618610;
long long k;
int fr[N],ifr[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int v1[N][N],v2[N][N];
void pmul(int n,int f[][N],int a,int b)
{
	for(int i=n-1;i>=0;i--)for(int j=n-1-i;j>=0;j--)
	f[i+1][j]=(f[i+1][j]+1ll*f[i][j]*a)%mod,
	f[i][j+1]=(f[i][j+1]+1ll*f[i][j]*b)%mod;
}
void pdiv(int n,int f[][N],int a,int b)
{
	for(int i=0;i<n;i++)for(int j=0;i+j<n;j++)
	f[i+1][j]=(f[i+1][j]+mod-1ll*f[i][j]*a%mod)%mod,
	f[i][j+1]=(f[i][j+1]+mod-1ll*f[i][j]*b%mod)%mod;
}
void dft(int n,int f[][N],int g[][N],int w,int rw)
{
	for(int i=0;i<=n;i++)for(int j=0;i+j<=n;j++)g[i][j]=v1[i][j]=0;
	v1[0][0]=1;for(int i=1;i<=n;i++)pmul(n,v1,1,1);
	for(int x=0;x<=n;x++)
	{
		for(int i=0;i<=n;i++)for(int j=0;i+j<=n;j++)v2[i][j]=v1[i][j];
		for(int y=0;x+y<=n;y++)
		{
			int rs=0;
			for(int i=0;i<=n;i++)for(int j=0;i+j<=n;j++)rs=(rs+1ll*v2[i][j]*f[i][j])%mod;
			g[x][y]=rs;
			pdiv(n,v2,1,1);pmul(n,v2,rw,w);
		}
		pdiv(n,v1,1,1);pmul(n,v1,w,rw);
	}
}
int main()
{
	scanf("%d%d%lld",&n,&m,&k);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),f[a][b]++;
	dft(n,f,g,w,rw);for(int i=0;i<=n;i++)for(int j=0;i+j<=n;j++)g[i][j]=pw(g[i][j],k%(mod-1));
	dft(n,g,f,rw,w);
	fr[0]=1;for(int i=1;i<=n;i++)fr[i]=1ll*fr[i-1]*i%mod;
	ifr[n]=pw(fr[n],mod-2);for(int i=n;i>=1;i--)ifr[i-1]=1ll*ifr[i]*i%mod;
	int ir=pw(3,mod-1-n);
	for(int i=0;i<=n;i++,printf("\n"))
	for(int j=0;i+j<=n;j++)printf("%d ",1ll*f[i][j]*fr[n]%mod*ifr[i]%mod*ifr[j]%mod*ifr[n-i-j]%mod*ir%mod);
}
D10 T2 #32 中奖率
Problem

给定一个长度为 nn0101 序列 tt。将该序列无限循环得到序列 ss,即 si=t(i1modn)+1s_i=t_{(i-1\bmod n)+1}

viv_iss 的前 ii 个元素中 11 的比例,即 vi=1ij=1isjv_i=\frac 1i\sum_{j=1}^is_j,由此得到一个无限长的实数序列。

考虑将所有下标按照对应 vv 从大到小排序,如果 vv 相同则按照下标从小到大排序,这样得到了由自然数构成的序列 cc

qq 次询问,每次询问为如下类型之一:

  1. 给定 kk,求出 cc 中的第 aa 个元素。
  2. 给定 aa,求出 aa 在序列中的位置。如果答案为 ++\infty,则输出 inf

n105,q20,a1010000n\leq 10^5,q\leq 20,a\leq 10^{10000}

3s,512MB3s,512MB

partial: tp=2tp=2

Sol

考虑循环对 vv 的影响,可以发现 vi+nk=si+ksni+nkv_{i+nk}=\frac{s_i+ks_n}{i+nk}。考虑减去 snn\frac{s_n}n,则通分得到:

vi+nksnn=nsiisnn(i+nk)v_{i+nk}-\frac{s_n}n=\frac{n*s_i-i*s_n}{n(i+nk)}

可以发现此时分子与 kk 无关,因此如果将模 nn 同余的下标放在一组,则一组内的 vi+nksnnv_{i+nk}-\frac{s_n}n 可以表示为上述形式。

ri=nsiisnr_i=n*s_i-i*s_n,则可以发现这一组内的 vi+nksnnv_{i+nk}-\frac{s_n}n 符号与 rir_i 相同。因为 nn 这一组的 rn=0r_n=0,可以发现此时有如下情况:

  1. 不存在 ri>0r_i>0,则最大的 vi+nksnn=0v_{i+nk}-\frac{s_n}n=0,且有无限个满足该条件的数。此时排序后只会有 ri=0r_i=0 的数按照下标排列,因此询问 22 询问一个对应 ri<0r_i<0 的位置时答案为 inf,否则答案为在它之前且也满足 ri=0r_i=0 的位置数量加一。那么统计出一个循环内 ri=0r_i=0 的位置数量,就容易得到一个数的 rank。同时查询 kth 也容易做到。两者都只需要 O(1)O(1) 次高精度数 kk 对低精度数进行取模/乘法等操作,因此一次询问的复杂度为 O(n+loga)O(n+\log a)
  2. 存在 ri>0r_i>0,此时有无限个位置满足 visnn>0v_i-\frac{s_n}n>0,因此询问 22 询问 ri0r_i\leq 0 位置时答案均为 inf。同时接下来只需要考虑 ri>0r_i>0 的这些组。可以发现每一组的权值都收敛到 00,因此剩余询问答案一定有限。

现在只考虑第二种情况。需要考虑的所有 vi+nksnnv_{i+nk}-\frac{s_n}n 都大于 00,且分子不变分母变化,考虑翻转过来,即考虑 (vi+nksnn)1=n(i+nk)ri(v_{i+nk}-\frac{s_n}n)^{-1}=\frac{n(i+nk)}{r_i},可以发现在 ri>0r_i>0 的这些组中,这个值越小则 viv_i 越大。

现在考虑查询 rank 的操作 22,对于询问点类似的求出 narj\frac{na}{r_j},其中 jjaa 对应的组。考虑在每一组中求 rank,对于组 ii,先考虑将相等的下标全部算入,此时组内应该被统计的 i+nki+nkkk 满足:

narjn(i+nk)riari(i+nk)rjkrianrjin\frac{na}{r_j}\geq \frac{n(i+nk)}{r_i}\\ a*r_i\geq (i+nk)*r_j\\ k\leq \frac{r_i*a}{n*r_j}-\frac in

但还需要考虑相等的问题,可以发现如果两个位置 i+nk,j+nki+nk,j+nk'vv 相等,则 n(i+nk)ri=n(j+nk)rj\frac{n(i+nk)}{r_i}=\frac{n(j+nk')}{r_j},可以发现 rir_i 越大下标越靠后。而如果两个 rir_i 相等,则 i+nk=j+nki+nk=j+nk',即此时只有可能是询问下标,因此应该统计。所以只需要对于 ri>rjr_i>r_j 的组不统计相等情况,其余组统计相等情况。从而答案为:

1in,ri>0max(0,riairj[ri>rj]nrj+1)\sum_{1\leq i\leq n,r_i>0}\max(0,\lfloor\frac{r_i*a-i*r_j-[r_i>r_j]}{n*r_j}\rfloor+1)

aa 非常大,如果暴力计算该结果,则运算复杂度为 O(nloga)O(n\log a),不能接受。

但可以发现分母只和 jj 有关,也就是只和 aa 有关。因此考虑如果 a2nrja\geq 2*n*r_j,则将 aa 减去 nrjn*r_j 后,答案正好减少 1in,ri>0ri\sum_{1\leq i\leq n,r_i>0}r_i。(由于有减法的存在,最好不要减到 nrjn*r_j 以内)。

从而可以先计算 anrj\lfloor\frac a{n*r_j}\rfloor,将 aa 减去若干倍的 nrjn*r_j,答案加上若干倍的 1in,ri>0ri\sum_{1\leq i\leq n,r_i>0}r_i。此时剩余的 aa 不超过 2nrj=O(n3)2*n*r_j=O(n^3),因此可以使用 __int128 计算。复杂度变为 O(n+loga)O(n+\log a)

现在考虑求 kth,即询问 11

首先考虑答案对应的 narj\frac{na}{r_j}。令其等于 nxnx,可以发现这等价于找到最小的实数 x(xarj)x(x\rightarrow \frac a{r_j}),使得在上一步中询问 nx\leq n*x 的数数量总和大于等于 aa,即:

1in,ri>0max(0,rixin+1)a\sum_{1\leq i\leq n,r_i>0}\max(0,\lfloor\frac{r_i*x-i}{n}\rfloor+1)\geq a

显然 xx 应该等于某个 i+nkri\frac{i+nk}{r_i},因此其分母不超过 n2n^2,但分子可能非常大,不能直接处理。

但与上一种情况类似的,可以发现如果 x2nx\geq 2n,那么将 xx 减去 nn 后,左侧式子值正好减去 1in,ri>0ri\sum_{1\leq i\leq n,r_i>0}r_i。那么类似考虑,如果 a21in,ri>0ria\geq 2\sum_{1\leq i\leq n,r_i>0}r_i,那么此时答案为将 aa 减去一个 1in,ri>0ri\sum_{1\leq i\leq n,r_i>0}r_i 后的 xx 加上 nn

因此可以类似地进行取模,变为 a<21in,ri>0ria<2\sum_{1\leq i\leq n,r_i>0}r_i 的情况,此时可以发现 x3nx\leq 3n,这样就可以二分了。

一种二分有理数的方式是 Stern-Brocot Tree,但还有一种更加大力的方式:

假设已知结果分母不超过 MM,值不超过 NN

考虑所有的 [iM2,i+1M2][\frac i{M^2},\frac{i+1}{M^2}] 区间,可以发现任意两个不同的可能结果不可能落在同一个区间(差不可能小于 1M(M1)\frac 1{M(M-1)}),那么二分 k[0,NM2]k\in[0,NM^2],查询 kM2\frac k{M^2} 即可。

本题中 MM 可以取 n4n^4,这样分子不超过 3n53n^5,上述运算中的最大值不超过 O(n7)O(n^7)__int128 可以存下。这样二分复杂度为 O(logn)O(\log n)常数上天

二分后,可以找到等于存在 xx 的位置的若干组。通过小于等于 xx 的数的数量可以得到此时应该取第几个等于 xx 的位置。由上一部分的结论,xx 相同时位置按照 rir_i 从小到大排序,找到第若干个即可。此时位置 aa 满足 arj=x\frac a{r_j}=x,因此 xrjx*r_j 即为答案。如果使用大力二分方式,可以取答案之上的第一个 kn3\frac k{n^3},然后取 krin3\lfloor\frac{k*r_i}{n^3}\rfloor

最后还需要将 xx 加上若干倍的 nn,即给 arj\frac{a}{r_j} 加上若干倍 nn,可以发现给下标加上若干倍的 nrin*r_i 即可。

复杂度 O(q(nlogn+logk))O(q(n\log n+\log k))

此时大力做法常数非常离谱(全程 __int128,二分 102510^{25},虽然也能过),但可以发现一些结论:

bonus: 取 M=n2M=n^2,能过。

证明:此时可能有若干个本来不相同的有理数 i+nkri\frac{i+nk}{r_i} 被分到了同一组,最后实现时将同一组内部按照 rr 从小到大排序取第 kk 个。因此出现问题当且仅当出现以下情况之一:

  1. 两个相同 rir_i 但值不同的分数被分到同一组。
  2. 两个不同的分数 ab,cd\frac ab,\frac cd 被分到同一组,满足 b<db<da>ca>c(每个下标只出现一次,不会有分子相等情况)

首先情况 11 不可能:riMr_i\leq M。然后考虑情况 22,此时 abcdadcd1d1M\frac ab-\frac cd\geq \frac ad-\frac cd\geq \frac 1d\geq \frac 1M,因此它们也不能被分到一组。

从而此时虽然可能不同的有理数被分到了一组,但结果仍然正确。

这样大力二分就能打过 S-B Tree了

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
//simple integer
#define ll long long
ll bs=1e18;
struct integer{
	vector<ll> v;
	explicit operator bool()const{return !v.empty();}
	void add(ll a)
	{
		ll rs=a;
		for(int i=0;rs;i++)
		{
			if(i>=v.size())v.push_back(0);
			rs+=v[i];
			v[i]=rs%bs;
			rs/=bs;
		}
	}
	void mul(ll a)
	{
		__int128 rs=0;
		for(int i=0;i<v.size()||rs;i++)
		{
			if(i>=v.size())v.push_back(0);
			rs+=(__int128)a*v[i];
			v[i]=rs%bs;
			rs/=bs;
		}
	}
	void div(ll a)
	{
		int sz=v.size();
		ll rs=0;
		for(int i=sz-1;i>=0;i--)
		{
			__int128 ri=(__int128)bs*rs+v[i];
			v[i]=ri/a;rs=ri%a;
		}
		while(v.size()&&v.back()==0)v.pop_back();
	}
	ll mod(ll a)
	{
		int sz=v.size();
		ll rs=0;
		for(int i=sz-1;i>=0;i--)
		{
			__int128 ri=(__int128)bs*rs+v[i];
			rs=ri%a;
		}
		return rs;
	}
};
#define N 105000
char s[N];
integer input()
{
	integer si;
	scanf("%s",s+1);
	ll s1=1,s2=0;
	int le=1;
	while(s[le+1])le++;
	while(le)
	{
		s2+=s1*(s[le]-'0');s1*=10;
		if(s1==bs)si.v.push_back(s2),s1=1,s2=0;
		le--;
	}
	if(s2)si.v.push_back(s2);
	return si;
}
void output(integer s)
{
	if(!s){printf("0\n");return;}
	printf("%lld",s.v.back());
	for(int i=(int)s.v.size()-2;i>=0;i--)printf("%018lld",s.v[i]);
	printf("\n");
}
int n,q,a,su[N],fg;
ll calc(ll a,ll b)
{
	ll si=0;
	//sum la*s/lx/n-i/n
	for(int i=1;i<=n;i++)if(1ll*su[i]*n>1ll*su[n]*i)
	{
		ll la=1ll*su[i]*n-1ll*su[n]*i;
		__int128 ri=(__int128)la*a+b*n-i*b;
		si+=ri/n/b;
	}
	return si;
}
void query1(integer s)
{
	if(!fg)
	{
		int ci=0;
		for(int i=1;i<=n;i++)if(1ll*su[i]*n==1ll*su[n]*i)ci++;
		int ri=s.mod(ci);
		s.div(ci);s.mul(n);
		for(int i=1;i<=n;i++)if(1ll*su[i]*n==1ll*su[n]*i)
		{
			ri--;
			if(!ri)s.add(i);
		}
		output(s);
		return;
	}
	ll s1=0;
	for(int i=1;i<=n;i++)if(1ll*su[i]*n>1ll*su[n]*i)s1+=1ll*su[i]*n-1ll*su[n]*i;
	ll re=s.mod(s1);s.div(s1);
	if(s)s.add(-1),re+=s1;
	ll lb=0,rb=3ll*n*n*n,fr=1ll*n*n,as=0;
	while(lb<=rb)
	{
		ll mid=(lb+rb)>>1;
		if(calc(mid,fr)<re)as=mid,lb=mid+1;
		else rb=mid-1;
	}
	re-=calc(as,fr);
	vector<pair<ll,int> > tp;
	for(int i=1;i<=n;i++)if(1ll*su[i]*n>1ll*su[n]*i)
	{
		ll la=1ll*su[i]*n-1ll*su[n]*i;
		__int128 ri=(__int128)la*as+fr*n-i*fr;
		if(ri/n/fr!=(ri+la)/n/fr)tp.push_back(make_pair(la,i));
	}
	sort(tp.begin(),tp.end());
	int sx=tp[re-1].second;
	ll ry=(__int128)(as+1)*(1ll*su[sx]*n-1ll*su[n]*sx)/fr;
	s.mul(1ll*su[sx]*n-1ll*su[n]*sx);s.mul(n);s.add(ry);
	output(s);
}
void query2(integer s)
{
	int id=s.mod(n);
	if(!id)id=n;
	if(1ll*su[id]*n<1ll*su[n]*id||(1ll*su[id]*n==1ll*su[n]*id&&fg)){printf("inf\n");return;}
	if(1ll*su[id]*n==1ll*su[n]*id)
	{
		s.div(n);
		int ci=0,c2=0;
		for(int i=1;i<=n;i++)if(1ll*su[i]*n==1ll*su[n]*i)ci++,c2+=id<n&&i<=id;
		s.mul(ci);s.add(c2);
		output(s);
		return;
	}
	ll lx=1ll*su[id]*n-1ll*su[n]*id,rs=lx*n,ri=s.mod(rs);
	s.div(rs);
	if(s)s.add(-1),ri+=rs;
	ll s1=0;
	for(int i=1;i<=n;i++)if(1ll*su[i]*n>1ll*su[n]*i)s1+=1ll*su[i]*n-1ll*su[n]*i;
	s.mul(s1);
	//sum la*s/lx/n-i/n
	for(int i=1;i<=n;i++)if(1ll*su[i]*n>1ll*su[n]*i)
	{
		ll la=1ll*su[i]*n-1ll*su[n]*i;
		__int128 r1=(__int128)la*ri-i*lx-(la>lx)+n*lx;
		s.add(r1/n/lx);
	}
	output(s);
}
int main()
{
	scanf("%d%d%s",&n,&q,s+1);
	for(int i=1;i<=n;i++)su[i]=su[i-1]+s[i]-'0';
	for(int i=1;i<n;i++)if(1ll*su[i]*n>1ll*su[n]*i)fg=1;
	while(q--)
	{
		scanf("%d",&a);integer b=input();
		if(a==1)query1(b);else query2(b);
	}
}
D10 T3 #50 染色
Problem

有一个 nnqq 列的网格,初始每个格子都是白色。定义一行的高度为最大的 kk 满足该行前 kk 个位置都是黑色。每一行有一个权值,初始权值均为 00

qq 次操作,每次操作为如下类型之一:

  1. 将第 llrr 行中第 kk 列的格子染黑。
  2. 将第 llrr 行中第 kk 列的格子染白。
  3. 将第 llrr 行中,所有高度最小(在 [l,r][l,r] 中)的行权值加上 vv
  4. 求出第 llrr 行的权值和,答案模 2642^{64}

n,q3×105,k1.5×105n,q\leq 3\times 10^5,k\leq 1.5\times 10^5

4s,1024MB4s,1024MB

Sol

给点 naive 做法.jpg

首先考虑求出每次 33 操作的最小高度。

考虑整体二分,假设已经确定了若干询问的结果在 [l,r][l,r] 之间,则继续二分这些询问时只需要再使用 k[l+1,r]k\in[l+1,r] 部分的修改。

考虑取一个 midmid,判断当前所有询问结果是否大于等于 midmid。那么相当于只考虑 [l+1,mid][l+1,mid] 部分,看某次询问时是否将一个区间的行的这些列全部染黑。

但这仍然无法很好的处理染色操作,考虑将颜色看作 0,10,1,染色操作是区间赋值。此时可以通过预处理,将操作变为总共 O(n)O(n) 次区间加减。变为区间加减后,容易判断是否全部为黑色:将所有区间加减合并到一个 BIT 上维护,询问时判断当前区间总和是否等于区间长度乘上考虑的列数量,即可判断是否全黑。

这样即可进行整体二分,该部分复杂度为 O(qlog2n)O(q\log^2 n)

此时一个 33 操作相当于对这些行中所有第 x+1x+1 列为白色的行加上某个权值。那么有一个 naive 的做法:

对每一列维护一个动态开点线段树,维护这一列的颜色,1,21,2 操作相当于在某一个线段树上区间赋值。33 操作相当于对这棵线段树上区间内为 11 的点权值加一个值,可以打标记。

但询问需要所有线段树在这一区间的和。因此考虑再用一棵线段树维护总和,这棵线段树上的每个点同时记录所有线段树对应的该点上哪些点有标记。询问时,将涉及到的点在所有线段树上同时下传标记,这样即可更新出总和。

考虑如果标记下传到一个颜色相同的区间就直接转化为总权值上的区间加,这样询问的推标记操作不会导致树上新建点,总点数 O(nlogn)O(n\log n)

最大的问题在于一般情况下这样的复杂度是错误的:最坏复杂度 O(nnlogn)O(n\sqrt n\log n)。一个简单的构造是 n\sqrt n 棵线段树,每棵线段树在 n\sqrt n 的倍数处有值,每组操作先对每棵线段树打整体标记,然后询问每个单点下传所有标记。

但该问题的修改不完全是一般的修改,考虑继续分析性质。

首先有如下性质:

  1. 如果初始状态后没有染色操作,则任意 33 操作后,一个线段树节点上最多有一种有效标记(还没有变为整体加的标记)。

证明:如果有一个第 kk 列的标记,说明该区间最小高度至少是 k1k-1,因此这个区间前 k1k-1 列全黑,从而如果有两个标记,kk 更小的那个一定可以被忽略。

接下来只需要再考虑修改带来的影响,考虑两个重叠修改间的影响:

  1. 如果 kk 相同,使用之前的讨论即可。
  2. 如果 kk 变大,考虑重叠部分。如果重叠部分的最小高度在修改间没有变化,则变为之前情况。否则,可能出现一种特殊情况:先在小的 kk 上进行了标记,然后将这一段染黑增大最小高度,再在大的 kk 上标记,这样一个点上就可能有多个标记,与之前情况不同。此时可以发现如果在染色前将区间内 kk 更小的标记全部下传到消失,则复杂度为区间内第 kk 列颜色切换的次数乘上 O(logn)O(\log n)(因为遇到颜色相同的段就会直接结束)。而修改会全部染成黑色,这之后颜色切换的次数会减少区间内颜色切换的次数。考虑设势能为每一列中颜色切换的次数之和,那么每个节点下传标记的复杂度为势能减少量再乘以 O(logn)O(\log n),从而即使全部下传这个标记,复杂度也不超过均摊 O(qlogn)O(q\log n)。因此可以不考虑这一段标记,让它被后面的操作处理,复杂度最多在不考虑这种标记的情况下增加 O(qlogn)O(q\log n)。这说明 kk 大的标记对之前的 kk 小的标记的复杂度影响不超过 O(qlogn)O(q\log n)
  3. 如果 kk 变小,则 kk 大的一次标记可能被之后的操作下传。可以发现如果在这一个 kk 上有 ckc_k 次染色操作,则一次之后的修改最多对这次标记产生 O(logn)O(\log n) 的复杂度,且对这次标记产生的总复杂度不超过 O(klogn)O(k\log n)。同时如果两次考虑的操作间有一个 kk 更大的操作,则 kk 上的标记可以使用上一种情况的讨论消去,因此可以不考虑这种情况。那么现在计算代价的过程可以看出对所有 kk 建笛卡尔树,考虑每个 kk 上的修改被子树内修改产生的复杂度。设一个点上有 aa 次染色,bb 次修改,则它有 bb 个子树,一个子树在该点的代价为 min(sz,a)logn\min(sz,a)*\log n。归纳应该大概好像可能能证明,复杂度不超过 O(qloglogqlogn)O(q\log \log q*\log n)。(一种构造:当前层先染色 q\sqrt q 次,然后操作 q\sqrt q 次,每次操作间间隔 q\sqrt q,变为 q\sqrt q 个子问题,则层数为 O(loglognq)O(\log\log nq),复杂度为上述情况)

因此总复杂度可能是 O(qlog2n)O(q\log^2 n)

事实上在题目数据中,标记下传次数均为 O(n)O(n)。通过上述构造数据得到了 5×1065\times 10^6 次下传,但因为 loglog\log\log 很小,不清楚这东西是 O(qlogn)O(q\log n) 还是有 loglogq\log\log q事实上整体二分一直是最慢的部分

bonus: 有没有人会证这个复杂度啊

Code
#include<cstdio>
#include<vector>
#include<algorithm>
#include<set>
using namespace std;
#define N 300500
#define M 22142500
#define ul unsigned long long
int n,q,s[N][4],mn[N];
ul vi[N];
struct sth{int l,r,v,d;};
vector<sth> si[N];
set<pair<int,int> > s1[N];
void init_s1()
{
	for(int i=1;i<=q;i++)if(s[i][0]<3)
	{
		int l=s[i][1],r=s[i][2],v=s[i][3];
		while(1)
		{
			set<pair<int,int> >::iterator it=s1[v].lower_bound(make_pair(l,0));
			if(it!=s1[v].begin())
			{
				it--;
				if((*it).second>=l)
				{
					pair<int,int> ri=*it;
					s1[v].erase(ri);s1[v].insert(make_pair(ri.first,l-1));
					si[i].push_back((sth){l,min(ri.second,r),v,-1});
					if(ri.second>r)s1[v].insert(make_pair(r+1,ri.second));
					continue;
				}
				it++;
			}
			if(it==s1[v].end())break;
			pair<int,int> ri=*it;
			if(ri.first>r)break;
			si[i].push_back((sth){ri.first,min(ri.second,r),v,-1});
			s1[v].erase(ri);
			if(ri.second>r)s1[v].insert(make_pair(r+1,ri.second));
		}
		if(s[i][0]==1)s1[v].insert(make_pair(l,r)),si[i].push_back((sth){l,r,v,1});
	}
}
struct BIT{
	ul tr[N];
	void add(int x,ul v){for(int i=x;i<=3e5;i+=i&-i)tr[i]+=v;}
	ul query(int x){ul as=0;for(int i=x;i;i-=i&-i)as+=tr[i];return as;}
}tx,t1;
void solve(int l,int r,vector<int> ri)
{
	if(l>r||ri.empty())return;
	int mid=(l+r)>>1;
	vector<int> s1,s2;
	for(int i=0;i<ri.size();i++)
	{
		int id=ri[i];
		if(s[id][0]<3)
		{
			if(s[id][3]<=mid)
			for(int j=0;j<si[id].size();j++)
			{
				int lb=si[id][j].l,rb=si[id][j].r,vi=si[id][j].d;
				tx.add(lb,vi);tx.add(rb,-vi);
				t1.add(lb,vi*(1-lb));t1.add(rb,vi*rb);
			}
			if(s[id][3]<mid)s1.push_back(id);
			if(s[id][3]>mid)s2.push_back(id);
		}
		else
		{
			int lb=s[id][1],rb=s[id][2];
			ul ci=rb*tx.query(rb)+t1.query(rb)-(lb-1)*tx.query(lb-1)-t1.query(lb-1);
			if(ci==1ll*(mid-l+1)*(rb-lb+1))
			mn[id]=mid,s2.push_back(id);
			else s1.push_back(id);
		}
	}
	for(int i=0;i<ri.size();i++)
	{
		int id=ri[i];
		if(s[id][0]<3&&s[id][3]<=mid)
		for(int j=0;j<si[id].size();j++)
		{
			int lb=si[id][j].l,rb=si[id][j].r,vi=si[id][j].d;
			tx.add(lb,-vi);tx.add(rb,vi);
			t1.add(lb,vi*(lb-1));t1.add(rb,-vi*rb);
		}
	}
	solve(l,mid-1,s1);
	solve(mid+1,r,s2);
}
int ct,ch[M][2],lz[M],ci[M],sz[M],is[M];
ul fi[M];
void doit(int x,int s){if(s)lz[x]=s,ci[x]=s==1?sz[x]:0;}
void modify(int x,int l,int r,int l1,int r1,int s)
{
	if(l1>r||r1<l)return;
	if(l1<=l&&r1>=r){doit(x,s);return;}
	if(!ch[x][0])ch[x][0]=++ct,sz[ct]=(sz[x]+1)/2;
	if(!ch[x][1])ch[x][1]=++ct,sz[ct]=sz[x]/2;
	doit(ch[x][0],lz[x]);doit(ch[x][1],lz[x]);lz[x]=0;
	int mid=(l+r)>>1;
	modify(ch[x][0],l,mid,l1,r1,s);
	modify(ch[x][1],mid+1,r,l1,r1,s);
	ci[x]=ci[ch[x][0]]+ci[ch[x][1]];
}
struct node{int l,r;ul su,lz;vector<int> li;}e[N*4];
void build(int x,int l,int r)
{
	e[x].l=l;e[x].r=r;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(x<<1,l,mid);build(x<<1|1,mid+1,r);
}
void pushup(int x){e[x].su=e[x<<1].su+e[x<<1|1].su;}
void doit1(int x,ul v){e[x].lz+=v;e[x].su+=v*(e[x].r-e[x].l+1);}
void doit2(int x,int y,ul v)
{
	if(!v||ci[y]==0)return;
	if(ci[y]==sz[y]){doit1(x,v);return;}
	fi[y]+=v;e[x].su+=ci[y]*v;
	if(fi[y]&&!is[y])is[y]=1,e[x].li.push_back(y);
}
void pushdown(int x)
{
	if(e[x].lz)doit1(x<<1,e[x].lz),doit1(x<<1|1,e[x].lz),e[x].lz=0;
	for(int i=0;i<e[x].li.size();i++)
	{
		int u=e[x].li[i];
		doit2(x<<1,ch[u][0],fi[u]);
		doit2(x<<1|1,ch[u][1],fi[u]);
		fi[u]=is[u]=0;
	}
	e[x].li.clear();
}
void modify1(int x,int u,int l,int r,ul v)
{
	if(l>e[x].r||r<e[x].l)return;
	if(l<=e[x].l&&r>=e[x].r){doit2(x,u,v);return;}
	pushdown(x);
	modify1(x<<1,ch[u][0],l,r,v);modify1(x<<1|1,ch[u][1],l,r,v);
	pushup(x);
}
ul query(int x,int l,int r)
{
	if(l>e[x].r||r<e[x].l)return 0;
	if(l<=e[x].l&&r>=e[x].r)return e[x].su;
	pushdown(x);
	return query(x<<1,l,r)+query(x<<1|1,l,r);
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&s[i][0],&s[i][1],&s[i][2]);
		if(s[i][0]<3)scanf("%d",&s[i][3]);
		if(s[i][0]==3)scanf("%llu",&vi[i]);
	}
	init_s1();
	vector<int> tp;
	for(int i=1;i<=q;i++)if(s[i][0]<4)tp.push_back(i);
	solve(1,150000,tp);
	for(int i=1;i<=150001;i++)lz[i]=1,sz[i]=n,ci[i]=n;
	ct=150001;
	build(1,1,n);
	for(int i=1;i<=q;i++)
	if(s[i][0]<=2)
	{
		query(1,s[i][1],s[i][2]);
		modify(s[i][3],1,n,s[i][1],s[i][2],s[i][0]==2?1:-1);
	}
	else if(s[i][0]==3)
	{
		query(1,s[i][1],s[i][2]);
		modify(mn[i]+1,1,n,s[i][1],s[i][2],0);
		modify1(1,mn[i]+1,s[i][1],s[i][2],vi[i]);
	}
	else printf("%llu\n",query(1,s[i][1],s[i][2]));
}
D11 T1 #10 Tree
Problem

交互。

你需要猜一棵 nn 个点的有根树,保证 11 为根。

你可以向交互库询问一个子集 TT,返回值为如下结果:

SuS_uuu 的子树中点构成的集合,记 d(i,j)d(i,j) 为树上距离(经过的边数)。

R(T)=iTSiR(T)=\cup_{i\in T}S_i,则返回值为 i,jR(T),i<jd(i,j)\sum_{i,j\in R(T),i<j}d(i,j)

你需要在 limlim 次询问内求出树的结构。

n1000,lim=3×104n\leq 1000,lim=3\times 10^4

5s,1024MB5s,1024MB

Sol

这里问题的经典处理方式:每次删叶子/每次加叶子。

首先考虑询问单点,由于父亲的子树严格包含儿子的子树,因此如果 ffuu 的父亲,询问 T={f}T=\{f\} 的结果一定严格大于 T={u}T=\{u\} 的结果。因此考虑询问一次所有单点然后按照结果排序,这样可以得到一个序列,满足每个点的父亲在序列中在它之前出现。那么如果能从前往后考虑,每次对于每个点在前面找到它的父亲,即可得到答案。此时的操作相当于加叶子。

考虑如何加一个叶子 uu。考虑询问集合 AA\neq\emptyset,再询问 A{u}A\cup \{u\},此时如果某个 AA 中节点的子树包含了 uu,则两次询问结果一定相同。否则,可以发现第二次询问结果一定更大(因为加入了点)。因此通过这次询问,可以判断 uu 是否在 AA 中某个节点的子树内。

此时有两种做法:

  1. 考虑边分治,每次选择一条边,询问划分出的两个部分中靠下的一部分。如果 uu 在这部分的子树内,则 uu 的父亲就在这部分内,否则 uu 的父亲在另外一部分内。可以发现虚拟三度化不影响询问过程。询问次数 O(nlogn)O(n\log n)
  2. 当前顺序为一个合法的遍历顺序,考虑询问已经求出部分的一个后缀,如果 uu 在某个后缀的子树内,则 uu 的父亲一定在这段后缀内,否则 uu 的父亲不在这段后缀内。对序列二分即可。询问次数 O(nlogn)O(n\log n)

两种做法的本质都在于,如果将树划分为两部分,使得第二部分中的点的儿子仍然在第二部分内,则如果 uu 在第二部分的某个点子树内,uu 父亲也一定在这一部分内,反之 uu 的父亲不能在这一部分内。因此可以进行二分。

询问次数 O(nlogn)O(n\log n)(两种做法常数不同(2nlog32n,2nlog2n2n\log_{\frac 32}n,2n\log_2n),但都能通过,实际上第二种做法只需要 1830018300 次询问,第一种做法在现有数据下只需要 2100021000 次),复杂度 O(n2logn)O(n^2\log n) 或者 O(n2)O(n^2)

Code
#include"tree.h"
#include<algorithm>
using namespace std;
#define N 1059
int n,rs[N],id[N];
bool cmp(int a,int b){return rs[a]>rs[b];}
vector<int> as;
vector<int> solve(int _n)
{
	n=_n;as.resize(n-1);
	for(int i=1;i<=n;i++)id[i]=i,rs[i]=query({i});
	sort(id+1,id+n+1,cmp);
	for(int i=2;i<=n;i++)
	{
		int lb=1,rb=i-1,si;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1,vi=0;
			vector<int> tp;
			for(int j=mid;j<i;j++)tp.push_back(id[j]);
			vi=query(tp);
			tp.push_back(id[i]);
			if(query(tp)==vi)si=mid,lb=mid+1;
			else rb=mid-1;
		}
		as[id[i]-2]=id[si];
	}
	return as;
}
D11 T2 #24 挑战分解质因数
Problem

给出 n,ϕ(n)n,\phi(n),请对 nn 分解质因数。

n21500n\leq 2^{1500},输入输出均使用二进制

1s,256MB1s,256MB

partial: n=pqn=pq | nn 无平方因子 | n2300n\leq 2^{300}

Sol

考虑最简单的情况:n=pq,pqn=pq,p\neq qp,qp,q 都是奇质数。

ϕ(n)\phi(n) 最直接的作用是,i s.t. (i,n)=1,iϕ(n)1(modn)\forall i\ s. t.\ (i,n)=1,i^{\phi(n)}\equiv 1(\bmod n)

考虑随机一个 [1,n1][1,n-1] 间的数 xx,如果 (x,n)1(x,n)\neq 1,则这直接完成了分解。否则,xϕ(n)1(modn)x^{\phi(n)}\equiv 1(\bmod n)

s=ϕ(n)s=\phi(n),则当前对于一个因子 pp,有 ϕ(p)s,xs1(modp)\phi(p)|s,x^s\equiv 1(\bmod p)。考虑 Miller-Rabin 中的二次探测,每次将 ss 除以 22。因为 ϕ(p)\phi(p) 是偶数,一定除到某一次时有 ϕ(p)2s\phi(p)|2s 但不整除 ss。此时考虑 xs(modp)x^s(\bmod p),由于原根的存在,如果 xxg2ig^{2i},则 xs1(modp)x^s\equiv 1(\bmod p),否则 xs1(modp)x^s\equiv -1(\bmod p)。可以发现这两种情况出现的概率均为 50%50\%

因此在不断除以 22 的过程中,存在一个固定但未知的步骤,到达这一步时 xs1x^s-1 有一半的概率是 pp 的倍数。

但可以发现,对于 qq 也存在这样一个步骤。如果这两步重合,则到达这一步时,有一半的概率 xs1x^s-1p,qp,q 中一个的倍数,但不是另外一个的倍数。此时取 gcd(xs1,n)\gcd(x^s-1,n) 就有一半的概率完成分解。而如果两个步骤不同,则在更先出现的一步考虑,成功分解概率也超过一半。

可以发现在这一步之前一定有 xs1(modp)x^s\equiv 1(\bmod p),因此可以得到如下做法:不断将 ss 除以 22,直到 xs1(modp)x^s\not\equiv 1(\bmod p),尝试 gcd(xs1,n)\gcd(x^s-1,n) 进行分解。这样在 n=pqn=pq 时成功率大于等于 12\frac 12

实现时可以使用二次探测的写法,先将 ss 尽可能的除 22,算出此时的 xs(modp)x^s(\bmod p),接下来每乘一个 22 相当于平方一次,这样只需要算一次快速幂。单次尝试需要 O(logn)O(\log n) 次乘法与取模,以及一次 gcd\gcd

然后考虑 nn 等于若干质数相乘的情况,同样找到第一个分解的步骤,此时有一些质数可能分出来,但只要不全部分出来就能将 nn 分成两部分,因此使用之前的做法,仍然有 12\geq \frac 12 的概率成功将 nn 分成两部分。考虑对两部分递归做,可以发现分出一部分后,剩余的 nn' 仍然满足 is1(modn)i^s\equiv 1(\bmod n'),因此接下来继续使用之前的 s=ϕ(n)s=\phi(n) 做即可。

最后考虑一般的情况,但可以发现输入p^2该做法就卡死了

可以发现这一做法能够很好地分解不同的质因子,但对于 pqp^q 的情况,可以发现只有选择 pp 的倍数才有可能能分解,这是难以接受的。

因此考虑特殊处理 pqp^q,此时有几种做法:

  1. 考虑使用 ϕ(n)\phi(n),注意到 pq1ϕ(pq)p^{q-1}|\phi(p^q),考虑计算 ngcd(n,ϕ(p))\frac n{\gcd(n,\phi(p))},由上一步可以发现这个结果不存在平方因子,且显然包含了 nn 的最大质因子。因此考虑对这个结果分解质因数,然后去除分出的质因数,将 ϕ\phi 除以这些质因数贡献的部分得到新的 ϕ\phi,重复该过程直到分解完。
  2. 考虑枚举 qq,可能的范围很小(尤其是暴力去除小的因子后),然后每个 qq 大力二分即可。

考虑第一种做法,每一次分解需要 O(lognlogϵ)O(\log n*\log \epsilon) 次乘法和取模,O(logϵ)O(\log \epsilon)gcd\gcd,因为成功率为常数,可以认为第二个 log\logloglogn\log \log n

logn=l\log n=l,质因数有 O(llogl)O(\frac l{\log l}) 个,因此需要 O(l2)O(l^2) 次乘法和取模,O(l)O(l)gcd\gcd

乘法直接实现复杂度为 O(l2ω2)O(\frac{l^2}{\omega^2})gcd\gcd 可以使用如下方式避免大量取模操作:

如果 a,ba,b 都是偶数,同时除以 22,最后将答案乘 22

否则,如果 a,ba,b 中有一个是偶数,则可以将其除以 22 而不影响答案。

否则,考虑辗转相减,让大的减去小的,这样减之后可以再除以 22,因此只会进行 O(l)O(l) 步。

这样只需要减法,除以 22,不需要取模,复杂度 O(l2ω)O(\frac{l^2}{\omega})。(尽管如此,仍然不能每乘一次就 gcd\gcd 一次)

最后考虑取模,如果直接使用竖式除法的方式,复杂度为 O(l2ω)O(\frac{l^2}{\omega}),这样复杂度即为 O(l4ω)O(\frac{l^4}{\omega}),不能通过。

考虑加速除法。例如,考虑大力试除的方式:

将除数 bb 表示为 a2k(a[0,2641])a*2^k(a\in [0,2^{64}-1])aa 尽量大,然后对 aa 上取整。

竖式除法时,当前一步为试除某一位上的 b2k(b2128)b*2^{k'}(b\leq 2^{128}),那么考虑直接用 ba\lfloor\frac ba\rfloor 得到若干位的结果。注意到如果 aa 被取整了,则 a[263,2641]a\in[2^{63},2^{64}-1],此时 aa 最多比真实值多了 11,这样试除的差不超过 O(1)O(1) 且这个值很小,微调即可。

复杂度 O(l2ω2)O(\frac{l^2}{\omega^2})

这样复杂度变为 O(l4ω2)O(\frac{l^4}{\omega^2}),虽然这样除很慢但是也能过。

更优秀的方式是使用 Barrett reduction:

考虑计算 amodna\mod n,其中 a<n2a<n^2。显然算出 an\lfloor\frac an\rfloor 就可以完成取模。

直观的想法:计算 m=2knm=\lfloor\frac{2^k}{n}\rfloor,然后 an\lfloor\frac an\rfloor 可以由 am2k\lfloor\frac{am}{2^k}\rfloor 逼近。

考虑取 k=log2ak=\log_2 a,则由于 mm 的误差不超过 11,右侧最后结果误差也不超过 11,因此最多少减了一个 nn

这样就只需要使用一次除法预处理,之后的 abmodna*b\mod n 可以用乘法解决。这样常数显著更小。

这样大概可以快一倍(对比上述大力试除)。

卡常小技巧:分解时试 1010 次就能通过。

另一个卡常小技巧:先大力筛掉很小的因子(例如小于 2182^{18})。

该问题说明求 ϕ\phi 和分解等难。

Code
#include<cstdio>
#include<vector>
#include<random>
#include<algorithm>
using namespace std;
struct integer{
	typedef long long s64;
	typedef unsigned long long u64;
	typedef unsigned __int128 u128;
	vector<u64> v;
	integer(){}
	integer(u64 x){if(x)v={x};}
	explicit operator bool()const{return !v.empty();}
	explicit operator u64()const{return v.empty()?0:v[0];}
	bool operator ==(const integer &a)const{return v==a.v;}
	bool operator !=(const integer &a)const{return v!=a.v;}
	bool operator <(const integer &a)const
	{
		s64 l1=v.size(),l2=a.v.size();
		if(l1!=l2)return l1<l2;
		while(l1)
		{
			l1--;
			if(v[l1]!=a.v[l1])return v[l1]<a.v[l1];
		}
		return 0;
	}
	bool operator >(const integer &a)const{return a<(*this);}
	bool operator <=(const integer &a)const{return !(a<*this);}
	bool operator >=(const integer &a)const{return !(*this<a);}
	integer &operator <<=(u64 d)
	{
		u64 ci=d>>6,ri=d&63;
		for(u64 i=0;i<ci+1;i++)v.push_back(0);
		for(s64 i=v.size()-1;i>=0;i--)
		{
			u64 as=0;
			if(i>=ci)as|=v[i-ci]<<ri;
			if(i>=ci+1&&ri)as|=v[i-ci-1]>>(64-ri);
			v[i]=as;
		}
		while(v.size()&&v.back()==0)v.pop_back();
		return *this;
	}
	integer &operator >>=(u64 d)
	{
		u64 ci=d>>6,ri=d&63;
		(*this)<<=64-ri;
		for(u64 i=0;i+ci+1<v.size();i++)v[i]=v[i+ci+1];
		for(u64 i=0;i<=ci&&v.size();i++)v.pop_back();
		while(v.size()&&v.back()==0)v.pop_back();
		return *this;
	}
	integer operator <<(u64 d)const{return integer(*this)<<=d;}
	integer operator >>(u64 d)const{return integer(*this)>>=d;}
	integer &operator +=(const integer &a)
	{
		u64 s1=v.size(),s2=a.v.size(),vi=0;
		if(s1<s2)v.resize(s2);
		for(u64 i=0;i<s1||i<s2||vi;i++)
		{
			u128 rs=vi;
			if(i<s1)rs+=v[i];if(i<s2)rs+=a.v[i];
			if(i>=s1&&i>=s2)v.push_back(0);
			vi=(rs>>32)>>32;
			v[i]=rs;
		}
		return *this;
	}
	integer &operator -=(const integer &a)
	{
		u64 s1=v.size(),s2=a.v.size(),vi=0;
		for(u64 i=0;i<s1||i<s2||vi;i++)
		{
			u128 rs=vi;if(vi==-1)rs=-1;
			if(i<s1)rs+=v[i];if(i<s2)rs-=a.v[i];
			if(i>=s1&&i>=s2)v.push_back(0);
			vi=(rs>>32)>>32;
			v[i]=rs;
		}
		while(v.size()&&v.back()==0)v.pop_back();
		return *this;
	}
	integer &operator ++(int){return (*this)+=integer(1);}
	integer &operator --(int){return (*this)-=integer(1);}
	integer operator +(const integer &a){return integer(*this)+=a;}
	integer operator -(const integer &a){return integer(*this)-=a;}
	integer &operator *=(const integer &a)
	{
		vector<u128> tp;
		vector<u64> as;
		u64 s1=v.size(),s2=a.v.size();
		tp.resize(s1+s2+2);as.resize(s1+s2+1);
		for(u64 i=0;i<s1;i++)for(u64 j=0;j<s2;j++)
		{
			u128 si=(u128)v[i]*a.v[j];
			tp[i+j+1]+=(si>>32)>>32;
			tp[i+j]+=(u64)si;
		}
		for(u64 i=0;i+1<tp.size();i++)
		{
			tp[i+1]+=(tp[i]>>32)>>32;
			as[i]=tp[i];
		}
		v=as;
		while(v.size()&&v.back()==0)v.pop_back();
		return *this;
	}
	integer operator *(const integer &a){return integer(*this)*=a;}
	void rem(const integer &a,bool fi)
	{
		u64 vi=a.v.back(),le=(a.v.size()-1)<<6;
		if(le>=64)
		{
			u64 li=__builtin_clzll(vi);
			le-=li;vi=(vi<<li)|(li?a.v[a.v.size()-2]>>(64-li):0);
		}
		if(vi==-1)vi>>=1,le++;
		if(le)vi++;
		integer as;
		while((*this)>=a)
		{
			u128 v1=v.back();u64 l1=(v.size()-1)<<6;
			if(l1)l1-=64,v1=(v1<<32)<<32|v[v.size()-2];
			if(l1<le)v1>>=(le-l1),l1=le;
			u128 ri=v1/vi;
			integer rs,rt;
			u64 t1=(ri>>32)>>32,t2=ri;
			rs.v.push_back(t2);if(t1)rs.v.push_back(t1);
			rt=rs*a;
			rt<<=l1-le;rs<<=l1-le;
			as+=rs;(*this)-=rt;
			if((*this)>=a)*this-=a,as++;
		}
		if(fi)(*this).v=as.v;
	}
	integer &operator /=(const integer &a){rem(a,1);return *this;}
	integer &operator %=(const integer &a){rem(a,0);return *this;}
	integer operator /(const integer &a){return integer(*this)/=a;}
	integer operator %(const integer &a){return integer(*this)%=a;}
	void output()const
	{
		if(v.empty()){printf("0\n");return;}
		bool fg=0;
		for(s64 i=v.size()-1;i>=0;i--)
		{
			u64 si=v[i];
			for(int j=63;j>=0;j--)
			{
				bool tp=(si>>j)&1;
				fg|=tp;
				if(fg)printf("%d",tp);
			}
		}
		printf("\n");
	}
	bool odd()const{return !v.empty()&&(v[0]&1);}
};
integer init(char s[])
{
	integer as;
	for(int i=1;s[i];i++)as=as*2+(s[i]-'0');
	return as;
}
mt19937 rnd(3);
integer n,p;
char s[1593];
vector<integer> as,tp;
integer bi,br;
int bl;
void init(integer p)
{
	if(p==br)return;
	br=p;
	bl=(p.v.size()<<6)-__builtin_clzll(p.v.back());
	bl=bl*2;
	bi=((integer)1<<bl)/p;
}
integer reduce(integer n)
{
	n-=((n*bi)>>bl)*br;
	while(n>=br)n-=br;
	return n;
}
integer pw(integer a,integer b,integer p)
{
	init(p);
	integer as=1;
	while(b)
	{
		if(b.odd())as=reduce(as*a);
		a=reduce(a*a);
		b>>=1;
	}
	return as;
}
integer gcd(integer a,integer b)
{
	if(a<b)swap(a,b);
	if(!b)return a;
	if(!a.odd()&&!b.odd())return gcd(a>>1,b>>1)<<1;
	while(!a.odd())a>>=1;
	while(!b.odd())b>>=1;
	if(a<b)swap(a,b);
	return gcd(a-b,b);
}
void doit(integer q)
{
	if(n%q>0)return;
	as.push_back(q);p/=q-1;n/=q;
	while(n%q==0)as.push_back(q),p/=q,n/=q;
}
void solve(integer n)
{
	int ci=0;
	init(n);
	while(ci<10)
	{
		integer si=rnd()+59,gi=gcd(n,si);
		si%=n;
		if(gi==n)continue;
		if(gi>1){solve(gi);solve(n/gi);return;}
		integer ri=p;
		while(!ri.odd())ri>>=1;
		integer f=pw(si,ri,n);
		while(1)
		{
			integer nt=reduce(f*f);
			if(nt==1)
			{
				gi=gcd(f-1,n);
				if(gi>1&&gi<n){solve(gi);solve(n/gi);return;}
				break;
			}
			f=nt;
		}
		ci++;
	}
	tp.push_back(n);
}
int main()
{
	scanf("%s",s+1);n=init(s);
	scanf("%s",s+1);p=init(s);
	while(n>1)
	{
		integer si=n/gcd(n,p);
		tp.clear();solve(si);
		for(int i=0;i<tp.size();i++)doit(tp[i]);
	}
	sort(as.begin(),as.end());
	printf("%d\n",as.size());
	for(int i=0;i<as.size();i++)as[i].output();
}
D11 T3 #30 匹配计数
Problem

给定 nn 个点,点有颜色 cc。你需要在点之间连若干条边,满足以下限制:

  1. 任意边两端点颜色相同。
  2. 一个点最多与一条边相连,无自环。
  3. 称两条边 (a,b),(c,d)(a<c,b<d)(a,b),(c,d)(a<c,b<d) 相交当且仅当 a<b<c<da<b<c<d 或者 b<a<d<cb<a<d<c,则有偶数对边相交(这里的对为无序对)。

求方案数,模 998244353998244353

TT 组数据。

T5,n2000T\leq 5,n\leq 2000

1s,512MB1s,512MB

partial: ci=1c_i=1 | ci10c_i\leq 10

Sol

根据单位根反演[2k]=12(1+(1)k)[2|k]=\frac 12(1+(-1)^k),即求出不考虑偶数限制时的总方案数,求出此时所有方案 1-1 的相交次数次方,求和除以二即为答案。

总方案数是简单的:每种颜色情况独立,设 fif_i 表示 ii 个点连边的方案数,枚举第一个点是否连边可以发现 fi=fi1+(i1)fi2f_i=f_{i-1}+(i-1)f_{i-2},所有颜色的情况相乘即可。

现在考虑求和 1-1 的相交次数次方。首先考虑最简单的 ci=1c_i=1。考虑枚举有多少个点有连边,变为要求每个点必须连边的问题。此时打表观察可以发现如下结论:

2k2k 个点进行连边,满足前两个条件且每个点都连出边,所有方案的 1-1 的相交次数次方总和为 11

证明:考虑第一个点连向了哪里,设其连向了 xx,则可以发现与边 (1,x)(1,x) 相交次数的奇偶性一定正好为 xx 的奇偶性(考虑左侧点数的奇偶性,同时所有点都可以连边)。从而 11 连向 xx 的情况的答案等于删去这两个点的答案乘上 (1)x(-1)^x。可以发现任意删点都得到 n2n-2 的情况,系数和为 i=22n(1)i=1\sum_{i=2}^{2n}(-1)^i=1,因此每一步都给答案乘 11,归纳可得答案为 11

这说明一种颜色间的权值只和选择点的数量有关,和具体连边情况无关。再考虑两种颜色间边的交点情况。可以类似地发现结论:

如果两种颜色 a,ba,b 分别选定了哪些点会连边,则两种颜色的边的相交次数奇偶性与连边情况无关,其固定为 i<j,ci=a,cj=b1\sum_{i<j,c_i=a,c_j=b}1 的奇偶性。

证明:考虑计算一条颜色 bb 的边 (x,y)(x,y) 与颜色 aa 相交的次数的奇偶性。记 rir_i 表示跨过点 ii 的颜色 aa 边数,则可以发现相交次数和 rx+ryr_x+r_y 的奇偶性相同。(唯一不等于 rx+ryr_x+r_y 的情况为同时跨过 x,yx,y,但此时奇偶性正确)。那么总的奇偶性即为 i<j,ci=a,cj=b1\sum_{i<j,c_i=a,c_j=b}1

现在考虑,在选择了要连边的点后,不同颜色间的相交次数奇偶性确定,对于任意一种颜色,其所有连边方式内部权值和为 11。那么对每种颜色的边分别求和,即可得到此时所有这些点间连边的方案权值总和为 1-1 的不同颜色间相交次数次方。如果在考虑颜色时全部取 a<ba<b 考虑,则该值等于 (1)i<j,ci<cj1(-1)^{\sum_{i<j,c_i<c_j}1}

此时这部分问题相当于,选出若干点,要求每种颜色的点选择偶数个,权值为选出点的 (1)i<j,ci<cj1(-1)^{\sum_{i<j,c_i<c_j}1},求和权值。

那么可以得到一个暴力 dp,设 dpi,Sdp_{i,S} 表示考虑了前 ii 个点,当前每种颜色选了奇数个还是偶数个。复杂度 O(n2c)O(n2^c)。但它与正解关系不大。

考虑上述问题。如果没有奇偶性的限制,考虑转化为一个图论问题:如果 i<j,ci<cji<j,c_i<c_j 则连边 (i,j)(i,j),问题变为每种选择点集的方式求和 1-1 的导出子图边数次方。

此时还有偶数个的限制,考虑如何处理,可以发现如下构造:

对于每种颜色,考虑新建一个点,该点向所有这种颜色的边连边。

考虑其它点选择的一种方案,如果这种颜色的点选择了偶数,则新加入的点是否选择不影响权值,即将该方案权值 2*2。而如果这种颜色的点选择了奇数个,则选择这个点会将权值乘 1-1,从而该方案权值为 00。因此加入该点后的答案为所有选择了偶数个这种颜色的点的方案权值总和 2*2 的结果。

因此考虑对每种颜色都这样构造,此时得到一个 2n2n 个点的图,同时消除了颜色的限制,最后将答案除以 2n2^n 即可。

那么只剩下这样一个问题:

给定 nn 个点的图,求所有选择点集的方式的 1-1 的导出子图边数次方之和。

可以发现,之前的构造实际上提供了一种减少点数的思路:考虑一个点 uu,如果删去 uu,则需要计算的方案为所有满足选择了偶数个与 uu 相邻的点的方案权值总和。此时有另外一种构造方式:考虑给每个点一个权值 aia_i,代表选择了它会给这种方案的权值乘上 aia_i。那么考虑将所有与 uu 相邻的点的 aia_i 乘上 1-1 并删去 uu,此时得到的即为选择了偶数个与 uu 相邻的点的方案权值总和减去选择了奇数个的方案权值总和。而如果不改变 aia_i,则得到偶数加奇数的方案,两者求和即可满足要求。

但这样有很大的问题:每一步会拆分出两种情况,因此情况数为 O(2n)O(2^n)

可以发现问题在于,如果选择一个点删去,剩余情况的权值为 2/02/0 或者 2/0-2/0,但删点后一种情况只能表示 1/11/-1 权值,不能直接由 1/11/-1 得到 2/02/0,必须两种情况相加,这就导致必须分裂出两种情况。因此选一个点一定不行。

那么考虑选择两个点 u,vu,v,假设它们不连边。此时考虑删去这两个点后的一种方案,其在加上这两个点后的系数由 u,vu,v 是否选择的四种情况得到。可以画出如下图:

v=0v=0 v=1v=1
u=0u=0 11 11
u=1u=1 11 11

权值为四个值之和。但此时还需要考虑与 u,vu,v 相邻的点。这些点有三种类型:与 uu 相连,与 vv 相连,与两者同时相连。

可以发现如果选择了奇数个与 uu 相连的点,则上述系数矩阵中最后一行需要乘 1-1。如果选择了奇数个与 vv 相邻的点则需要将最后一列乘 1-1,最后一种情况则需要将右上到左下的对角线乘 1-1

那么可以发现三条线同时乘 1-1 又还原回了原状态,因此得到四种可能的矩阵:

11 11 11 11 11 1-1 11 1-1
11 11 1-1 1-1 11 1-1 1-1 11

但这样权值和为 4,0,0,04,0,0,0,并没有解决之前的问题。

但可以发现还有 u,vu,v 有边的情况,此时初始矩阵变为:

v=0v=0 v=1v=1
u=0u=0 11 11
u=1u=1 11 1-1

四种情况分别为:

11 11 11 11 11 1-1 11 1-1
11 1-1 1-1 11 11 11 1-1 1-1

权值和为 2,2,2,22,2,2,-2,这是一个可行的思路。

考虑用三个 0,10,1 依次表示只与 uu 相邻(这里表示不与 vv 相邻),只与 vv 相邻和同时与 u,vu,v 相邻的点选择数量的奇偶性,则需要将 001,110001,110 的系数变为 2-2,剩余六种情况的系数变为 22。但这个形式仍然不好,考虑进行一些转化。

考虑将所有同时与 u,vu,v 相邻的点权值乘 1-1,此时所有第三位为 11 的状态系数乘了 1-1,另外一半状态系数不变,此时需要 110,101,011,111110,101,011,111 系数为 2-2,另外四种为 22。这个形式非常好看:如果出现了大于等于两个就是 1-1,否则是 11

此时可以发现合法的构造:考虑一个三元环,则环上选择了 0,10,1 个点时系数均为 11,如果选择了 2,32,3 个点,导出子图边数分别为 1,31,3,系数均为 1-1。这对应了上面的系数。上面过程中每个部分是一个集合,此时可以将两个集合连边看出一个集合的每个点向另外一个集合的每个点连边。那么得到了如下构造:

选择两个有边,且 ai=1a_i=1 的点。删去这两个点,记只与 uu 相邻的,只与 vv 相邻的和与两者都相邻的点分别构成集合 S1,S2,S3S_1,S_2,S_3。进行以下两步:

  1. S3S_3 中点的 aia_i 全部乘上 1-1
  2. 对于 i<ji<j,从 SiS_i 中的每个点向 SjS_j 中的每个点连一条边(显然两条重边可以消除,因此图可以还是简单图)

则原图答案为该图答案乘 22

但这样只解决了 a=1a=1 的情况,aa 还可能是 1-1

首先考虑 au=1,av=1a_u=1,a_v=-1 的情况,此时初始矩阵变为:

v=0v=0 v=1v=1
u=0u=0 11 1-1
u=1u=1 11 11

但可以发现,之前的三条线(下,右,对角)构成了右下三个元素的三角形,此时的矩阵与之前唯一的区别在于三角形中唯一一个 1-1 所在的位置不同,其它都是相同的。因此此时可以沿用之前的做法,只需要将 S3S_3 改为只与 uu 相邻的点即可。

那么 au=1,av=1a_u=-1,a_v=1 的情况类似,只需要交换 SS 的顺序。但 au=av=1a_u=a_v=-1 的情况有所不同:

v=0v=0 v=1v=1
u=0u=0 11 1-1
u=1u=1 1-1 1-1

此时可能的矩阵为:

11 1-1 11 1-1 11 11 11 11
1-1 1-1 11 11 1-1 11 11 1-1

即系数为 2,2,2,2-2,2,2,2。需要将 000,111000,111 两种状态的系数变为 2-2

此时三个集合在系数上是对称的,考虑一点对称的构造。

考虑将 S1,S2,S3S_1,S_2,S_3 中的点权值全部乘 1-1,则有 1,31,311 的状态会翻转,此时 000,100,010,001000,100,010,001 四种状态的系数为 2-2,另外四种系数为 22

因此可以发现,提取一个 2-2,使用三元环构造即可。此时步骤变为:

  1. S1,S2,S3S_1,S_2,S_3 中点的 aia_i 全部乘上 1-1
  2. 对于 i<ji<j,从 SiS_i 中的每个点向 SjS_j 中的每个点连一条边(显然两条重边可以消除,因此图可以还是简单图)

最后答案乘 2-2

那么只需要能选出一条边,就可以删掉这两个点,变为 n2n-2 的情况。

而如果有一个点没有边相连,那么删掉这个点是容易的:最后乘 1+ai1+a_i 即可。

因此得到了整个过程:

选择一个点,此时:

  1. 如果该点没有出边,删掉这个点,答案乘 1+ai1+a_i
  2. 否则选择一个与该点相连的边,对这条边使用上述讨论。

讨论 nn 次即可得到答案。同时可以发现答案为 00±2k\pm 2^k

如果直接维护,复杂度为 O(n3)O(n^3),差一点能过去(常数非常小)

考虑对于每个点,使用 bitset 维护其连边情况,那么可以发现三个集合的连边操作只需要准备三种 bitset,然后做 xor 即可。这样整个过程都可以使用 bitset 优化。

复杂度 O(Tn332)O(T*\frac{n^3}{32})

Code
#include<cstdio>
#include<vector>
#include<bitset>
using namespace std;
#define N 4060
#define mod 998244353
int dp[N],T,n,v[N],ci[N],as,ri[N];
bitset<N> f[N],is;
int calc()
{
	int s=-1,t=-1;
	for(int i=1;i<=n*2;i++)if(is[i])s=i;
	if(s==-1)return 1;
	for(int i=1;i<=n*2;i++)if(f[i][s]&&is[i])t=i;
	if(t==-1){is[s]=0;return (ri[s]+1)*calc()%mod;}
	is[s]=is[t]=0;
	bitset<N> si[4];
	for(int p=0;p<2;p++)for(int q=0;q<2;q++)
	{
		bitset<N> v1=f[s],v2=f[t];
		if(p)v1=~v1;if(q)v2=~v2;
		si[p*2+q]=v1&v2&is;
	}
	for(int i=0;i<3;i++)
	for(int p=1;p<=n*2;p++)if(si[i][p])
	for(int j=0;j<3;j++)if(i!=j)
	f[p]^=si[j];
	int fg=(ri[s]==-1)*2+(ri[t]==-1);
	for(int i=0;i<3;i++)if(i==fg||fg==3)for(int p=1;p<=n*2;p++)if(si[i][p])ri[p]*=-1;
	return 2ll*(fg==3?mod-1:1)%mod*calc()%mod;
}
void solve()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)ci[i]=0;as=1;
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),ci[v[i]]++;
	for(int i=1;i<=n;i++)as=1ll*as*dp[ci[i]]%mod;
	for(int i=1;i<=n*2;i++)f[i].reset();
	for(int i=1;i<=n;i++)f[i][v[i]+n]=f[v[i]+n][i]=1,ri[i]=ri[i+n]=1,is[i]=is[i+n]=1;
	for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(v[i]>v[j])f[i][j]=f[j][i]=1;
	int re=calc();
	for(int i=1;i<=n;i++)re=1ll*re*(mod+1)/2%mod;
	as=1ll*(as+re)*(mod+1)/2%mod;
	printf("%d\n",as);
}
int main()
{
	dp[0]=dp[1]=1;for(int i=2;i<=2000;i++)dp[i]=(dp[i-1]+1ll*(i-1)*dp[i-2])%mod;
	scanf("%d",&T);
	while(T--)solve();
}
D12 T1 #22 生活在对角线下
Problem

给定 n,mn,m 以及一个二元多项式 F(x,y)F(x,y),保证 FFxx 的次数不超过 ppyy 的次数不超过 qq。求:

xyCx+yxC(nx)+(my)nxF(x,y)\sum_{x\geq y}C_{x+y}^xC_{(n-x)+(m-y)}^{n-x}F(x,y)

答案模 998244353998244353

TT 组询问,保证所有询问的 mn=cm-n=c 为定值,p,qp,q 相同。

T,n,m105,(p+1)(q+1)10T,n,m\leq 10^5,(p+1)(q+1)\leq 10

1.5s,512MB1.5s,512MB

Sol

大力多项式.jpg

可以发现问题相当于考虑所有从 (0,0)(0,0)(n,m)(n,m) 的路径,求出路径上所有在对角线下的点的 F(x,y)F(x,y) 总和的总和。

首先考虑 nmn\leq m 的情况,此时结束位置在对角线上方,因此对角线下的每一部分一定从对角线上开始,在对角线上结束。

考虑对这部分进行计数,设 fn,a,bf_{n,a,b} 表示从 (0,0)(0,0) 走到 (n,n)(n,n),全程在对角线 x=yx=y 及其下方,所有路径中路径上每个点的 xaybx^ay^b 之和。设 Fa,b(x)=ifi,a,bxiF_{a,b}(x)=\sum_i f_{i,a,b}x^i

考虑如何统计对角线下的路径。考虑从对角线开始,到第一次返回对角线的一段,枚举每一段计算贡献。

首先考虑 a=b=0a=b=0 的情况。设这一段从 (l,l)(l,l) 出发,第一次返回位置为 (r,r)(r,r),则中间情况可以看出从 (l+1,l)(l+1,l)(r,r1)(r,r-1),不越过 x=y+1x=y+1,则中间所有情况的点数和(不考虑两端)为 frl1,0,0f_{r-l-1,0,0},考虑右端点为 frl1,0,0+crl1f_{r-l-1,0,0}+c_{r-l-1},其中 cnc_n 表示卡特兰数。(为了避免相邻两段重复计数,这里不考虑左端点)。而之前路径方案数为 clc_l,之后路径方案数为 crc_r,因此设 R(x)=icixiR(x)=\sum_i c_ix^i,则有:

F0,0(x)=R(x)(F0,0(x)+R(x))R(x)+R(x)F0,0(x)=(R3(x)+R(x))/(1R2(x))F_{0,0}(x)=R(x)*(F_{0,0}(x)+R(x))*R(x)+R(x)\\ F_{0,0}(x)=(R^3(x)+R(x))/(1-R^2(x))

可以 O(nlogn)O(n\log n) 求出。(这里最后一项 R(x)R(x) 是因为之前每一段只统计了右端点,但这样 (0,0)(0,0) 没有统计)

然后考虑一般情况,此时枚举 l,rl,r 后,frl1f_{r-l-1} 统计的是 xayb\sum x^ay^b,但可以发现需要的是 (x+l+1)a(y+l)b\sum (x+l+1)^a(y+l)^b,因此考虑拆开,得到:

a1=0ab1=0bxa1yb1Caa1Cbb1(l+1)aa1lbb1\sum_{a_1=0}^a\sum_{b_1=0}^bx^{a_1}y^{b_1}*C_a^{a_1}*C_b^{b_1}*(l+1)^{a-a_1}*l^{b-b_1}

最后的系数只和 ll 有关,因此考虑 Ga,b(x)=ici(i+1)aibxiG_{a,b}(x)=\sum_i c_i(i+1)^ai^bx^i,这就表示为 Gaa1,bb1(x)G_{a-a_1,b-b_1}(x)Fa1,b1(x)F_{a_1,b_1}(x) 的乘积。同时每一段的右端点也需要乘上系数。由此可以得到:

Fa,b(x)=R(x)a1=0ab1=0bCaa1Cbb1Gaa1,bb1(x)(Fa1,b1(x)+iia1(i+1)b1cixi)+[a=b=0]R(x)F_{a,b}(x)=R(x)*\sum_{a_1=0}^a\sum_{b_1=0}^bC_a^{a_1}*C_b^{b_1}G_{a-a_1,b-b_1}(x)*(F_{a_1,b_1}(x)+\sum_i i^{a_1}(i+1)^{b_1}c_ix^i)+[a=b=0]R(x)

可以发现 G0,0(x)=R(x)G_{0,0}(x)=R(x),因此最后的分母还是 1R2(x)1-R^2(x),那么可以直接使用之前的求逆结果。

如果按照 a,ba,b 从小到大算,则其它 Fa1,b1F_{a_1,b_1} 都是已经求出的,因此可以推出 Fa,bF_{a,b}。但这里每一个 Fa,bF_{a,b} 需要 (p+1)(q+1)(p+1)(q+1) 次多项式乘法,复杂度难以接受。但乘的多项式只有 O((p+1)(q+1))O((p+1)(q+1)) 个,因此考虑对每个多项式都维护 DFT 后的点值,对应点值相乘再加起来即可。(使用点值计算时,需要注意不能乘到次数大于等于卷积长度,否则会溢出到前面)

因此可以在 O(((p+1)(q+1))2n+(p+1)(q+1)nlogn)O(((p+1)(q+1))^2n+(p+1)(q+1)n\log n) 的复杂度内求出所有 FF

然后考虑计算 nmn\leq m 情况的答案。由于 mn=cm-n=c 固定,设 sn,a,bs_{n,a,b} 表示从 (0,0)(0,0)(n,n+c)(n,n+c),所有路径上所有对角线下的点的 xaybx^ay^b 之和,对应生成函数为 Sa,b(x)S_{a,b}(x)。同样考虑枚举一段。此时枚举极长的一段在对角线及下方的段 [l,r][l,r],则左侧方案数为 C2l1l1C_{2l-1}^{l-1}l=0l=0 特判为 11),右侧为 C2(nr)+c1nrC_{2(n-r)+c-1}^{n-r}(同样特判 l=rl=r),那么类似的设 Ha,b(x)=[a+b==0]1+i1C2i1i1iaibxi,T(x)=1+i1C2i+c1iH_{a,b}(x)=[a+b==0]1+\sum_{i\geq 1}C_{2i-1}^{i-1}i^ai^bx^i,T(x)=1+\sum_{i\geq 1}C_{2i+c-1}^i,那么有:

Sa,b(x)=T(x)a1=0ab1=0bCaa1Cbb1Haa1,bb1(x)Fa1,b1(x)S_{a,b}(x)=T(x)*\sum_{a_1=0}^a\sum_{b_1=0}^bC_a^{a_1}*C_b^{b_1}H_{a-a_1,b-b_1}(x)*F_{a_1,b_1}(x)

不需要求逆,点值处理乘法求和的部分即可。复杂度 O(((p+1)(q+1))2n+(p+1)(q+1)nlogn)O(((p+1)(q+1))^2n+(p+1)(q+1)n\log n)

最后考虑 n>mn>m,此时考虑先求出总数,然后翻转得到翻转前在对角线上的,减去这部分即可得到答案。翻转后得到 n<mn<m,可以使用之前的答案。

然后考虑需要拿什么去减,首先需要 (0,0)(0,0)(n,m)(n,m) 的所有路径上所有点的权值和,但上面还会减去对角线上的点,因此需要将对角线上的点加回来。可以发现对于一个 nn 和一个 a,ba,b,结果为:

i=0nC2iiCn+m2iniiaib\sum_{i=0}^nC_{2i}^iC_{n+m-2i}^{n-i}i^ai^b

iC2iiia+bxi\sum_i C_{2i}^ii^{a+b}x^iiCn+m2inixi\sum_i C_{n+m-2i}^{n-i}x^i 乘起来即可。

最后只需要考虑所有点的权值和。考虑求所有 xaybx^ay^b 的和。

考虑转下降幂,变为如下组合意义:选择一个点,在其之前选择 a1a_1 条向右走的边,b1b_1 条向上走的边。也可以看成先选边,然后在最后一条选择的边之后选一个点。

但点边同时考虑较为困难,可以将最后的选择一个点改为在之后选择一条边或者不选,因此变为统计如下方案数:

在路径上选择 a1a_1 条向右走的边,b1b_1 条向上走的边,再在之后选择一条边或者不选择。

枚举最后一条边选择哪个方向,那么方案数由以下三部分乘积得到:

  1. 选择的这些边排列的顺序。
  2. 不选择的边排列的顺序。
  3. 两部分边合并的顺序。

加上转下降幂的部分,可以 O(((p+1)(q+1))2)O(((p+1)(q+1))^2) 求出。因此可以 O(((p+1)(q+1))2n)O(((p+1)(q+1))^2n) 求出该部分答案。最后将所有部分合起来即可。

这样得到了每个 nn 的情况 F=xaybF=x^ay^b 的答案,询问可以直接回答。

复杂度 O(((p+1)(q+1))2n+(p+1)(q+1)nlogn+(p+1)(q+1)T)O(((p+1)(q+1))^2n+(p+1)(q+1)n\log n+(p+1)(q+1)T)

注意常数问题,这里大概需要 13(p+1)(q+1)+c13(p+1)(q+1)+c 次 dft,但还是能跑过去。

Code
#include<cstdio>
using namespace std;
#define N 263001
#define M 11
#define mod 998244353
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int fr[N*2],ifr[N*2],gr[2][N*2],rev[N*2];
void init(int l=18)
{
	fr[0]=1;for(int i=1;i<=1<<l+1;i++)fr[i]=1ll*i*fr[i-1]%mod;
	ifr[1<<l+1]=pw(fr[1<<l+1],mod-2);for(int i=1<<l+1;i>=1;i--)ifr[i-1]=1ll*i*ifr[i]%mod;
	for(int s=2;s<=1<<l;s<<=1)for(int i=1;i<s;i++)rev[i+s]=(rev[(i>>1)+s]>>1)+((i&1)*(s>>1));
	for(int t=0;t<2;t++)
	for(int s=2;s<=1<<l;s<<=1)
	{
		int tp=pw(3,(mod-1)/s);
		if(!t)tp=pw(tp,mod-2);
		int vl=1;
		for(int i=0;i<s;i++)gr[t][s+i]=vl,vl=1ll*vl*tp%mod;
	}
}
int f[N],g[N],ntt[N];
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)ntt[rev[i+s]]=a[i];
	for(int l=2;l<=s;l<<=1)
	for(int i=0;i<s;i+=l)
	for(int j=0;j<l>>1;j++)
	{
		int v1=ntt[i+j],v2=1ll*ntt[i+j+(l>>1)]*gr[t][j+l]%mod;
		ntt[i+j]=(v1+v2)%mod;ntt[i+j+(l>>1)]=(v1+mod-v2)%mod;
	}
	int tp=t?1:pw(s,mod-2);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*tp%mod;
}
void polyinv(int n,int *s,int *t)
{
	if(n==1){t[0]=pw(s[0],mod-2);return;}
	polyinv((n+1)>>1,s,t);
	int l=1;while(l<=n*2+4)l<<=1;
	for(int i=0;i<l;i++)f[i]=g[i]=0;
	for(int i=0;i<n;i++)f[i]=s[i],g[i]=t[i];
	dft(l,f,1);dft(l,g,1);for(int i=0;i<l;i++)f[i]=1ll*g[i]*(2+mod-1ll*f[i]*g[i]%mod)%mod;
	dft(l,f,0);
	for(int i=0;i<n;i++)t[i]=f[i];
}

int cl[N],ls[M][N],vl[M][N],fi[M][N],tp[N],rs[N],iri[N],ri[N],c[M][M];
void solve_cl(int n,int p,int q)
{
	for(int i=0;i<=10;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=10;i++)for(int j=1;j<i;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	int l=1;while(l<=n*2+3)l<<=1;
	cl[0]=1;for(int i=1;i<=n;i++)cl[i]=1ll*cl[i-1]*(4*i-2)%mod*pw(i+1,mod-2)%mod;
	for(int i=0;i<=n;i++)rs[i]=cl[i];
	dft(l,rs,1);
	for(int a=0;a<=p;a++)for(int b=0;b<=q;b++)
	{
		//ls*rs
		for(int i=0;i<=n;i++)ls[a*(q+1)+b][i]=1ll*cl[i]*pw(i+1,a)%mod*pw(i,b)%mod;
		dft(l,ls[a*(q+1)+b],1);
		for(int i=0;i<l;i++)ls[a*(q+1)+b][i]=1ll*ls[a*(q+1)+b][i]*rs[i]%mod;
		dft(l,ls[a*(q+1)+b],0);
		for(int i=n+1;i<l;i++)ls[a*(q+1)+b][i]=0;
		dft(l,ls[a*(q+1)+b],1);
		//fi
		for(int i=0;i<=n;i++)fi[a*(q+1)+b][i]=1ll*cl[i]*pw(i,a)%mod*pw(i+1,b)%mod;
		dft(l,fi[a*(q+1)+b],1);
	}
	// / 1-ls_0*x
	dft(l,ls[0],0);
	ri[0]=1;for(int i=1;i<=n;i++)ri[i]=mod-ls[0][i-1];
	polyinv(n+1,ri,iri);
	dft(l,iri,1);
	dft(l,ls[0],1);
	for(int a=0;a<=p;a++)for(int b=0;b<=q;b++)
	{
		for(int i=0;i<l;i++)tp[i]=0;
		//vl_(a,b)=x*(vl_(a,b)+f_i)*ls+c
		if(a+b==0)
		{
			for(int i=0;i<=n;i++)tp[i]=cl[i];
			dft(l,tp,1);
		}
		for(int a1=0;a1<=a;a1++)for(int b1=0;b1<=b;b1++)
		for(int i=0;i<l;i++)tp[i]=(tp[i]+1ll*c[a][a1]*c[b][b1]*gr[1][l+i]%mod*
		(vl[a1*(q+1)+b1][i]+fi[a1*(q+1)+b1][i])%mod*ls[(a-a1)*(q+1)+(b-b1)][i])%mod;
		dft(l,tp,0);
		for(int i=n+1;i<l;i++)tp[i]=0;
		dft(l,tp,1);
		for(int i=0;i<l;i++)tp[i]=1ll*tp[i]*iri[i]%mod;
		dft(l,tp,0);
		for(int i=0;i<=n;i++)vl[a*(q+1)+b][i]=tp[i];
		dft(l,vl[a*(q+1)+b],1);
	}
}
int ls2[M][N],t1[N];
void solve_c2(int n,int p,int q,int d)
{
	int l=1;while(l<=n*2+3)l<<=1;
	for(int a=0;a<=p;a++)for(int b=0;b<=q;b++)
	{
		for(int i=0;i<=n;i++)ls2[a*(q+1)+b][i]=1ll*(i?1ll*fr[i*2-1]*ifr[i]%mod*ifr[i-1]%mod:1)*pw(i,a+b)%mod;
		dft(l,ls2[a*(q+1)+b],1);
	}
	for(int a=p;a>=0;a--)for(int b=q;b>=0;b--)
	{
		for(int i=0;i<l;i++)vl[a*(q+1)+b][i]=1ll*vl[a*(q+1)+b][i]*ls2[0][i]%mod;
		for(int a1=0;a1<=a;a1++)for(int b1=0;b1<=b;b1++)if(a1+b1<a+b)
		for(int i=0;i<l;i++)vl[a*(q+1)+b][i]=(vl[a*(q+1)+b][i]+1ll*c[a][a1]*c[b][b1]*
		vl[a1*(q+1)+b1][i]%mod*ls2[(a-a1)*(q+1)+(b-b1)][i]%mod)%mod;
		dft(l,vl[a*(q+1)+b],0);
		for(int i=n+1;i<l;i++)vl[a*(q+1)+b][i]=0;
	}
	for(int i=0;i<=n;i++)t1[i]=i?1ll*fr[2*i+d-1]*ifr[i+d-1]%mod*ifr[i]%mod:1;
	dft(l,t1,1);
	for(int a=p;a>=0;a--)for(int b=q;b>=0;b--)
	{
		dft(l,vl[a*(q+1)+b],1);
		for(int i=0;i<l;i++)vl[a*(q+1)+b][i]=1ll*vl[a*(q+1)+b][i]*t1[i]%mod;
		dft(l,vl[a*(q+1)+b],0);
	}
}
int al[M][N],s1[M][M],t2[N];
void solve_a1(int n,int p,int q,int c)
{
	int l=1;while(l<=n*2+3)l<<=1;
	for(int i=0;i<=n;i++)t2[i]=1ll*fr[2*i+c]*ifr[i+c]%mod*ifr[i]%mod;
	dft(l,t2,1);
	for(int a=0;a<=p;a++)for(int b=0;b<=q;b++)
	{
		for(int i=0;i<=n;i++)al[a*(q+1)+b][i]=1ll*fr[i*2]*ifr[i]%mod*ifr[i]%mod*pw(i,a+b)%mod;
		dft(l,al[a*(q+1)+b],1);
		for(int i=0;i<l;i++)al[a*(q+1)+b][i]=1ll*al[a*(q+1)+b][i]*t2[i]%mod;
		dft(l,al[a*(q+1)+b],0);
	}
	s1[0][0]=1;
	for(int i=1;i<=10;i++)for(int j=1;j<=i;j++)s1[i][j]=1ll*j*(s1[i-1][j]+s1[i-1][j-1])%mod;
	for(int i=0;i<=n;i++)
	for(int a=0;a<=p&&a<=i;a++)for(int b=0;b<=q&&b<=i+c;b++)
	{
		int si=1ll*ifr[a]%mod*ifr[b]%mod*ifr[i-a]%mod*ifr[i+c-b]%mod*fr[i*2+c]%mod;
		if(a<i)si=(si+1ll*ifr[a]%mod*ifr[b]%mod*ifr[i-a-1]%mod*ifr[i+c-b]%mod*fr[i*2+c]%mod*ifr[a+b+1]%mod*fr[a+b])%mod;
		if(b<i+c)si=(si+1ll*ifr[a]%mod*ifr[b]%mod*ifr[i-a]%mod*ifr[i+c-b-1]%mod*fr[i*2+c]%mod*ifr[a+b+1]%mod*fr[a+b])%mod;
		for(int a2=a;a2<=p;a2++)for(int b2=b;b2<=q;b2++)
		al[a2*(q+1)+b2][i]=(al[a2*(q+1)+b2][i]+1ll*s1[a2][a]*s1[b2][b]*si)%mod;
	}
}
int T,d,p,q,n,fg,v[M][M],m;
int main()
{
	scanf("%d%d%d%d%d",&T,&d,&p,&q,&n);
	init();
	if(d<0)p^=q^=p^=q,d*=-1,fg=1;
	solve_cl(n,p,q);
	solve_c2(n,p,q,d);
	if(fg)solve_a1(n,p,q,d);
	while(T--)
	if(fg)
	{
		int as=0;
		scanf("%*d%d",&m);
		for(int i=0;i<=q;i++)for(int j=0;j<=p;j++)scanf("%d",&v[j][i]);
		for(int i=0;i<=p;i++)for(int j=0;j<=q;j++)as=(as+1ll*v[i][j]*(al[i*(q+1)+j][m]+mod-vl[i*(q+1)+j][m]))%mod;
		printf("%d\n",as);
	}
	else
	{
		int as=0;
		scanf("%d%*d",&m);
		for(int i=0;i<=p;i++)for(int j=0;j<=q;j++)scanf("%d",&v[i][j]);
		for(int i=0;i<=p;i++)for(int j=0;j<=q;j++)as=(as+1ll*v[i][j]*vl[i*(q+1)+j][m])%mod;
		printf("%d\n",as);
	}
}
D12 T2 #41 整数
Problem

交互。

给定 nn,你需要确定一个 0,,n10,\cdots,n-1 的排列 pp

交互库有一个整数 xx,初始 x=0x=0。每次你可以询问一个 ii,交互库会将 xx 加上 2pi2^{p_i},随后返回 xx 当前的二进制表示中 11 的数量。

得分与操作次数有关,操作次数小于等于 limlim 时得到满分。

交互库不自适应。

n5000,lim=8×105n\leq 5000,lim=8\times 10^5

4s,512MB4s,512MB

Sol

考虑将所有数询问一次,这样操作后 x=2n1x=2^n-1,即最后 nn 位上全是 11

然后考虑再询问 11,则 p1p_1 这一位会一直进位到 2n2^n,即此时 x=2n+2p11x=2^n+2^{p_1}-1,那么可以根据返回值直接确定 p1p_1

此时如果询问一个满足 pi>p1p_i>p_1 的位置 ii,则会直接加不产生进位,因此此时返回值 +1+1,几乎不能得到有效信息。

但下一次询问 pi<p1p_i<p_1 的数时,又会产生进位,此时返回值相比之前会减少 p1pi1p_1-p_i-1,因此又可以得到这个数的值。之后再询问 [pi,p1][p_i,p_1] 间的数也只能得到 +1+1 的结果。

以此类推可以发现,在 x=2n1x=2^n-1 时顺序询问一次排列,可以得到排列中所有的前缀最小值处的值。

但这之后 xx 变成了 2n+122^{n+1}-2,不能直接重复上面的过程。但注意到第一轮询问一定求出了 11 的位置,那么再加一个 11,即可得到 22n12*2^n-1

那么考虑初始 x=k2n1x=k*2^n-1 的情况,可以发现按照之前的做法做即可,唯一需要注意的是 2n12^{n-1} 前面连续的 11 会对第一次询问造成影响。

此时考虑,每一轮询问时先询问还没求出来的位置,最后再加上已经求出来的位置,这样每一轮都可以求出剩余位置的前缀最小值。

但如果排列为 0,1,,p10,1,\cdots,p-1,这样就需要 O(n2)O(n^2) 的复杂度。

注意到排列是固定的,可以考虑一些随机操作。

考虑每次随机顺序询问,期望前缀最小值个数是 O(logn)O(\log n) 的,因此复杂度为 O(n2logn)O(\frac{n^2}{\log n}),需要 2e6 次,大概可以得到 60 分

但可以发现剩余的元素也有一些信息,前面的元素更可能更大,因此考虑另外一种方式

考虑只在开头随机一次排列然后询问。可以发现如下结果:

操作次数为排列的 LIS 长度。

证明:不难发现每次取出所有前缀最小值后 LIS 长度减一。

而随机一次排列后,LIS 长度期望为 O(n)O(\sqrt n),因此这样复杂度期望 O(nn)O(n\sqrt n)。可以通过。(数据中最坏情况为 7.2×1057.2\times 10^5,平均情况大约为 6.7×1056.7\times 10^5

Code
#include"integer.h"
#include<vector>
#include<random>
#include<algorithm>
using namespace std;
mt19937 rnd(1221391*1221141);
vector<int> findPermutation(int n)
{
	vector<int> rs,as;
	for(int i=0;i<n;i++)operate(i),rs.push_back(i),as.push_back(-1);
	int lv=n,ti=0;
	shuffle(rs.begin(),rs.end(),rnd);
	while(rs.size())
	{
		int li=n,tp=ti;
		while(tp&1)tp>>=1,li++;
		vector<int> sr;
		for(int i=0;i<rs.size();i++)
		{
			int v=operate(rs[i]);
			if(lv<v)sr.push_back(rs[i]);
			else as[rs[i]]=n+li+v-lv-1,li=li+v-lv-1;
			lv=v;
		}
		for(int i=0;i<n;i++)
		{
			if(as[i]>-1&&as[i]<n)lv=operate(i);
			if(as[i]>=n)as[i]-=n;
			if(as[i]==0)lv=operate(i);
		}
		ti++;rs=sr;
	}
	return as;
}
D12 T3 #44 蜘蛛爬树
Problem

给一棵 nn 个点的树,边有边权 lil_i,点有点权 aia_i

现在将这棵树复制 mm 份,记第 ii 份上编号为 jj 的点为 (i,j)(i,j)

接下来在不同树的相同点间连边。对于 i<m,j[1,n]i<m,j\in[1,n],在 (i,j),(i+1,j)(i,j),(i+1,j) 间连一条边,边权为 aja_j。这样得到了 nmnm 个点的连通图。

qq 次询问,每次给定两个点 (c1,x),(c2,y)(c_1,x),(c_2,y),求两点间最短路。

n,q2×105,1ai,m109,1li1012n,q\leq 2\times 10^5,1\leq a_i,m\leq 10^9,1\leq l_i\leq 10^{12}

4s,1024MB4s,1024MB

partial: 树是一条链

Sol

首先考虑最优路径的形式。不妨设 c1c2c_1\leq c_2,则:

  1. 最优路径中经过的点 (i,j)(i,j) 一定满足 ii 不降。

证明:如果折返,因为不同树上的相同边边权相同,将折返部分缩回来一定更优。

  1. 最优路径一定形如,沿着当前树边走到某个点 (c1,z)(c_1,z),沿着树之间的边之间走到 (c2,z)(c_2,z),然后沿着树边走到 (c2,y)(c_2,y)

证明:如果在两个不同的点经过了非树边,则考虑将经过非树边转移到 aa 更小的点,树边路径的平移不改变距离。例如:

o  o  o->o    o  o  o->o
      ^             ^
o  o->o  o -> o  o  o  o
   ^                ^
o->o  o  o    o->o->o  o

考虑走非树边的点 zz,可以得到答案为:

minzdis(x,z)+dis(z,y)+azc1c2\min_z dis(x,z)+dis(z,y)+a_z*|c_1-c_2|

首先考虑一条链的情况,设链上点按照顺序排列,11ii 的距离为 did_i,则询问有三种情况:

  1. zz 在路径上,则答案为 dydx+c1c2mini=xyaid_y-d_x+|c_1-c_2|*\min_{i=x}^ya_i
  2. zz 在左侧,则答案为 dydx+mini=1x(2(dxdi)+aic1c2)d_y-d_x+\min_{i=1}^x(2(d_x-d_i)+a_i*|c_1-c_2|)
  3. zz 在右侧,情况与上一个类似:dydx+mini=yn(2(didy)+aic1c2)d_y-d_x+\min_{i=y}^n(2(d_i-d_y)+a_i*|c_1-c_2|)

第一种可以线段树解决,考虑第二种,相当于求前缀内 2di+aic1c2-2d_i+a_i*|c_1-c_2| 的最小值。那么考虑 (ai,di)(a_i,d_i) 构成的上凸壳,答案一定在凸壳上,如果求出凸壳则可以二分得到答案。第三种情况同理。

因此考虑两种情况分别线段树维护凸壳,即可回答询问。

然后考虑一般情况,考虑通过重链剖分变为链上情况。考虑 (u,v)(u,v) 路径经过的每一条重链,考虑 zz 从这条重链上分出去的情况。设 (x,y)(x,y) 的路径在重链上经过了 [l,r][l,r],可以发现此时变为链上询问 [l,r][l,r] 的问题,但每个点不仅可以选当前点,还可以选轻子树内的点,选轻子树内的点需要额外距离。注意到轻子树大小和为 O(nlogn)O(n\log n),因此将所有轻子树点一起线段树上做凸包即可。此时第一种情况不再是简单的区间 min\min(因为额外距离),但可以再建一个凸包解决。

但这样仍然存在问题;剖分路径 (x,y)(x,y) 后考虑了所有从 (x,y)(x,y) 路径上某个点分出去向下走的情况,但可能 zz 往上走,超过了当前重链。对于这种情况,考虑再询问一次 LCA(x,y)LCA(x,y) 到根经过的所有重链,每条链上询问一个单点,额外加上从路径上走到当前点的距离即可。

最后考虑复杂度,如果直接大力建凸包,询问二分,则每一步都是 O(log3n)O(\log^3 n) 的,无法通过。

首先考虑建凸包,这部分的额外 log\log 只在于按照 xx 坐标排序,但首先可以发现,合并当前点凸壳时只有两个儿子的凸壳上的点可能有用,而儿子的凸壳已经排好序了,归并即可得到顺序,这样得到所有凸包的复杂度变为了 O(nlog2n)O(n\log^2 n)

事实上可以不考虑上下凸壳之类的问题,而是分别维护走到当前区间对应左端点,右端点以及只需要走到重链上时距离和 aa 构成的凸包。合并时可能需要将整体的距离加上某个值,但这不影响点是否在凸包上。

然后考虑询问,考虑将询问按照 c1c2|c_1-c_2| 从小到大排序依次询问,这样一个凸包上的询问满足斜率 kk 递增。此时可以发现可以使用单调栈的方式:如果当前点没有之后的点优秀,则 kk 再增大一定更不优秀(按照凸包上 aa 从大到小的顺序考虑),可以直接删去,删到当前点更优时当前点即为答案。这样即可均摊 O(1)O(1) 回答一个凸包上的询问。

复杂度 O((n+q)log2n)O((n+q)\log^2 n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 200500
#define ll long long
struct sth{ll a,b;};
bool cmp(sth a,sth b){return a.a==b.a?a.b<b.b:a.a<b.a;}
vector<sth> get_hull(vector<sth> si)
{
	vector<sth> as;if(!si.size())return as;
	as.push_back(si[0]);
	for(int i=1;i<si.size();i++)
	{
		if(si[i].b>=as.back().b)continue;
		int ci=as.size()-1;
		while(ci&&(__int128)(si[i].a-as[ci-1].a)*(as[ci].b-as[ci-1].b)>=(__int128)(si[i].b-as[ci-1].b)*(as[ci].a-as[ci-1].a))ci--,as.pop_back();
		as.push_back(si[i]);
	}
	return as;
}
vector<sth> merge(vector<sth> s1,vector<sth> s2)
{
	vector<sth> as;
	int a=s1.size(),b=s2.size(),la=0,lb=0;
	for(int i=0;i<a+b;i++)
	if(la==a)as.push_back(s2[lb++]);
	else if(lb==b)as.push_back(s1[la++]);
	else if(cmp(s1[la],s2[lb]))as.push_back(s1[la++]);
	else as.push_back(s2[lb++]);
	return as;
}

int n,vl[N],head[N],cnt;
struct edge{int t,next;ll v;}ed[N*2];
void adde(int f,int t,ll v)
{
	ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;
	ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;
}
int sz[N],sn[N],f[N];
ll dep[N];
void dfs0(int u,int fa)
{
	sz[u]=1;f[u]=fa;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		dep[ed[i].t]=dep[u]+ed[i].v,dfs0(ed[i].t,u);
		sz[u]+=sz[ed[i].t];
		if(sz[ed[i].t]>sz[sn[u]])sn[u]=ed[i].t;
	}
}
int tp[N],id[N],rid[N],ci,rb[N];
void dfs1(int u,int fa,int t)
{
	tp[u]=t;id[u]=++ci;rid[ci]=u;
	if(sn[u])dfs1(sn[u],u,t);
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])dfs1(ed[i].t,u,ed[i].t);
}
int rt[N],ch[N*2][2],ct;
vector<sth> su[N*2],sl[N*2],sr[N*2];
int build(int l,int r)
{
	int st=++ct;
	if(l==r)
	{
		int u=rid[l],lb=n+1,rb=0;
		for(int i=head[u];i;i=ed[i].next)if(sz[ed[i].t]<sz[u]&&ed[i].t!=sn[u])
		lb=min(lb,id[ed[i].t]),rb=max(rb,id[ed[i].t]+sz[ed[i].t]-1);
		vector<sth> si;
		for(int i=lb;i<=rb;i++)
		{
			int v=rid[i];
			si.push_back((sth){vl[v],2*(dep[v]-dep[u])});
		}
		si.push_back((sth){vl[u],0});
		sort(si.begin(),si.end(),cmp);
		su[st]=sl[st]=sr[st]=get_hull(si);
		for(int i=0;i<su[st].size();i++)
		sl[st][i].b-=2*dep[u],sr[st][i].b+=2*dep[u];
		return st;
	}
	int mid=(l+r)>>1;
	ch[st][0]=build(l,mid);ch[st][1]=build(mid+1,r);
	su[st]=get_hull(merge(su[ch[st][0]],su[ch[st][1]]));
	sl[st]=get_hull(merge(sl[ch[st][0]],sl[ch[st][1]]));
	sr[st]=get_hull(merge(sr[ch[st][0]],sr[ch[st][1]]));
	return st;
}
ll query_u(int x,int l,int r,int l1,int r1,int v)
{
	if(l>r1||r<l1)return 5.9e18;
	if(l1<=l&&r1>=r)
	{
		int si=su[x].size()-1;
		while(si&&su[x][si].a*v+su[x][si].b>=su[x][si-1].a*v+su[x][si-1].b)si--,su[x].pop_back();
		return su[x][si].a*v+su[x][si].b;
	}
	int mid=(l+r)>>1;
	return min(query_u(ch[x][0],l,mid,l1,r1,v),query_u(ch[x][1],mid+1,r,l1,r1,v));
}
ll query_l(int x,int l,int r,int l1,int r1,int v)
{
	if(l>r1||r<l1)return 5.9e18;
	if(l1<=l&&r1>=r)
	{
		int si=sl[x].size()-1;
		while(si&&sl[x][si].a*v+sl[x][si].b>=sl[x][si-1].a*v+sl[x][si-1].b)si--,sl[x].pop_back();
		return sl[x][si].a*v+sl[x][si].b;
	}
	int mid=(l+r)>>1;
	return min(query_l(ch[x][0],l,mid,l1,r1,v),query_l(ch[x][1],mid+1,r,l1,r1,v));
}
ll query_r(int x,int l,int r,int l1,int r1,int v)
{
	if(l>r1||r<l1)return 5.9e18;
	if(l1<=l&&r1>=r)
	{
		int si=sr[x].size()-1;
		while(si&&sr[x][si].a*v+sr[x][si].b>=sr[x][si-1].a*v+sr[x][si-1].b)si--,sr[x].pop_back();
		return sr[x][si].a*v+sr[x][si].b;
	}
	int mid=(l+r)>>1;
	return min(query_r(ch[x][0],l,mid,l1,r1,v),query_r(ch[x][1],mid+1,r,l1,r1,v));
}
ll query_li(int u,int v,int k)
{
	int ri=rt[tp[u]];
	return min(query_u(rt[tp[u]],id[tp[u]],rb[tp[u]],id[u],id[v],k),
	min(query_l(rt[tp[u]],id[tp[u]],rb[tp[u]],1,id[u],k)+2*dep[u],query_r(rt[tp[u]],id[tp[u]],rb[tp[u]],id[v],n,k)-2*dep[v]));
}
void init_tr()
{
	dfs0(1,0);dfs1(1,0,1);
	for(int i=1;i<=n;i++)if(tp[i]==i)
	{
		rb[i]=id[i];while(tp[rid[rb[i]+1]]==i)rb[i]++;
		rt[i]=build(id[i],rb[i]);
	}
}
int getLCA(int x,int y)
{
	while(tp[x]!=tp[y])
	{
		if(dep[tp[x]]<dep[tp[y]])swap(x,y);
		x=f[tp[x]];
	}
	return dep[x]<dep[y]?x:y;
}
ll getdis(int x,int y){return dep[x]+dep[y]-2*dep[getLCA(x,y)];}
ll query_fi(int u,int v,int k)
{
	ll as=5.9e18,ds=getdis(u,v);
	while(tp[u]!=tp[v])
	{
		if(dep[tp[u]]<dep[tp[v]])swap(u,v);
		as=min(as,query_li(tp[u],u,k));u=f[tp[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	as=min(as,query_li(u,v,k));
	int si=u;
	while(si)
	{
		as=min(as,query_l(rt[tp[si]],id[tp[si]],rb[tp[si]],1,id[si],k)+2*dep[u]);
		as=min(as,query_r(rt[tp[si]],id[tp[si]],rb[tp[si]],id[si],n,k)+2*dep[u]-4*dep[si]);
		si=f[tp[si]];
	}
	return as+ds;
}

int q;
ll a,b,c,as[N];
struct que{int s,t,k,id;}qu[N];
bool cmp1(que a,que b){return a.k<b.k;}
int main()
{
	scanf("%d%*d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%lld",&vl[i]);
	for(int i=1;i<n;i++)scanf("%lld%lld%lld",&a,&b,&c),adde(a,b,c);
	init_tr();
	for(int i=1;i<=q;i++)
	{
		scanf("%lld%lld",&a,&b);
		qu[i].s=(a-1)%n+1,qu[i].t=(b-1)%n+1;
		qu[i].k=(a-1)/n-(b-1)/n;if(qu[i].k<0)qu[i].k*=-1;
		qu[i].id=i;
	}
	sort(qu+1,qu+q+1,cmp1);
	for(int i=1;i<=q;i++)as[qu[i].id]=query_fi(qu[i].s,qu[i].t,qu[i].k);
	for(int i=1;i<=q;i++)printf("%lld\n",as[i]);
}
新的旅途即将开始
你还好吗?
-- 4182_543_731
2023.2.9