九月题目总结(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)\)(也就是新的总贡献):
- 它单独形成一个新的连通块,且不作为开头或结尾:\(f_{i,j,k,p,q}\times(j+1-p-q)\to f_{i+1,j+1,k',p,q}\)。
- 它单独形成一个连通块,但是是作为开头:\(f_{i,j,k,0,q}\to f_{i+1,j+1,k',1,q}\)。
- 它单独形成一个连通块,但是是作为结尾:\(f_{i,j,k,p,0}\to f_{i+1,j+1,k',p,1}\)。
- 它和别的连通块相连,但是不是作为开头或结尾(要求 \(j\ge1\)):\(f_{i,j,k,p,q}\times(2\cdot j-p-q)\to f_{i+1,j,k',p,q}\)。
- 它和别的连通块相连,但是是作为开头(要求 \(j\ge1\)):\(f_{i,j,k,0,q}\to f_{i+1,j,k',1,q}\)。
- 它和别的连通块相连,但是是作为结尾(要求 \(j\ge1\)):\(f_{i,j,k,p,0}\to f_{i+1,j,k',p,1}\)。
- 它连接了两个连通块(要求 \(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;
}

浙公网安备 33010602011771号