九月题目总结(2026)

前言

除补题总结和九月晚练总结(2026)之外的题目总结。

A. 相逢是问候

题面

同学发现的神秘题,思路简单但是有点难写。

要算 \(c^{c^{\dots}}\bmod p\) 这种东西,显然我们需要使用扩展欧拉定理,记 \(f(x,i,p)\) 表示 \(c^{c^{\dots c^{x}}}\bmod p\),一共 \(i\) 个 \(c\),那么根据扩展欧拉定理,我们可以得出 \(f(x,i,p)=c^{f(x,i-1,\varphi(p))+\varphi(p)}\bmod p\)。

因为我们知道 \(\varphi(\varphi(\dots\varphi(p)))\) 这样最多只会嵌套 \(\log p\) 次就会变为 \(1\),所以我们可以直接暴力算出来每个 \(f\) 的值,当模数归为 \(1\) 时结果就是 \(0\)。

然后你可以对每个 \(a_i\) 算出所有的 \(f(x,i,p)\) 并把他们存下来,那么一次修改就相当于把这一位的指针往后挪动一格,直到挪到结尾后就不会再动了。

这个挪动直到挪到结尾的过程显然可以均摊,时间复杂度大致为 \(O(n\log n\log p)\)。

但是你会发现写 f 函数时需要快速幂,然后下面枚举 \(i\) 需要 \(\log p\) 次,迭代需要 \(\log p\) 次,总复杂度达到了三支 \(\log\),显然过不了。

因此我们考虑如何去掉快速幂的那支 \(\log\),显然我们可以用类似 BSGS 的方法,将幂拆成 \(p\cdot B+q\) 的形式,然后预处理一下就行了。

预处理的复杂度:\(O(B\log p)\)。

计算 f 的复杂度:\(O(n\log^2p)\)。

查询的复杂度:\(O(n\log n\log p)\)。

代码:

const int N=5e4+6,B=2e4;
int n,m,p,c,cnt,a[N],c1[36][B],c2[36][B];
bool b1[36][B],b2[36][B];//判断取模
vector<int>mod;
vector<int>v[N];
struct seg_tree{
	struct Node{
		int l,r,it,ans;
	}node[N<<2];
	void pushup(int pos,int ls,int rs)
	{
		node[pos].it=min(node[ls].it,node[rs].it);
		node[pos].ans=(node[ls].ans+node[rs].ans)%p;
	}
	void build(int l,int r,int pos)
	{
		node[pos].l=l,node[pos].r=r,node[pos].it=0,node[pos].ans=0;
		if(l==r)
		{
			node[pos].ans=v[l][0];
			return;
		}
		int mid=l+r>>1;
		build(l,mid,pos<<1);
		build(mid+1,r,pos<<1|1);
		pushup(pos,pos<<1,pos<<1|1);
	}
	void update(int pos,int l,int r)
	{
		if(node[pos].it==cnt-1)
		{
			return;
		}
		if(node[pos].l==node[pos].r)
		{
			node[pos].it++;
			node[pos].ans=v[node[pos].l][node[pos].it];
			return;
		}
		int mid=node[pos].l+node[pos].r>>1;
		if(l<=mid)
		{
			update(pos<<1,l,r);
		}
		if(r>mid)
		{
			update(pos<<1|1,l,r);
		}
		pushup(pos,pos<<1,pos<<1|1);
	}
	int query(int pos,int l,int r)
	{
		if(node[pos].l>=l&&node[pos].r<=r)
		{
			return node[pos].ans;
		}
		int mid=node[pos].l+node[pos].r>>1,ans=0;
		if(l<=mid)
		{
			ans=(ans+query(pos<<1,l,r))%p;
		}
		if(r>mid)
		{
			ans=(ans+query(pos<<1|1,l,r))%p;
		}
		return ans;
	}
}tr;
int phi(int x)
{
	int ans=x;
	for(int i=2;i*i<=x;i++)
	{
		if(x%i==0)
		{
			ans=ans/i*(i-1);
			while(x%i==0)
			{
				x/=i;
			}
		}
	}
	if(x>1)
	{
		ans=ans/x*(x-1);
	}
	return ans;
}
void init()
{
	int x=p;
	mod.push_back(0);
	mod.push_back(p);
	while(x>1)
	{
		x=phi(x);
		mod.push_back(x);
	}
	mod.push_back(1);
	cnt=mod.size()-1;
	for(int i=1;i<=cnt;i++)
	{
		c2[i][0]=1;
		b2[i][0]=false;
		for(int j=1;j<=B;j++)
		{
			if(c2[i][j-1]*c>=mod[i])
			{
				b2[i][j]=true;
			}
			c2[i][j]=c2[i][j-1]*c%mod[i];
			b2[i][j]|=b2[i][j-1];
		}
		c1[i][0]=1;
		b1[i][0]=false;
		for(int j=1;j<=B;j++)
		{
			if(c1[i][j-1]*c2[i][B]>=mod[i])
			{
				b1[i][j]=true;
			}
			c1[i][j]=c1[i][j-1]*c2[i][B]%mod[i];
			b1[i][j]|=b1[i][j-1];
			b1[i][j]|=b2[i][B];
		}
	}
}
int qpow(int y,int id,bool &fl)
{
	fl=b1[id][y/B]|b2[id][y%B]|(c1[id][y/B]*c2[id][y%B]>=mod[id]);
	return c1[id][y/B]*c2[id][y%B]%mod[id];
}
int f(int x,int l,int r,bool &fl)//这里需要判断是否取模,如果没取那么不用加 mod[l+1]
{
	if(l==r)
	{
		if(x<mod[r])
		{
			fl=false;
		}
		else
		{
			fl=true;
		}
		return x%mod[r];
	}
	bool flag;
	int num=f(x,l+1,r,flag);
	return qpow(num+(flag?mod[l+1]:0),l,fl);
}
signed main()
{
	n=read(),m=read(),p=read(),c=read();
	init();
	read(a+1,a+n+1);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=cnt;j++)
		{
			bool fl;
			v[i].push_back(f(a[i],1,j,fl));
		}
	}
	tr.build(1,n,1);
	while(m--)
	{
		int op=read(),l=read(),r=read();
		if(op==0)
		{
			tr.update(1,l,r);
		}
		else
		{
			write(tr.query(1,l,r));
			putchar('\n');
		}
	}
	return 0;
}

B. 摩天大楼 / Skyscraper

题面

本来是晚练的,被拿来当练习题了。

首先排列不定,很容易想到依次考虑每个数,然后把当前这个数插入到某个位置。

问题在于我们要算差的绝对值,如果直接插入,我并不知道我旁边的两个数和它们的大小关系,这咋办呢?

那既然没有大小关系,我们强制定义一个大小关系不就对了?

我们考虑将整个数组从小到大排序后依次扫描,考虑将下一个数插入进来后会产生的贡献。

显然目前每个连通块左右都会产生一次贡献(除了开头和结尾),所以我们可以直接计算这个数 插入进来后产生的贡献。

接下来显然就是转移了,因此我们先把 DP 状态设出来:设 \(f_{i,j,k,0/1,0/1}\) 表示考虑前 \(i\) 个数,已经形成了 \(j\) 个连通块,目前总贡献是 \(k\),开头有(或没有)连通块,结尾有(或没有)连通块时的方案数。初始化 \(f_{0,0,0,0,0}=1\)。

我们考虑将 \(i+1\) 这个数插入进去后的所有可能情况,记 \(k'=k+(a_{i+1}-a_i)(2\cdot j-p-q)\)(也就是新的总贡献):

  1. 它单独形成一个新的连通块,且不作为开头或结尾:\(f_{i,j,k,p,q}\times(j+1-p-q)\to f_{i+1,j+1,k',p,q}\)。
  2. 它单独形成一个连通块,但是是作为开头:\(f_{i,j,k,0,q}\to f_{i+1,j+1,k',1,q}\)。
  3. 它单独形成一个连通块,但是是作为结尾:\(f_{i,j,k,p,0}\to f_{i+1,j+1,k',p,1}\)。
  4. 它和别的连通块相连,但是不是作为开头或结尾(要求 \(j\ge1\)):\(f_{i,j,k,p,q}\times(2\cdot j-p-q)\to f_{i+1,j,k',p,q}\)。
  5. 它和别的连通块相连,但是是作为开头(要求 \(j\ge1\)):\(f_{i,j,k,0,q}\to f_{i+1,j,k',1,q}\)。
  6. 它和别的连通块相连,但是是作为结尾(要求 \(j\ge1\)):\(f_{i,j,k,p,0}\to f_{i+1,j,k',p,1}\)。
  7. 它连接了两个连通块(要求 \(j\ge2\)):\(f_{i,j,k,p,q}\times(j-1)\to f_{i+1,j-1,k',p,q}\)。

最终答案就是 \(\sum_{i=0}^Lf_{n,1,i,1,1}\)。

代码:

const int N=106,L=1e3+6,mod=1e9+7;
int n,limit,a[N],f[N][N][L][2][2];
signed main()
{
	n=read(),limit=read();
	read(a+1,a+n+1);
	if(n==1)
	{
		write(1);
		return 0;
	}
	f[0][0][0][0][0]=1;
	sort(a+1,a+n+1);
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<=i;j++)
		{
			for(int k=0;k<=limit;k++)
			{
				for(int p=0;p<2;p++)
				{
					for(int q=0;q<2;q++)
					{
						int kk=k+(a[i+1]-a[i])*(2*j-p-q);
						if(kk>limit)
						{
							continue;
						}
						f[i+1][j+1][kk][p][q]=(f[i+1][j+1][kk][p][q]+f[i][j][k][p][q]*(j+1-p-q)%mod)%mod;
						if(!p)
						{
							f[i+1][j+1][kk][1][q]=(f[i+1][j+1][kk][1][q]+f[i][j][k][p][q])%mod;
						}
						if(!q)
						{
							f[i+1][j+1][kk][p][1]=(f[i+1][j+1][kk][p][1]+f[i][j][k][p][q])%mod;
						}
						if(j>=1)
						{
							f[i+1][j][kk][p][q]=(f[i+1][j][kk][p][q]+f[i][j][k][p][q]*(j*2-p-q)%mod)%mod;
							if(!p)
							{
								f[i+1][j][kk][1][q]=(f[i+1][j][kk][1][q]+f[i][j][k][p][q])%mod;
							}
							if(!q)
							{
								f[i+1][j][kk][p][1]=(f[i+1][j][kk][p][1]+f[i][j][k][p][q])%mod;
							}
						}
						if(j>=2)
						{
							f[i+1][j-1][kk][p][q]=(f[i+1][j-1][kk][p][q]+f[i][j][k][p][q]*(j-1)%mod)%mod;
						}
					}
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<=limit;i++)
	{
		ans=(ans+f[n][1][i][1][1])%mod;
	}
	write(ans);
	return 0;
}
posted @ 2026-09-04 11:46  cwxcplh  阅读(6)  评论(0)    收藏  举报