8.30 模拟赛题解
T1
不难发现 \(b_{2^n-1}=a_n\),证明可以考虑数学归纳法,即 \(f_n\) 表示 \(a_n\) 的位置,显然有 \(f_n=2f_{n-1}+1\),归纳求解通项公式即可。
然后根据倍长的性质可得 \(b_{2^n-1+k}=b_k,k\in [0,2^n-1]\),且 \(S_{2^n-1}=2S_{2^{n-1}-1}+a_n\),直接把 \(k\) 按照这些式子递归即可。
根据实现不同,存在时间复杂度为 \(O(\log k),O(\log ^2 k),O(\log ^3 k)\) 的做法,标算写的是 \(O(\log ^3 k)\) 的,反正不会超时。
code
#include <bits/stdc++.h>
//#define BIG
#define MID
#ifdef BIG
#define int __int128
#endif
#ifdef MID
#define int long long
#endif
using namespace std;
const int mod=998244353;
int n,k,a[65];
inline int read(){
int ret=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){ret=ret*10+c-'0';c=getchar();}
return ret*f;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline void writesp(int x){write(x),putchar(' ');}
inline void writeln(int x){write(x),putchar('\n');}
inline int S(int k){
if(k<=0)return 0;
if(k==1)return a[1];
int n=1;while(2*n+1<k)n=2*n+1;
if(2*n+1==k)return (2*S(n)+a[__lg(n+1)+1])%mod;
return (S(n)+S(k-n))%mod;
}
signed main(){
n=read(),k=read();
for(int i=1;i<=n;i++)a[i]=read();
writeln(S(k));
return 0;
}
T2
考虑对于一条边,当且仅当取 \(x = c_u ⊕ c_v\) 会让这条边产生冲突。
然后扩展一下,发现如果定义边的权值 \(w_i = c_u ⊕ c_v\),钦定 \(x=w_i\),将 \(w_i\) 相等的所有边合并,那么得到的一个连通块要么全部异或 \(x\),要么不动,当然还有未曾出现在 \(w_i\) 中的 \(x\),这时候连通块个数为 \(n\),最后统计即可。
然后直接拍一个并查集上去就是 \(O(nm\log n)\) 的了,期望得分 60pts。实际拿了 85。数据不是我造的
考虑优化,发现只会加入 \(m=O(n)\) 条边,那么每一次清空并查集就是不必要的了,只要改成撤销即可均摊到 \(O(m\log n)\)。
于是写一个可撤销并查集就做完了。
关于可撤销并查集
首先介绍启发式合并的并查集,考虑记录每个连通块的大小,每次把小的连通块合并到大的上,这样并查集的深度是 \(O(\log n)\) 的,因为每次合并相对于小块而言块大小倍增,而深度只会 +1。
考虑为什么路径压缩不能撤销,应为路径压缩的复杂度是均摊的,存在一次修改 \(O(n)\) 个节点的情况,但是启发式合并的复杂度是严格的,而且一次只修改 \(O(1)\) 个节点。
开一个 stack 记录每次修改了哪几个节点,回退时改回去即可。
code
#include <bits/stdc++.h>
//#define BIG
#define MID
#ifdef BIG
#define int __int128
#endif
#ifdef MID
#define int long long
#endif
using namespace std;
const int N=5e5+5,mod=998244353;
int n,m,k,c[N],ans,cnt;
struct Miku{
int u,v,w;
inline bool operator <(const Miku &B)const{return w<B.w;}
}e[N];
struct His{
int fp,fv,sp,sv;
};
namespace DSU{
int fa[N],siz[N],res;
stack<His> his;
inline int getfa(int x){return fa[x]==x?x:getfa(fa[x]);}
inline void merge(int x,int y){
int fx=getfa(x),fy=getfa(y);
if(fx==fy)return;res--;
if(siz[fx]>siz[fy])swap(fx,fy);
his.push({fx,fa[fx],fy,siz[fy]});
fa[fx]=fy,siz[fy]+=siz[fx];
}
inline void rollback(int t){
while(his.size()>t){
His tp=his.top();his.pop();
fa[tp.fp]=tp.fv,siz[tp.sp]=tp.sv,res++;
}
}
}using namespace DSU;
inline int read(){
int ret=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){ret=ret*10+c-'0';c=getchar();}
return ret*f;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline void writesp(int x){write(x),putchar(' ');}
inline void writeln(int x){write(x),putchar('\n');}
inline int qpow(int a,int b=mod-2,int p=mod){
int res=1;
for(;b;b>>=1,a=a*a%p)
if(b&1)res=res*a%p;
return res;
}
signed main(){
n=read(),m=read(),k=read();
for(int i=1;i<=n;i++)c[i]=read();
for(int i=1;i<=m;i++){
e[i].u=read(),e[i].v=read();
e[i].w=c[e[i].u]^c[e[i].v];
}
sort(e+1,e+m+1);
iota(fa+1,fa+n+1,1),fill(siz+1,siz+n+1,1),res=n;
for(int i=1;i<=m;){
int j=i;for(;e[i].w==e[j].w;j++)merge(e[j].u,e[j].v);
ans=(ans+qpow(2,res))%mod,cnt++,rollback(0),i=j;
}
ans=(ans+(qpow(2,k)-cnt)*qpow(2,n)%mod)%mod;
writeln((ans+mod)%mod);
return 0;
}
T3
考虑把好求的信息先算出来,注意到时光走到一个节点再走回去能炸鱼的数量是一定的,设走到 \(u\) 返回 \(1\) 的收益为 \(f_u\),贪心的考虑一定是先选择单位时间小的鱼去炸,所以 \(f_u\) 会按照 \(t\) 从小往大取。
考虑把 \((t,x)\) 拍到一个值域数据结构上,这里以线段树为例,当然这题分块应该也可以,但是比线段树难写。
不难想到可以二分答案,然后在线段树上查询并 check 是容易的,注意到二分结构和线段树的结构极为相似,所以可以合并在一个函数里,然后就是 \(O(\log n)\) 的了,这个技巧也被称为线段树二分。
然后考虑二分答案,将 \(mid\leq f_u\) 的点设为黑点,那么我们需要判断褚嬴是否能通过题目中的操作让时光到不了任何一个黑点。
这是一个简单 DP 问题,这里不讲了。
复杂度 \(O(n\log n)\)
有非常多的 corner case,我卡了三个我写的错解,都卡成 0 分了,可见对拍的重要性。
code
#include <bits/stdc++.h>
// #define BIG
#define MID
#ifdef BIG
#define int __int128
#endif
#ifdef MID
#define int long long
#endif
using namespace std;
using pii=pair<int,int>;
const int N=1e5+5,M=1e6+5,U=1e6;
int n,T,a[N],t[N],f[N];
bool w[N],g[N];
vector<pii> G[N];
namespace SGT{
struct Tree{
int sum,val;
inline Tree operator +(const Tree &B)const{return {sum+B.sum,val+B.val};}
}tr[M<<2];
inline void pushup(int k){tr[k]=tr[k<<1]+tr[k<<1|1];}
inline void update(int k,int l,int r,int x,int v){
if(l==r)return tr[k]=tr[k]+(Tree){x*v,v},void();int mid=l+r>>1;
if(x<=mid)update(k<<1,l,mid,x,v);
else update(k<<1|1,mid+1,r,x,v);pushup(k);
}
inline int query(int k,int l,int r,int v){
if(l==r)return min(tr[k].val,v/l);int mid=l+r>>1;
if(v<=tr[k<<1].sum)return query(k<<1,l,mid,v);
return tr[k<<1].val+query(k<<1|1,mid+1,r,v-tr[k<<1].sum);
}
}using namespace SGT;
inline int read(){
int ret=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){ret=ret*10+c-'0';c=getchar();}
return ret*f;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline void writesp(int x){write(x),putchar(' ');}
inline void writeln(int x){write(x),putchar('\n');}
inline void DFS(int u,int s){
update(1,1,U,t[u],a[u]),f[u]=query(1,1,U,T-2*s);
for(auto [v,w]:G[u])DFS(v,s+w);update(1,1,U,t[u],-a[u]);
}
inline void dfs(int u){
int cnt=0;
for(auto [v,w]:G[u])dfs(v),cnt+=!g[v];
g[u]=cnt<=1;if(w[u])g[u]=0;
if(!G[u].size())g[u]=!w[u];
}
inline bool check(int mid){
for(int i=1;i<=n;i++)w[i]=f[i]>=mid,g[i]=1;dfs(1);if(!g[1])return 1;
int cnt=0;for(auto [v,w]:G[1])cnt+=!g[v];return cnt>0;
}
signed main(){
n=read(),T=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)t[i]=read();
for(int i=2;i<=n;i++){
int p=read(),w=read();
G[p].emplace_back(i,w);
}
DFS(1,0);
int L=0,R=1e11;
while(L<=R){
int mid=L+R>>1;
if(check(mid))L=mid+1;else R=mid-1;
}
writeln(R);
return 0;
}
T4
首先考虑拆贡献,计算每一个 \(a_i\) 的贡献系数 \(c_i\),每次询问的答案即为 \(\sum_{i=l}^{r} a_i\times c_{i-l+1}\)
对于这种等概率选择的问题,有一个经典的等价模型,即将每种选法映射为一个 \(1\) 到 \(n\) 的排列。
然后你发现你推不下去了,因为那个排列的权值计算十分复杂,好像是前面逆序对乘上后面顺序对,感觉不可做的样子
我们考虑不用这个经典模型,而是类似于 CF1392H 的思路去二次拆分贡献,这里就是把 \(a_i\) 的贡献拆成 \(a_i\) 个 \(1\) 的贡献。
我们将 \(c_i\) 改为 \(f_{i,n}\) 表示 \(a_i=1\),其余为 \(0\),总长度为 \(n\) 的期望和,因为一维数组不太好转移。
分类讨论:
1.恰好选到 \(i\),概率为 \(\frac{1}{n}\),权值为 \(\sum_{j=1}^{i-1} f_{j,n-1}\),因为选到 \(i\) 会让 \(1\) 到 \(i-1\) 的所有数都变成 \(1\),然后由期望的线性性可知可以直接加起来。
2.选到 \([i+1,n]\),概率为 \(\frac{n-i}{n}\),权值为 \(f_{i,n-1}\),即变为规模为 \(n-1\) 的子问题。
3.选到 \([1,i-1]\),概率为 \(\frac{i-1}{n}\),权值 \(f_{i-1,n-1}\),同理不难理解。
三种情况的加权和就是 \(f_{i,n}\) 的转移方程,复杂度 \(O(n^2+nq)\),能过 45pts。
感性理解一下,\(f_{i,n}\) 的值应该会与 \(n\) 无关,因为后面不可能影响前面。
下面严谨证明 \(f_{i,n}=f_{i,i}\),考虑数学归纳法,显然 \(f_{1,1}=f_{1,1}\)。
假设已知 \(\forall p\in [1,i],q\in [p,k],f_{i,k}=f_{i,i}\),有 \(f_{i,k+1}=\frac{\sum_{j=1}^{k} f_{j,k}+(n-i)f_{i,k}+(i-1)f_{i-1,k}}{n}=\frac{\sum_{j=1}^{k} f_{j,j}+(n-i)f_{i,i}+(i-1)f_{i-1,i-1}}{n}\)。
不难发现这和 \(f_{i,i}\) 的转移式是一样的,即 \(f_{i,k}=f_{i,i}\),得证。
然后就又可以把二维的数组压缩成一维了,显然可以前缀和优化,所以可以 \(O(n)\) 算出系数 \(c_i\) 的值。
询问直接暴力算,能过 65pts,特判特殊性质能有 75pts,也许加上 barret 约减和循环展开能过 Subtask 3。
然后考虑正解,我们需要维护的东西十分复杂,考虑分块,那么对一个询问 \(l,r\),块 \([L,R]\) 的贡献为 \(\sum_{i=L}^{R} a_i\times c_{i-l+1}\),设一个块内的 \(a\) 的子数组为 \(b_0,b_1,…,b_{len-1}\),有权值为 \(\sum_{i=0}^{len-1} b_i \times c_{i+(L-l)+1}\)。换元,令 \(d=L-l\),不妨称它为"偏移量"。那么,对于每个块,每个偏移量,我们要求出上述式子的和。
将 \(b_i\) 反转,那么式子变为 \(\sum_{i=0}^{len-1} b_{len-i-1}\times c_{i+d+1}\),注意到下标和相同,若 \(b_{len}\) 之后全部补上 0,那么这个式子就是多项式卷积的形式,可以使用一次 NTT 计算所有偏移量的权值和。
通过调整块长,复杂度最优为 \(O(n\sqrt{n\log n})\),实际由于 NTT 常数极大,所以块长要开到 3000 到 3500。
upd on 2026.8.31 注意到多项式 F 长为 \(O(\sqrt n)\),而 G 是一个定多项式,可以提前 NTT 转为点值表示,这样的复杂度为 \(O(n\sqrt n)\)。
标算取块长 3500 跑了 4.3s,本题可能轻微卡常,提供一个简单的优化:每次计算多项式 \(c\) 的时候,可以把大于右端点的项直接舍去,因为用不到。
DeepSeek 的代码只跑了 1.6s,块长取的是 3000,说明出题人的确是菜
两份代码都会给出,供大家参(ka)考(chang)。
code
出题人std:
#include <bits/stdc++.h>
//#define BIG
#define MID
#ifdef BIG
#define int __int128
#endif
#ifdef MID
#define int long long
#endif
using namespace std;
const int N=2e5+5,BL=3500,mod=998244353,_G=3,iG=332748118;
int n,q,a[N],c[N],L[N],R[N],blk[N],s[60][N];
namespace Poly{
const int M=4e5+5;
int A[M<<1],B[M<<1],tr[M<<1];
inline int qpow(int a,int b=mod-2,int p=mod){int res=1;for(;b;b>>=1,a=a*a%p)if(b&1)res=res*a%p;return res;}
inline void init(int n,int l){for(int i=0;i<n;i++)tr[i]=(tr[i>>1]>>1)|((i&1)<<(l-1));}
inline void atovo(int *a,int type,int n,int l){init(n,l);for(int i=0;i<n;i++)if(i<tr[i])swap(a[i],a[tr[i]]);for(int mid=1;mid<n;mid<<=1){int wn=qpow(type==1?_G:iG,(mod-1)/(mid<<1));for(int r=mid<<1,j=0;j<n;j+=r){int w=1;for(int k=0;k<mid;k++,w=w*wn%mod){int x=a[j|k],y=w*a[j|mid|k]%mod;a[j|k]=x+y>=mod?x+y-mod:x+y;a[j|mid|k]=x-y<0?x-y+mod:x-y;}}}}
inline void mul(int *a,int *b,int n){for(int i=0;i<=n;i++)a[i]=a[i]*b[i]%mod;}
#define poly vector<int>
inline poly operator *(const poly &a,const poly &b){int n=1,l=0;while(n<=a.size()+b.size()-1)n<<=1,l++;for(int i=0;i<a.size();i++)A[i]=a[i];for(int i=0;i<b.size();i++)B[i]=b[i];atovo(A,1,n,l),atovo(B,1,n,l),mul(A,B,n),atovo(A,-1,n,l);poly c;c.resize(a.size()+b.size()-1);int invn=qpow(n);for(int i=0;i<c.size();i++)c[i]=A[i]*invn%mod;for(int i=0;i<=n;i++)A[i]=0;for(int i=0;i<=n;i++)B[i]=0;return c;}
}using namespace Poly;
inline int read(){
int ret=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){ret=ret*10+c-'0';c=getchar();}
return ret*f;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline void writesp(int x){write(x),putchar(' ');}
inline void writeln(int x){write(x),putchar('\n');}
signed main(){
n=read(),q=read();
for(int i=1;i<=n;i++)a[i]=read();
c[1]=1;int sum=0;
for(int i=2;i<=n;i++)c[i]=(c[i-1]+(1+sum)*qpow(i))%mod,sum=(sum+c[i-1])%mod;
int num=n/BL;if(n%BL)num++;
for(int i=1;i<=num;i++)L[i]=(i-1)*BL+1,R[i]=i*BL;R[num]=n;
for(int i=1;i<=n;i++)blk[i]=(i-1)/BL+1;
for(int i=1;i<=num;i++){
poly F;for(int j=R[i];j>=L[i];j--)F.push_back(a[j]);
poly G;for(int j=0;j<=R[i];j++)G.push_back(c[j]);
F=F*G;for(int j=0;j<=L[i]-1;j++)s[i][j]=F[R[i]-L[i]+1+j];
}
while(q--){
int l=read(),r=read(),ans=0;
if(blk[l]==blk[r]){
for(int i=l;i<=r;i++)ans=(ans+a[i]*c[i-l+1])%mod;
writeln(ans);continue;
}
for(int i=l;i<=R[blk[l]];i++)ans=(ans+a[i]*c[i-l+1])%mod;
for(int i=L[blk[r]];i<=r;i++)ans=(ans+a[i]*c[i-l+1])%mod;
for(int i=blk[l]+1;i<=blk[r]-1;i++)ans=(ans+s[i][L[i]-l])%mod;
writeln(ans);
}
return 0;
}
DeepSeek
#include <bits/stdc++.h>
#include <unistd.h>
using namespace std;
using uint = unsigned int;
using ull = unsigned long long;
static constexpr uint MOD = 998244353u;
static constexpr uint ROOT = 3u;
struct FastScanner {
static const int BUFSIZE = 1 << 20;
int idx = 0, size = 0;
char buf[BUFSIZE];
inline char getChar() {
if (idx >= size) {
size = (int)fread(buf, 1, BUFSIZE, stdin);
idx = 0;
if (size == 0) return 0;
}
return buf[idx++];
}
template <class T>
bool readInt(T& out) {
char c = getChar();
if (!c) return false;
while (c != '-' && (c < '0' || c > '9')) { c = getChar(); if (!c) return false; }
T sign = 1;
if (c == '-') { sign = -1; c = getChar(); }
T num = 0;
while (c >= '0' && c <= '9') { num = num * 10 + (c - '0'); c = getChar(); }
out = num * sign;
return true;
}
};
struct FastPrinter {
static const int BUFSIZE = 1 << 20;
int idx = 0;
char buf[BUFSIZE];
~FastPrinter() { flush(); }
inline void pushChar(char c) { if (idx >= BUFSIZE) flush(); buf[idx++] = c; }
void flush() { if (idx) { fwrite(buf, 1, (size_t)idx, stdout); idx = 0; } }
void writeUInt(uint x) {
char tmp[12];
int len = 0;
if (x == 0) tmp[len++] = '0';
while (x) { tmp[len++] = char('0' + x % 10); x /= 10; }
while (len) pushChar(tmp[--len]);
pushChar('\n');
}
};
static inline uint powmod(uint a, ull e) {
uint r = 1;
while (e) {
if (e & 1) r = (uint)((ull)r * a % MOD);
a = (uint)((ull)a * a % MOD);
e >>= 1;
}
return r;
}
static void ntt(uint* a, int n, bool invert) {
for (int i = 1, j = 0; i < n; ++i) {
int bit = n >> 1;
for (; j & bit; bit >>= 1) j ^= bit;
j ^= bit;
if (i < j) swap(a[i], a[j]);
}
for (int len = 2; len <= n; len <<= 1) {
uint wlen = powmod(ROOT, (ull)(MOD - 1) / len);
if (invert) wlen = powmod(wlen, MOD - 2);
const int half = len >> 1;
for (int i = 0; i < n; i += len) {
uint w = 1;
uint* ai = a + i;
for (int j = 0; j < half; ++j) {
uint u = ai[j];
uint v = (uint)((ull)ai[j + half] * w % MOD);
uint x = u + v; if (x >= MOD) x -= MOD;
uint y = u - v; if (u < v) y += MOD;
ai[j] = x;
ai[j + half] = y;
w = (uint)((ull)w * wlen % MOD);
}
}
}
if (invert) {
uint invN = powmod((uint)n, MOD - 2);
for (int i = 0; i < n; ++i) a[i] = (uint)((ull)a[i] * invN % MOD);
}
}
int main(int argc, char** argv) {
static FastScanner fs;
static FastPrinter fp;
if (argc > 1) {
FILE* fin = fopen(argv[1], "rb");
if (fin) dup2(fileno(fin), fileno(stdin));
}
if (argc > 2) {
FILE* fout = fopen(argv[2], "wb");
if (fout) dup2(fileno(fout), fileno(stdout));
}
int n, q;
if (!fs.readInt(n)) return 0;
fs.readInt(q);
vector<uint> a(n);
for (int i = 0; i < n; ++i) {
int x; fs.readInt(x);
x %= (int)MOD; if (x < 0) x += (int)MOD;
a[i] = (uint)x;
}
reverse(a.begin(), a.end());
vector<uint> inv(n + 3);
inv[1] = 1;
for (int i = 2; i <= n + 2; ++i)
inv[i] = (uint)(MOD - (ull)(MOD / i) * inv[MOD % i] % MOD);
vector<uint> w(n);
w[0] = 1;
if (n >= 2) w[1] = (uint)(3ull * inv[2] % MOD);
for (int s = 1; s + 1 < n; ++s) {
ull term = ((ull)(2 * s + 3) * w[s] + MOD - (ull)s * w[s - 1] % MOD) % MOD;
w[s + 1] = (uint)(term * inv[s + 2] % MOD);
}
const int B = 3000;
const int nb = (n + B - 1) / B;
int N = 1;
while (N < n + B) N <<= 1;
vector<uint> Wf(N, 0);
for (int i = 0; i < n; ++i) Wf[i] = w[i];
ntt(Wf.data(), N, false);
vector<uint> PS((size_t)n * nb, 0);
vector<uint> A(N);
for (int b = 0; b < nb; ++b) {
const int L = b * B, R = min(n, L + B);
bool nonzero = false;
for (int i = L; i < R; ++i) if (a[i]) { nonzero = true; break; }
fill(A.begin(), A.end(), 0);
if (nonzero) {
for (int i = L; i < R; ++i) A[i - L] = a[i];
ntt(A.data(), N, false);
for (int i = 0; i < N; ++i) A[i] = (uint)((ull)A[i] * Wf[i] % MOD);
ntt(A.data(), N, true);
}
for (int r = 0; r < n; ++r) {
uint cur = 0;
if (r >= L) cur = A[r - L];
if (b) {
cur += PS[(size_t)r * nb + (b - 1)];
if (cur >= MOD) cur -= MOD;
}
PS[(size_t)r * nb + b] = cur;
}
}
while (q--) {
int l, r;
fs.readInt(l); fs.readInt(r);
--l; --r;
const int L = n - 1 - r;
const int R = n - 1 - l;
const int bl = L / B, br = R / B;
uint ans = 0;
auto addRange = [&](int lo, int hi) {
if (lo > hi) return;
ull s = 0;
int cnt = 0;
const uint* ap = a.data() + lo;
const uint* wp = w.data() + (R - lo);
for (int k = lo; k <= hi; ++k) {
s += (ull)(*ap++) * (*wp--);
if (++cnt == 8) { s %= MOD; cnt = 0; }
}
ans += (uint)(s % MOD);
if (ans >= MOD) ans -= MOD;
};
if (bl == br) {
addRange(L, R);
} else {
addRange(L, (bl + 1) * B - 1);
if (bl + 1 <= br - 1) {
const uint* row = PS.data() + (size_t)R * nb;
uint t = row[br - 1] >= row[bl] ? row[br - 1] - row[bl] : row[br - 1] + MOD - row[bl];
ans += t;
if (ans >= MOD) ans -= MOD;
}
addRange(br * B, R);
}
fp.writeUInt(ans);
}
return 0;
}
喵?
浙公网安备 33010602011771号