2026一轮省集
2026一轮省集
D3T3
题面:
\(n\) 个顾客各点一道菜,做 \(i\) 顾客的需要 \(a_i\) 时间,顾客吃完需要 \(b_i\) 时间,同时只能做一道菜,但可以很多顾客都在吃。对于一个整数 \(k\),记 \(f(a,b,k)\) 表示任选其中 \(k\) 个顾客按任意顺序做他们的菜,从开始做到最后一个人吃完的最小时间。已知 \(b\) 序列,对于每个 \(k\) 输出,对于 \(a_i\) 在 \([1,V]\) 内任取整数的所有序列 \(a\),\(f(a,b,k)\) 的总和。 \(n\leq 30,V\leq 20,b_i\leq 600\)
题解:
首先考虑如果 \(a,b\) 都确定并且知道服务哪 \(k\) 个人,最优方案是什么,显然是按照 \(b\) 从大到小的顺序做这 \(k\) 个人的菜。
然后考虑如果知道 \(a,b\) 如何求出所有 \(k\) 的答案,做法将 \(b\) 从小到大排序,设 \(f_{i,j}\) 表示前 \(i\) 个人选了 \(j\) 个人服务的最短时间,转移是 \(f_{i,j}=\min(f_{i-1,j},\max(f_{i-1,j-1},b_i)+a_i)\),分别表示不选和选 \(i\) 的变化。
然后这个 DP 是下凸的,考虑如下 slope trick 计算答案:
- 将 \((a_i,b_i)\) 按照 \(b\) 从小到大排序,令 \(b_{n+1}=inf\),维护小根堆 \(q\) 初始有 \(inf\),变量 \(s=0\)。
- 然后遍历 \(i=1\sim n\):
- 若 \(s\) 与堆顶和 \(\leq b_i\),将 \(s\) 加上堆顶并将其弹出。
- 将堆顶元素减小 \(b_i-s\),令 \(s=b_i\)。
- 插入 \(a_i\)。
当 \(q\) 恰好弹出 \(k\) 个元素时的 \(s\) 即为服务 \(k\) 个人的答案。
考虑对此过程进行 DP,显然无法把堆放到状态中,所以考虑对于每个 \(a_i\),在插入式就提前决策其是否最终完全被弹出堆。下文弹出指完全弹出,如果部分被保留在堆中认为没有被弹出。设 \(f_{i,k,l,r,s}\) 表示考虑前 \(i\) 个已经钦定 \(k\) 个元素被弹出和当前答案 \(s\),以及最小不会被弹出的元素 \(r\),目前堆中最大的以后要被弹出的元素 \(l\),的方案数。
在上述 slope trick 中会发现 \(s\) 在任意时刻都是 \(\leq b_i\) 的,在 DP 中由于提前钦定一些东西最后一定会被弹出,他们的答案会在弹出前提前加入 \(s\),所以 \(s\) 可能会大于 \(b_i\)。
- 如果 \(s\leq b_i\),说明此时钦定被弹出的元素不在堆中,同时堆顶会减小 \(b_i-s\),所以 \(l'=0,r'=r-b_i+s,s'=b_i\)。
- 否则一定有元素钦定弹出但还在堆中,此时我们记录的是虚假的 \(s\),真正的 \(s\) 此时会变成 \(b_i\),那么 \(l\) 还能留在堆里的部分最多就是当前 \(s\) 和真正 \(s\) 的差值即 \(s-b_i\),其余不分不变。所以 \(l'=\min(l,s-b_i),r'=r,s'=s\)。
还有其余在加入一个元素时钦定它是那种情况以及统计答案的转移是简单的。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=35,M=1205,mod=998244353,inf=1e9+7;
int n,V,fac[N],b[N],ans[N],f[2][N][N][N][M],S;
int Mod(int x){return x>=mod?x-mod:x;}
void Add(int &x,int y){x=Mod(x+y);}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();V=read();
fac[0]=1;for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*V%mod;
for(int i=1;i<=n;i++) b[i]=read();
sort(b+1,b+1+n);
S=b[n]+n*V;
f[0][0][0][V+1][0]=1;
for(int i=1;i<=n;i++){
int z=i&1;
for(int j=0;j<=i;j++)
for(int l=0;l<=V;l++)
for(int r=l;r<=V+1;r++)
for(int s=0;s<=S;s++) f[z][j][l][r][s]=0;
for(int j=0;j<i;j++)
for(int l=0;l<=V;l++)
for(int r=l;r<=V+1;r++)
for(int s=b[i-1];s<=S;s++)
if(f[z^1][j][l][r][s]){
if(b[i-1]<s&&s<=b[i]) ans[j]=(ans[j]+1ll*f[z^1][j][l][r][s]*s%mod*fac[n-i+1])%mod;
int tl,tr,ts;
if(s<=b[i]) tl=0,tr=r-b[i]+s,ts=b[i];
else tl=min(l,s-b[i]),tr=r,ts=s;
if(r==V+1) tr=V+1;
if(tr<=0) continue;
for(int v=1;v<=V;v++){
if(v>=tl) Add(f[z][j][tl][min(tr,v)][ts],f[z^1][j][l][r][s]);
if(v<tr) Add(f[z][j+1][max(tl,v)][tr][ts+v],f[z^1][j][l][r][s]);
}
}
}
for(int j=0;j<=n;j++)
for(int l=0;l<=V;l++)
for(int r=l;r<=V+1;r++)
for(int s=b[n]+1;s<=S;s++)
if(f[n&1][j][l][r][s])
ans[j]=(ans[j]+1ll*f[n&1][j][l][r][s]*s)%mod;
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
return 0;
}
D4T1
题面:
维护一个序列 \(a\) 和一个质数 \(p\),支持两种操作 \(q\) 次,区间乘上 \(x\),或询问区间 \([l,r]\) 从中选择一些 \(a_i\)(可以重复选或不选)相乘对 \(p\) 取模有多少种结果。
\(1\leq n,q\leq 10^5,1\leq a_i,x<p<10^{14}\)
题解:
考虑询问想说什么,在模 \(p\) 下询问乘法结果数是困难的,但是我们知道询问加法是简单的,考虑裴蜀定理,如果询问 \(S\) 集合内元素加法只需求 \(\gcd_{x\in S}x\) 即可。那么就用原根转化为加法,具体的设 \(g^{x_i}=a_i\),求 \(x[l:r]\) 模 \(p-1\) 下的加法询问。
已知询问是区间 \(\gcd\),但是还有区间乘,原根后就是区间加,做法是区间 \(\gcd\) 接近于差分数组的区间 \(\gcd\),但是在差分上区间加就是单点加就可以做了。
问题在于求原根是 \(\sqrt p\) 的会 TLE。近似原根的我们可以求阶,设 \(a_i\) 的阶是 \(y_i\),我们目标是求 \(\gcd(x_i,p-1)\),考虑阶的定义 \(a_i^{y_i}=1,g^{p-1}=1,g^{x_i}=a_i\),所以 \(x_iy_i\mid p-1,y_i=\dfrac{p-1}{\gcd(x_i,p-1)}\),那么就有 \(\gcd(x_i,p-1)=\dfrac{p-1}{y_i}\),而求 \(y_i\) 是 \(\log^2p\) 的,只需要先做 \(p\) 的质因数分解,每次试除一个质数即可。
同样的令 \(b_i=a_i/a_{i-1}\),询问 \(a[l:r]\) 就等价于询问 \(b[l+1:r]+a_l\),区间乘就是单点乘,总复杂度 \(O(n\log^2p)\) 解决这道题。
代码
#include<bits/stdc++.h>
#define ll long long
#define u128 __int128
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=1e5+5;
int n,Q;
ll p,a[N],b[N];
vector<ll>pri;
ll ksm(ll a,ll b){
ll t=1;
for(;b;b>>=1,a=(u128)a*a%p)
if(b&1) t=(u128)t*a%p;
return t;
}
void init(ll x){
for(ll i=2;i*i<=x;i++){
if(x%i==0){
while(x%i==0) x/=i;
pri.push_back(i);
}
}
if(x>1) pri.push_back(x);
}
ll solve(ll v){
ll r=p-1;
for(ll q:pri)
while(r%q==0&&ksm(v,r/q)==1) r/=q;
return (p-1)/r;
}
namespace A{
struct Tree{
ll v,tag;
}t[N<<2];
void upd(int s,ll v){
t[s].v=(u128)t[s].v*v%p;
t[s].tag=(u128)t[s].tag*v%p;
}
void pushdown(int s){
if(t[s].tag!=1){
upd(s<<1,t[s].tag);
upd(s<<1|1,t[s].tag);
t[s].tag=1;
}
}
void build(int s,int l,int r){
t[s].tag=1;
if(l==r){
t[s].v=a[l];
return ;
}
int mid=l+r>>1;
build(s<<1,l,mid);
build(s<<1|1,mid+1,r);
}
void update(int s,int l,int r,int L,int R,ll v){
if(L<=l&&r<=R) return upd(s,v),void();
pushdown(s);
int mid=l+r>>1;
if(L<=mid) update(s<<1,l,mid,L,R,v);
if(R>mid) update(s<<1|1,mid+1,r,L,R,v);
}
ll query(int s,int l,int r,int x){
if(l==r) return t[s].v;
pushdown(s);
int mid=l+r>>1;
if(x<=mid) return query(s<<1,l,mid,x);
else return query(s<<1|1,mid+1,r,x);
}
}
namespace B{
struct Tree{
ll v,s;
}t[N<<2];
void build(int s,int l,int r){
if(l==r){
t[s].v=b[l];
t[s].s=solve(t[s].v);
return;
}
int mid=l+r>>1;
build(s<<1,l,mid);
build(s<<1|1,mid+1,r);
t[s].s=__gcd(t[s<<1].s,t[s<<1|1].s);
}
void update(int s,int l,int r,int x,ll v){
if(l==r){
t[s].v=(u128)t[s].v*v%p;
t[s].s=solve(t[s].v);
return ;
}
int mid=l+r>>1;
if(x<=mid) update(s<<1,l,mid,x,v);
else update(s<<1|1,mid+1,r,x,v);
t[s].s=__gcd(t[s<<1].s,t[s<<1|1].s);
}
ll query(int s,int l,int r,int L,int R){
if(L<=l&&r<=R) return t[s].s;
int mid=l+r>>1;
if(R<=mid) return query(s<<1,l,mid,L,R);
if(L>mid) return query(s<<1|1,mid+1,r,L,R);
return __gcd(query(s<<1,l,mid,L,R),query(s<<1|1,mid+1,r,L,R));
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();p=read();Q=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) b[i]=(u128)a[i]*ksm(a[i-1],p-2)%p;b[1]=a[1];
init(p-1);
A::build(1,1,n);B::build(1,1,n);
for(int i=1;i<=Q;i++){
int op=read();
if(op==1){
int l=read(),r=read();ll v=read();
A::update(1,1,n,l,r,v);
B::update(1,1,n,l,v);
if(r+1<=n) B::update(1,1,n,r+1,ksm(v,p-2));
}
else{
int l=read(),r=read();
ll g=0;
if(l+1<=r) g=B::query(1,1,n,l+1,r);
ll v=A::query(1,1,n,l);
g=__gcd(g,solve(v));
printf("%lld\n",(p-1)/g);
}
}
return 0;
}
D7T3
题面:
定义长 \(2n\) 的数组是好的,当且仅当 \(1\sim n\) 在其中各出现两次。
对于一个好数组 \(a\),定义 \(i\) 是完美点,当且仅当仅考虑前缀 \(a_1\sim a_i\) 是好数组。
\(T\) 组数据,给定 \(n,m\) 求满足以下条件的数组 \(a\) 的个数:
- \(a\) 是长 \(2n\) 的好数组。
- 如果 \(a_i,a_{i+1},a_{i+2}\) 形成等差数列,则 \(i,i+1\) 中必须至少有一个完美点。
- 不存在 \(i<j<k\) 且 \(a_i\geq a_j\geq a_k\)。
- 存在恰好 \(m\) 个 \(a_i\leq a_{i+1}\)。
\(1\leq T\leq 10^5,1\leq n,m\leq 10^6\)
题解:
现在你有 \(1\sim n\) 各两个,考虑第三个限制,设 \(i\) 这两个元素的出现位置是 \(pre_i,suf_i,(pre_i<suf_i)\),则 \(pre_i,suf_i\) 分别单调递增,只有这样才能满足第三个限制。
所以现在是两个 \(1\sim n\) 的序列,每次选一段填到总序列里,要求第二段任意时刻不能比第一段填的多。考虑最后一个限制,每次填第二序列时都会在交界处产生一个,所以限制转化为第二序列被分成恰好 \(m\) 段。
再考虑第二个限制,如果 \(i\) 是完美点,要求 \(1\sim i\) 填了第一序列第二序列各 \(\frac i 2\) 个。考虑两段序列交界时由于交界处前后差都是 \(1\),而交界处必然不是 \(1\) 所以不会等差,那么连续三个数形成等差必然要求连续填了某一个序列的大于等于三个数。你细想发现如果是第一序列则由于填之前就比第二序列填的多,所以必然没有完美点;如果是第二序列那么 \(i,i+1\) 中出现一个完美点则 \(i+2\) 一定会超过第一序列。
综上所述,如果成等差则一定不合法。所以问题变成了,两个 \(1\sim n\) 序列,每次填某一个序列的一个数或两个数,每个恰好填 \(m\) 次,任意时刻第一序列填的比第二序列多的方案数。
考虑格路计数,交替向右向上,每次一步或两步,不触碰直线 \(y=x+1\),\(m\) 步走到 \((n,n)\)。问题容易变成每次零步或一步,走到 \((n-m,n-m)\)。把交替向右向上两步看做一步,问题变成有四种走法:不动,向右,向上,向右上,\(m\) 步走到 \((n-m,n-m)\)。
枚举不动有 \(x\) 步,向右上有 \(y\) 步,则问题变为向右或向上走到 \((n-m-y,n-m-y)\),方案数是 \(C_{n-m-y}\),\(C_i\) 表示卡特兰数。同时有等量关系 \(2(n-m-y)+x+y=m\),所以 \(y\) 确定后 \(x\) 是唯一的。那么答案为:
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=1e6+5,mod=998244353;
ll fac[N],inv[N];
ll ksm(ll a,ll b){
ll t=1;
for(;b;b>>=1,a=a*a%mod)
if(b&1) t=t*a%mod;
return t;
}
ll C(int n,int m){
if(n<0||m<0||n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void solve(){
int n=read(),m=read();
printf("%lld\n",C(m+1,n-m)*C(m+1,n-m+1)%mod*ksm(m+1,mod-2)%mod);
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
fac[0]=1;
for(int i=1;i<N;i++) fac[i]=fac[i-1]*i%mod;
inv[N-1]=ksm(fac[N-1],mod-2);
for(int i=N-2;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
int T=read();
while(T--) solve();
return 0;
}
D8T3
题面:
称一对数组 \(a_{1\sim n},b_{1\sim m}\) 合法,当且仅当:
- \(a_i,b_j\in[0,2^k-1]\)。
- \(a\) 中元素互不相同,\(b\) 中元素互不相同。
- 对于 \(1\leq i \leq \min(n,m),a_i\not = b_i\)。
- \(a\) 中所有数异或和等于 \(b\) 中所有数异或和。
给出 \(n,m,K\) 求合法数组对数,\(T\) 组数据。\(T\leq 30,1\leq n,m,\leq 10^6,1\leq k\leq 10^9\)。
题解:
不妨设 \(n\geq m\),枚举 \(a,b\) 中有 \(p\) 个数相同,方案数是两部分,一是 \(n+m-2p\) 个数互不相同异或和为 \(0\),二是 \(p\) 对位置不同的匹配数值任意。
考虑第一部分 \(f_i\) 表示 \(i\) 个互不相同的数异或和为 \(0\) 的方案数,考虑前 \(i-1\) 个随便填互不相同,方案数 \((2^k)^{\underline{i-1}}\),用最后一个数调整成 \(0\),要求 \(i-1\) 个数的异或和与 \(i-1\) 个数都不同。考虑容斥,假设与 \(j\) 相同,则去掉 \(i,j\),变为 \(i-2\) 个数互不相同异或和为零的方案数。所以 \(f_i=(2^k)^{\underline{i-1}}-f_{i-2}(2^k-(i-2))(i-1)\)。
考虑第二部分,选择值的方案数是 \((2^k-(n+m-2p))^{\underline{p}}\),然后考虑选择匹配位置的方案数,假定是 \(m\) 的前 \(p\) 个位置与 \(n\) 中的位置匹配,最后方案数乘 \(\binom m p\)。问题变为 \(n\) 个数与 \(p\) 个数匹配不能选对应位置,转化为下面补齐 \(n-p\) 个位置,求 \(n\) 个数 \(p\) 个限制的错排数,其中原问题一方案对应新问题 \((n-p)!\) 方案。
设 \(n\) 个数 \(p\) 限制错排数为 \(D(n,p)\)。首先求出 \(D(n,n)\),考虑最后一位的匹配方案,转移是 \(D(n,n)=(n-1)(D(n-1,n-1)+D(n-2,n-2))\)。
\(D(n,p)\) 的转移方程有两种:
- 考虑 \(p+1\) 位匹配位置有 \(D(n,p)=D(n-1,p)+D(n,p+1)\)。
- 考虑 \(p\) 的匹配位置有 \(D(n,p)=(n-p)D(n-1,p-1)+(p-1)D(n-1,p-2)\)。
综合上述方程,若已知 \(D(n-1,p),D(n,p+1)\),根据第一个方程解得 \(D(n,p)\),根据第二个方程解得 \(D(n-1,p-1)\),得到 \(D(n-1,p-1),D(n,p)\),可以求得 \(n\) 固定下的所有 \(p\)。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=2e6+5,mod=1e9+7;
int n,m,K;
ll fac[N],inv[N],pw[N],inc[N],A[N],D[N],U[N],V[N],inp[N];
ll Mod(ll x){return x>=mod?x-mod:x;}
void Add(ll &x,ll y){x=Mod(x+y);}
ll ksm(ll a,ll b){
ll t=1;
for(;b;b>>=1,a=a*a%mod)
if(b&1) t=t*a%mod;
return t;
}
void init(){
ll x=ksm(2,K);
int lim=n+m;
if(K<=22) lim=min(lim,1<<K);
pw[0]=1;
for(int i=1;i<=lim;i++)
pw[i]=pw[i-1]*Mod(x+mod-i+1)%mod;
inc[lim]=ksm(pw[lim],mod-2);
for(int i=lim-1;i>=0;i--)
inc[i]=inc[i+1]*Mod(x+mod-i)%mod;
A[0]=A[1]=1;
for(int i=2;i<=lim;i++)
A[i]=Mod(pw[i-1]+mod-A[i-2]*(i-1)%mod*Mod(x+mod-(i-2))%mod);
U[n]=D[n-1];V[n]=D[n];
for(int p=n-1;p>=1;p--){
V[p]=Mod(U[p+1]+V[p+1]);
U[p]=inp[p]*Mod(V[p+1]+mod-U[p+1]*(n-p-1)%mod)%mod;
}
}
ll C(int n,int m){
if(n<m||m<0||n<0) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void solve(){
n=read();m=read();K=read();
if(n<m) swap(n,m);
if(K<=22&&n>(1<<K)){
puts("0");
return ;
}
init();
ll ans=0;
if(K>22||n+m<=(1<<K)) ans=A[n+m];
for(int p=1;p<=m;p++) if(K>22||n+m-p<=(1<<K))
(ans+=A[n+m-2*p]*C(m,p)%mod*inv[n-p]%mod*V[p]%mod*pw[n+m-p]%mod*inc[n+m-2*p])%=mod;
printf("%lld\n",ans);
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
fac[0]=1;
for(int i=1;i<N;i++) fac[i]=fac[i-1]*i%mod;
inv[N-1]=ksm(fac[N-1],mod-2);
for(int i=N-2;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
D[0]=1;D[1]=0;
for(int i=2;i<N;i++) D[i]=(i-1)*Mod(D[i-1]+D[i-2])%mod;
inp[1]=1;
for(int i=2;i<N;i++) inp[i]=(mod-mod/i)*inp[mod%i]%mod;
int T=read();
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号