专栏文章

【抽象】2025 sm的NOIP模拟赛 第二篇

题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@min5njgv
此快照首次捕获于
2025/12/01 20:58
3 个月前
此快照最后确认于
2025/12/01 20:58
3 个月前
查看原文

R42 T1

题意

给定 m,k,t1,t2m,k,t_1,t_2,现在有一个人从 00 点出发,00 点每隔 kk 秒会刷新一个羊毛,当处于 00 点时,可以拿走目前 00 点刷新出的所有羊毛。当从一个铺了羊毛的位置到一个没有铺羊毛的位置,需要花费一个羊毛和 t1t_1 时间,否则需要消耗 t2t_2 时间。若没有羊毛铺路,则不能去往下一格没有羊毛的位置。
现在求对于 i=1,2,mi=1,2,\dots m 时,这个人把 1i1\sim i 的位置都铺了羊毛的最少时间。
1m1051\le m\le 10^5

思路

首先铺羊毛的形式一定时等一会羊毛,把手上的羊毛铺完后回去拿羊毛或者等一会羊毛,于是我们设 fif_i 表示铺完前 ii 个位置的最小时间,考虑枚举一个位置 jj 走回起点拿羊毛再铺到 ii,那么我们有转移式
fi=minj=0i1{max(fj+j×t2,i×k)+j×t2+(ij)×t1}f_i=\min_{j=0}^{i-1}\{\max(f_j+j\times t_2,i\times k)+j\times t_2 + (i-j)\times t_1\}
这是一个 O(m2)\Omicron(m^2) 的 dp,考虑优化。
先拆分项,把跟 ii 有关的弄到一起,跟 jj 有关的弄到一起,那么有
\max(f_j+j\times t_2,i\times k)+j\times t_2 + (i-j)\times t_1 &= \max(f_j+j\times t_2+j\times(t_2-t_1),i\times k+j\times(t_2-t_1))+i\times t_1 \end{aligned}$$ 然后由于 $f_i+j\times t_2$ 递增,于是我们可以考虑用指针去移动 $i\times k$ 能够覆盖的位置,不能覆盖到的用线段树处理即可,复杂度 $\Omicron(m\log m)$。 ## 代码 ```cpp #include<bits/stdc++.h> #define ll long long #define L (p<<1) #define R (p<<1|1) using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const ll N=1e5+10,inf=1e17; ll m,k,t1,t2; ll f[N]; vector<ll>v[N]; ll sum[N<<2]; void pushup(ll p){ sum[p]=min(sum[L],sum[R]); } void build(ll l,ll r,ll p){ sum[p]=inf; if(l==r) return ; ll mid=l+r>>1; build(l,mid,L),build(mid+1,r,R); return ; } void modify(ll l,ll r,ll x,ll z,ll p){ if(l==r){ sum[p]=z; return ; } ll mid=l+r>>1; if(mid>=x) modify(l,mid,x,z,L); else modify(mid+1,r,x,z,R); pushup(p); return ; } ll query(ll l,ll r,ll x,ll y,ll p){ if(x>y) return inf; if(x<=l&&r<=y) return sum[p]; ll mid=l+r>>1,res=inf; if(mid>=x) res=min(res,query(l,mid,x,y,L)); if(mid<y) res=min(res,query(mid+1,r,x,y,R)); return res; } void solve(){ read(m),read(k),read(t1),read(t2); build(0,m,1); f[0]=0; modify(0,m,0,0,1); v[1].push_back(0); ll pos=0; for(int i=1;i<=m;i++){ for(auto p : v[i]) pos=max(pos,p); f[i]=min(query(0,m,pos+1,i-1,1)+1ll*i*t1,1ll*i*k+min(0ll,pos*(t2-t1))+1ll*i*t1); ll s=(f[i]+1ll*i*t2-1)/k+1; if((f[i]+1ll*i*t2)%k==0&&t2<t1) s++; if(s<=m) v[s].push_back(i); if(s<=i) pos=max(pos,1ll*i); modify(0,m,i,f[i]+1ll*i*t2+1ll*i*(t2-t1),1); // cout<<s<<" "; } // cout<<"\n"; for(int i=1;i<=m;i++){ write(f[i]); if(i!=m) putchar(' '); } putchar('\n'); for(int i=0;i<=m;i++) v[i].clear(); } int main(){ // freopen("a2.in","r",stdin); // freopen("a.out","w",stdout); int T; read(T); while(T--) solve(); return 0; } /* 10 1 998244353 32449587 103432545 10 1000000000 10 9 10 1000000000 8 2 10 3 1000000000 1000000000 10 8 1000000000 1000000000 100 72 97 91 100 30 97 86 1000 344350423 343943497 36846398 1000 182539971 663315063 130231850 100000 431722183 935312919 310876391 */ ``` # R42 T2 [P14461 【MX-S10-T2】『FeOI-4』青年晚报](https://www.luogu.com.cn/problem/P14461) ## 题意 给定两个 $m$ 次多项式 $F_0(x),G_0(x)$,并有递推式
F_i(x)=G_{i-1}(x)+G'{i-1}(x)\ G_i(x)=F{i-1}(x)-F'_{i-1}(x)\
给定 $n$,求 $F_n(x)$ 和 $G_n(x)$ 的各项系数。 $1\le m\le 5\times 10^3,1\le n\le 10^9$。 ### 思路1 以 $F$ 为例,我们设 $f_{i,j}$ 表示 $F_i(x)$ 的 $j$ 次项系数,$g_{i,j}$ 同理,根据递推式,那么我们有 $$\begin{aligned} f_{i,j} &= g_{i-1,j}+g_{i-1,j+1}\times(j+1)\\ &= f_{i-2,j}-f_{i-2,j+1}\times(j+1)+(f_{i-2,j+1}+f_{i-2,j+2}\times(j+2))\times (j+1)\\ &= f_{i-2,j}-f_{i-2,j+2}\times(j+1)\times (j+2) \end{aligned}$$ 在多项式意义下,即
F_i(x)=F_{i-2}(x)-F_{i-2}''(x)
同理,我们推导可得 同理,我们推导可得
G_i(x)=G_{i-2}(x)-G_{i-2}''(x)
因此我们只要会算 $F$ 就会算 $G$,考虑怎么算。 为了方便,我们强制 $n$ 为偶数,如果是奇数只要做一次递推即可。 我们尝试计算 $F_4(x)$。 $$\begin{aligned} F_4(x) &= F_2(x)-F_2(x)''\\ &= (F_0(x)-F_0(x)'')-(F_0(x)-F_0(x)'')''\\ &= F_0(x)-2F_0(x)''+F_0(x)'''' \end{aligned}$$ 似乎有点规律。我们再尝试计算 $F_6$。 $$\begin{aligned} F_6(x) &= F_4(x)-F_4(x)''\\ &= (F_0(x)-2F_0(x)''+F_0(x)'''')-(F_0(x)-2F_0(x)''+F_0(x)'''')''\\ &= F_0(x)-3F_0(x)''+3F_0(x)''''-F_0(x)'''''' \end{aligned}$$ 观察可得公式
F_{2n}(x)=\sum_{i=0}^{n}(-1)^i\times C_{n}^{i}\times F_0(x)^{(2i)}
然后注意到 $m$ 次多项式在导最多 $m+1$ 次后就会变成 $0$,于是我们有
F_{2n}(x)=\sum_{i=0}^{\min(n,\lfloor\frac{m+1}{2}\rfloor)}(-1)^i\times C_{n}^{i}\times F_0(x)^{(2i)}
然后直接做就行了,复杂度 $\Omicron(m^2)$。 ### 代码 ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const ll N=5e3+10,mod=1e9+7; ll m,n; struct Dx{ ll x[N]; Dx friend operator ~ (Dx a){ for(int i=0;i<m;i++) a.x[i]=1ll*a.x[i+1]*(i+1)%mod; a.x[m]=0; return a; } Dx friend operator - (Dx a,Dx b){ for(int i=0;i<=m;i++) a.x[i]=(a.x[i]-b.x[i]+mod)%mod; return a; } Dx friend operator + (Dx a,Dx b){ for(int i=0;i<=m;i++) a.x[i]=(a.x[i]+b.x[i]+mod)%mod; return a; } Dx friend operator * (Dx a,ll b){ for(int i=0;i<=m;i++) a.x[i]=(a.x[i]*b%mod+mod)%mod; return a; } }F,G,ansF,ansG; ll inv[N]; int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); read(n),read(m); for(int i=0;i<=m;i++) read(F.x[i]); for(int i=0;i<=m;i++) read(G.x[i]); inv[0]=inv[1]=1; for(int i=2;i<=m;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod; ll sum=1,tag=1; if(n&1){ Dx nF=G+(~G),nG=F-(~F); n--; F=nF,G=nG; } for(int i=0;i<=min(n/2,(m+1)/2);i++){ ansF=ansF+((F*sum)*tag); ansG=ansG+((G*sum)*tag); tag=-tag; F=(~F),F=(~F),G=(~G),G=(~G); sum=sum*(n/2-i)%mod*inv[i+1]%mod; } for(int i=0;i<=m;i++){ write(ansF.x[i]); if(i!=m) putchar(' '); } putchar('\n'); for(int i=0;i<=m;i++){ write(ansG.x[i]); if(i!=m) putchar(' '); } putchar('\n'); return 0; } /* */ ``` ### 思路2(口胡) 注意到对于系数的转移的式子是杨辉三角的形式的,直接做即可。 # R42 T3 [P14462 【MX-S10-T3】『FeOI-4』寻宝游戏](https://www.luogu.com.cn/problem/P14462) 神秘corner case,使大运创思我。 ## 题意 有 $n+10^6$ 个桶,前 $n$ 个桶中第 $i$ 个桶有 $a_i$ 袋泡面,现在给定 $q$ 此询问,每次询问给定一个区间 $[l,r]$,求在只保留区间 $[l,r]$ 内的桶和泡面以及 $[n+1,n+10^6]$ 的空桶的情况下是否能够将所有泡面移动到一个桶中。如果可以,输出最少的移动次数,否则输出 $-1$。 一次合法的移动如下: - 选择互不相同的 $i,j,k$,并且满足桶 $i,j$ 中至少有一袋泡面,在 $i,j$ 中各取一袋泡面,都放入桶 $k$ 中。 $1\le n,q\le 3\times 10^5$。 ## 思路 先考虑 $r-l+1\ge 3$ 的情况。 贪心地想,我们最后将所有泡面放在最大值桶中总是最优的,但是由于奇偶性问题,我们还需要比较放在严格次大值的桶的情况。 我们把这个确定的最后放置泡面的桶称为万能桶(最大值或严格次大值)。考虑如果我们确定最后放在万能桶,其他桶的泡面应该怎么移动。 我们设剩下的桶为 $a_1,a_2…a_k$,并且有 $a_1\ge a_2\dots\ge a_k$,考虑分类讨论。 - $a_1\le \sum_{i=2}^{k} a_i$。这个时候我们考虑每次移动让 $a_1\sim a_k$ 中的最大值和次大值匹配,并且移动到万能桶中。那么对于 $2\mid (\sum_{i=1}^{k} a_i)$ 的情况,显然最小移动次数为 $\frac{\sum_{i=1}^{k}a_i}{2}$。否则我们考虑从万能桶中拿一个出来和任意一个剩余桶匹配,那么和变为偶数,同时 $\sum_{i=1}^{k} a_i$ 增加 $1$,因此此时的移动次数为 $\frac{(\sum_{i=1}^{k} a_i)+1}{2}+1$。整合一下两种情况,那么答案就是 $\lfloor\frac{(\sum_{i=1}^{k}a_i)+1}{2}\rfloor + (\sum_{i=1}^{k} a_i)\bmod 2$。 - $a_1\gt \sum_{i=2}^{k}a_i$。这个时候我们考虑让 $a_1$ 去匹配后面的,并且将这些泡面放到 $a_{n+1}$ 中。那么显然此时 $(\sum_{i=1}^{k}a_i)+a_{n+1}$ 总和不变,并且每次我们使 $a_i$ 减 $1$,$(\sum_{i=1}^{k}a_i)+a_{n+1}$ 加 $1$。设 $S=\sum_{i=2}^{k} a_i$。若本身 $\sum_{i=1}^{k} a_i$ 就是偶数,那么通过 $\frac{a_1-T}{2}$ 步使得 $a_1=(\sum_{i=2}^{k}a_i)+a_{n+1}$ 后,再用 $\frac{a_1+T}{2}$ 步按照情况 $1$ 移动,显然是最终需要 $a_1$ 步。如果 $\sum_{i=1}^{k} a_i$ 是奇数,那么可以在一开始就从万能桶中拿一个出来和 $a_1$ 匹配放到 $a_{n+1}$ 中,这样一次性让 $a_1$ 减 $1$,$a_{n+1}$ 加 $2$,最终的步数也是 $a_1$。特别地,若最初时就有 $a_1=(\sum_{i=2}^{k}a_i)+1$,那么移动步数为 $a_1+1$。 整理一下,我们的移动步数就是 $\max(a_1,\lfloor\frac{(\sum_{i=1}^{k}a_i)+1}{2}\rfloor+(\sum_{i=1}^{k}a_i)\bmod 2)$。 更特别地,若 $\sum_{i=l}^{r}([a_i=1])=r-l+1$,只需要 $\lfloor\frac{r-l+1}{2}\rfloor$ 次即可。具体地,区间长度为奇数时把泡面匹配到区间内一个桶中,偶数时把所有泡面都匹配到一个空桶中。 接下来考虑 $r-l+1\le 2$ 的情况。 若 $r-l+1=1$,此时不用移动,答案为 $0$。 若 $r-l+1=2$,我们设桶 $l$ 值为 $x$,桶 $r$ 值为 $y$,并且默认 $x\le y$: - $x=1,y=1$,此时直接匹配放到一个空桶中即可,答案为 $1$。 - $x=1,y=2$,此时如何移动东无法完成,答案为 $-1$。 - $y\ge x>1$,此时我们考虑使用一次移动分别从 $x,y$ 中取一个放到空桶中,变为 $x-1,y-1,2$,这个时候就可以按照 $r-l+1\ge 3$ 的情况做了。 - $x=1,y\gt 2$,此时我们考虑两个桶分别取一个放到一个空桶中,变为 $y-1,2$,由于 $y\gt 2$,减 $1$,后仍有 $y\gt 1$,因此可以按 $y\ge x\gt 1$ 的情况做。 ## 代码 ```cpp #pragma GCC optimize(1,2,3,"Ofast","inline") #include<bits/stdc++.h> #define ll long long using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const ll N=3e5+10,inf=1e16; struct tree{ ll ma,se,sum,cnt; bool id1; }tr[N<<2]; ll n,q,a[N]; #define L (p<<1) #define R (p<<1|1) void pushup(ll p){ if(tr[L].ma>tr[R].ma){ tr[p].ma=tr[L].ma; tr[p].cnt=tr[L].cnt; if(tr[R].ma!=tr[p].ma) tr[p].se=max(tr[L].se,tr[R].ma); else tr[p].se=max(tr[L].se,tr[R].se); } else if(tr[L].ma<tr[R].ma){ tr[p].ma=tr[R].ma; tr[p].cnt=tr[R].cnt; if(tr[L].ma!=tr[p].ma) tr[p].se=max(tr[R].se,tr[L].ma); else tr[p].se=max(tr[R].se,tr[L].se); } else{ tr[p].ma=tr[L].ma; tr[p].cnt=tr[L].cnt+tr[R].cnt; tr[p].se=max(tr[L].se,tr[R].se); } tr[p].id1=(tr[L].id1&tr[R].id1); tr[p].sum=tr[L].sum+tr[R].sum; } void build(ll l,ll r,ll p){ tr[p]={-inf,-inf,0,0,0}; if(l==r) return ; ll mid=l+r>>1; build(l,mid,L),build(mid+1,r,R); return ; } void add(ll l,ll r,ll x,ll p){ if(l==r) return (void)(tr[p]={a[x],-inf,a[x],1,(a[x]==1)}); ll mid=l+r>>1; if(mid>=x) add(l,mid,x,L); else add(mid+1,r,x,R); pushup(p); return ; } struct g{ ll ma,se,cnt; }; void chan(g &a,g b){ if(a.ma<b.ma) swap(a,b); if(a.ma>b.ma) a.se=max(a.se,b.ma); else if(a.ma==b.ma){ a.cnt+=b.cnt; a.se=max(a.se,b.se); } } g query(ll l,ll r,ll x,ll y,ll p){ if(x<=l&&r<=y) return {tr[p].ma,tr[p].se,tr[p].cnt}; ll mid=l+r>>1; g res={-inf,-inf,0}; if(mid>=x) chan(res,query(l,mid,x,y,L)); if(mid<y) chan(res,query(mid+1,r,x,y,R)); return res; } bool all_1(ll l,ll r,ll x,ll y,ll p){ if(x<=l&&r<=y) return tr[p].id1; ll mid=l+r>>1; bool res=1; if(mid>=x) res&=all_1(l,mid,x,y,L); if(mid<y) res&=all_1(mid+1,r,x,y,R); return res; } ll ges(ll l,ll r,ll x,ll y,ll p){ if(x<=l&&r<=y) return tr[p].sum; ll mid=l+r>>1; ll res=0; if(mid>=x) res+=ges(l,mid,x,y,L); if(mid<y) res+=ges(mid+1,r,x,y,R); return res; } ll calc(ll s1,ll s2,ll s3){ ll sum=s1+s2+s3; ll ma=max(s1,(max(s2,s3))); ll se=max(s1*(s1!=ma),max(s2*(s2!=ma),s3*(s3!=ma))); if(s1==s2&&s2==s3) se=ma; ll cnt=(s1==ma)+(s2==ma)+(s3==ma); ll t1=max(cnt>1 ? ma : se,(sum-ma+1>>1)+(sum-ma&1)); ll t2=max(ma,(sum-se+1>>1)+(sum-se&1)); return min(t1,t2); } void solve(){ read(n),read(q); build(1,n,1); for(int i=1;i<=n;i++) read(a[i]),add(1,n,i,1); while(q--){ ll l,r; read(l),read(r); if(r-l+1>=3){ if(all_1(1,n,l,r,1)) write((r-l+1)>>1); else{ g t=query(1,n,l,r,1); ll sum=ges(1,n,l,r,1); if(t.se==-inf) t.se=t.ma; ll t1=max(t.cnt>1 ? t.ma : t.se,(sum-t.ma+1>>1)+(sum-t.ma&1)); ll t2=max(t.ma,(sum-t.se+1>>1)+(sum-t.se&1)); write(min(t1,t2)); // cout<<"\n"<<t.ma<<" "<<t.se<<" "<<t.cnt<<"\n---\n"; } } else if(r-l+1==1) write(0); else{ if(a[l]>a[r]) swap(l,r); if(a[l]==1&&a[r]==1) write(1); else if(a[l]==1&&a[r]==2) write(-1); else if(a[l]==1&&a[r]>2) write(calc(a[r]-2,1,2)+2); else if(a[l]>1) write(calc(a[l]-1,a[r]-1,2)+1); } putchar('\n'); } } int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); int T; read(T); while(T--) solve(); return 0; } /* 1 4 1 1 2 5 5 1 4 */ ``` # R43 T1 [CF1503E 2-Coloring](https://www.luogu.com.cn/problem/CF1503E) 很高的质量,但是T1给紫。 ## 题意 现在往一个 $n$ 行 $m$ 列的网格中放棋子,要求: - 每行放了棋子的格子必须为一个区间,且不允许不放。 - 每列的空格必须为一个区间,且不允许放满。 求放置方案数。 $1\le n,m\le 2021$。 ## 思路 首先手模或观察可得,最终的网格大致时上面下面各一个山峰,并且只有可能有两种形式: - 第一种,如下图,这种情况是下面的峰顶和上面的峰顶位于同一条线上,并且两者不会在峰顶相连。 ![图炸了](https://cdn.luogu.com.cn/upload/image_hosting/w4ksrzuz.png) 为了方便,我们即 $t_{i,j}$ 表示长度为 $i$ 且严格不降的最大值 $\le j$ 的序列数。 在这种情况下,我们考虑枚举上峰顶和下峰顶靠近中间的位置 $l,r$,以及这条重合线的高度,那么这种情况的方案数我们有
2\sum_{i=1}^{n-1}\sum_{l=1}^{m}\sum_{r=l+1}^{m} t_{l-1,i}\times t_{m-l,i-1}\times t_{r-1,i-1}\times t_{m-r,i}
在这里我们默认让下峰顶在上峰顶右边,由于是对称的,所以最后方案数会乘 $2$。 - 第二种,如下图,这种情况不像第一种有明确的“线”,而是上峰顶和下峰顶侧面有交。 ![图炸了](https://cdn.luogu.com.cn/upload/image_hosting/b5lfwijb.png) 类似于第一种,我们考虑枚举中间的线的位置以及上峰顶高度和下峰顶高度,那么这种情况的方案数为
2\sum_{i=1}^{m-1}\sum_{h1=2}^{n-1}\sum_{h2=n-h1+1}^{n-1} (t_{i,h1}-t_{i,h1-1})\times t_{m-i,n-h2-1}\times(t_{m-i,h2}-t_{m-i,h2-1})\times t_{i,n-h1-1}
这两个式子显然是可以前缀和优化到 $\Omicron(n^2)$ 的,预处理一部分求和即可。 Ps:笔者的代码在 $n\gt m$ 时会出错,至今不知道为什么。 ## 代码 ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const ll N=3e3+10,mod=998244353; ll n,m; ll t[N][N],f[N][N],g[N][N]; int main(){ freopen("magic.in","r",stdin); freopen("magic.out","w",stdout); read(n),read(m); if(n>m) swap(n,m); for(int i=0;i<=m;i++) t[0][i]=1; for(int i=1;i<=m;i++){ for(int j=0;j<=n;j++) t[i][j]=t[i-1][j]; for(int j=1;j<=n;j++) t[i][j]=(t[i][j]+t[i][j-1])%mod; } ll ans=0; for(int i=1;i<n;i++){ for(int j=1;j<=m;j++){ f[i][j]=(f[i][j-1]+t[j-1][i]*t[m-j][i-1]%mod)%mod; } } for(int i=1;i<n;i++){ for(int r=2;r<=m;r++){ // ans=(ans+t[l-1][i]*t[m-l][i-1]%mod*t[r-1][n-i-1]%mod*t[m-r][n-i]%mod)%mod; ans=(ans+f[i][r-1]%mod*t[r-1][n-i-1]%mod*t[m-r][n-i]%mod)%mod; } } for(int mid=1;mid<m;mid++){ for(int h2=1;h2<n;h2++){ g[mid][h2]=(g[mid][h2-1]+(t[m-mid][h2]-t[m-mid][h2-1]+mod)%mod*t[m-mid][n-h2-1]%mod)%mod; } } for(int mid=1;mid<m;mid++){ for(int h1=2;h1<n;h1++){ // ans=(ans+(t[mid][h1]-t[mid][h1-1]+mod)%mod*t[mid][n-h1-1]%mod*(t[m-mid][h2]-t[m-mid][h2-1]+mod)%mod*t[m-mid][n-h2-1]%mod)%mod; ans=(ans+(t[mid][h1]-t[mid][h1-1]+mod)%mod*t[mid][n-h1-1]%mod*(g[mid][n-1]-g[mid][n-h1]+mod)%mod)%mod; } } ans=(ans*2ll)%mod; write((ans+mod)%mod); return 0; } /* 行只能选择一个区间放 列只能放一段前缀和后缀 17 13 575053520 737338284 */ ``` # R43 T3 这题质量一般。 ## 题意 有一个 $n$ 行 $m$ 列的矩阵,并给出 $c$ 次矩阵加的操作。给定一个常数 $k$,每一行中数值 $\ge k$ 的格子为黑格,否则为白格。 现在给定 $q$ 次询问,每次给定 $x,y$,表示要选中 $x$ 行,每行选取 $y$ 个格子,设有一行选的 $y$ 个格子中有 $a$ 个白格,$b$ 个黑格,那么这一行的价值为 $a\times b$。求每次询问的所有选取方案的最大价值。 $1\le n,m,c\le3\times 10^5,1\le k\le 10,1\le q\le 10^6$。 ## 思路 缝合题。 注意到 $k$ 非常小,我们考虑线段树上每个区间维护区间内的严格前 $k$ 小值以及这些值每个值的个数,合并时归并合并即可。 然后矩形加的操作可以离线下来,转换为经典的扫描线。 那么对于一行的黑格和白格个数,我们在扫描线的过程就能够求出来。分别设为 $b_i$ 和 $w_i$,并设 $sum_i=\min(b_i,w_i)$。 考虑查询。贪心地想,一行的价值是 $\min(\lfloor\frac{y}{2}\rfloor,sum_i)\times(y-\min(\lfloor\frac{y}{2}\rfloor,sum_i))$,显然 $sum_i$ 越大越好,于是我们可以初始时考虑将所有 $sum_i$ 降序排序,查询时二分找到最后一个 $\ge \lfloor\frac{y}{2}\rfloor$ 的位置,分类讨论一下和 $x$ 的大小后求值即可。 注意二分边界。 复杂度 $\Omicron(n\log n+q\log n)$。 ## 代码 ```cpp #pragma GCC optimize(1,2,3,"Ofast","inline") #include<bits/stdc++.h> using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const int N=3e5+10,K=12,inf=2e9+10; int n,m,c,q,k; #define min(a,b) (a<b?a:b) #define pii pair<int,int> #define fi first #define se second struct SGT{ struct node{ int tag; pii c[K]; void operator += (int s){ for(int i=0;i<k&&c[i].fi<inf;++i) c[i].fi+=s; } }tr[N<<2]; #define L (p<<1) #define R (p<<1|1) void pushup(int p){ int i=0,j=0,pos=0; while(pos<k&&(i<k||j<k)&&(tr[L].c[i].fi<=c||tr[R].c[j].fi<=c)){ if(tr[L].c[i].fi<tr[R].c[j].fi){ tr[p].c[pos]=tr[L].c[i]; ++pos,++i; } else if(tr[L].c[i].fi>tr[R].c[j].fi){ tr[p].c[pos]=tr[R].c[j]; ++pos,++j; } else{ tr[p].c[pos]={tr[L].c[i].fi,tr[L].c[i].se+tr[R].c[j].se}; ++pos,++i,++j; } } while(pos<k) tr[p].c[pos]={inf,0},pos++; } void pushdown(int p){ tr[L]+=tr[p].tag,tr[R]+=tr[p].tag; tr[L].tag+=tr[p].tag,tr[R].tag+=tr[p].tag; tr[p].tag=0; } void build(int l,int r,int p){ tr[p].tag=0,tr[p].c[0]={0,r-l+1}; for(int i=1;i<=k;++i) tr[p].c[i]={inf,0}; if(l==r) return ; int mid=l+r>>1; build(l,mid,L),build(mid+1,r,R); return ; } void add(int l,int r,int x,int y,int z,int p){ if(x<=l&&r<=y){ tr[p].tag+=z; return (tr[p]+=z); } if(tr[p].tag) pushdown(p); int mid=l+r>>1; if(mid>=x) add(l,mid,x,y,z,L); if(mid<y) add(mid+1,r,x,y,z,R); pushup(p); return ; } }T; int x,y,xx,yy,xt,yt,lb,rb; struct line{ int l,r,id; }; vector<line>v[N]; int sum[N]; typedef long long ll; ll pre[N],pre1[N]; bool cmp(int a,int b){ return a>b; } signed main(){ freopen("army.in","r",stdin); freopen("army.out","w",stdout); read(n),read(m),read(c),read(k),read(q); for(int i=1;i<=c;++i){ read(x),read(y),read(xx),read(yy); v[x].push_back({y,yy,1}); if(xx+1<=n) v[xx+1].push_back({y,yy,-1}); } T.build(1,m,1); for(int i=1;i<=n;++i){ for(line p : v[i]) T.add(1,m,p.l,p.r,p.id,1); int t=0,j=0; while(j<k&&T.tr[1].c[j].fi<k){ t+=T.tr[1].c[j].se; ++j; } sum[i]=min(t,m-t); // for(int j=0;j<k;j++){ // cout<<T.tr[1].c[j].fi<<" "<<T.tr[1].c[j].se<<"\n"; // } // cout<<"\n"; } // for(int i=1;i<=n;++i) cout<<sum[i]<<" "; // cout<<"\n"; sort(sum+1,sum+n+1,cmp); for(int i=1;i<=n;++i) pre[i]=pre[i-1]+(1ll*sum[i]*sum[i]),pre1[i]=pre1[i-1]+1ll*sum[i]; // cout<<'\n'; while(q--){ read(xt),read(yt); int yg=yt/2; if(sum[xt]>=yg) write(1ll*xt*(yg*(yt-yg))); else{ lb=-1,rb=n+1; int kt=0; while(lb<=rb){ int md=lb+rb>>1; if(sum[md]>=yg) lb=md+1,kt=md; else rb=md-1; } write(1ll*kt*(1ll*yg*(1ll*yt-1ll*yg))+1ll*(pre1[xt]-pre1[kt])*yt-(pre[xt]-pre[kt])); } putchar('\n'); } return 0; } /* 10 10 5 2 1 1 1 6 8 6 5 6 5 4 4 5 8 4 1 5 2 4 1 6 6 6 6 ans: 24 expected: 25 */ ``` # R44 T3 妙妙~~暴力~~题。 ## 题意 给定一个长度为 $n$ 的字符串 $T$,以及 $x,k$。字符串只由 ```?,N,O,I,P``` 组成,```?``` 表示这个位置还没有确定,可以放另外的四个字母之一。 给定一个 $4$ 行 $4$ 的表格 $a$,$a_{i,j}$ 表示相邻两个字符 $s_x,s_{x+1}$ 满足 $s_i$ 为 ```"NOIP"``` 里的第 $i$ 个字母,$s_{i+1}$ 为第 $j$ 个字母时的权值。一个字符串 $S$ 的权值定义为所有相邻字符的权值之和。 求所有的在 $T$ 基础上的合法的权值 $\ge x$ 的字符串按字典序降序排序后的第 $k$ 个字符串。如果不存在,输出 $-1$。 $1\le n,k\le10^3$。 ## 思路 首先我们明显有一个暴力:直接按规则暴力搜索每一个位置并安排字母,如果其权值 $\ge x$ 就加入备选方案,最后把所有方案进行一个字典序降序排序,第 $k$ 个方案就是答案。 这样的复杂度~~hdu机子都跑不动~~包炸的,我们考虑剪一点枝。 首先,贪心地想,既然最后要求的是字典序降序排序后的第 $k$ 大,我们不妨改一下搜索顺序,每次的搜索按四个字母的字典序降序来枚举放置,那么我们枚举的方案就是字典序降序的。我们记录一个合法方案数 $cnt$,那么只要有一个方案权值 $\ge k$,我们就令 $cnt\gets cnt+1$。如果 $cnt=k$,那么此时搜索到的方案就是答案,可以直接结束搜索。 但是这个东西依旧过不了,而它的瓶颈在于我们会搜索一些很显然没有可能合法的方案,于是我们考虑把这些搜索也剪掉。 我们记 $f_{i,j}$ 表示后缀 $[i,n]$ 中在第 $i$ 个位置放置第 $j$ 个字母能够获得的最大权值,显然我们有
f_{i,j}=\max_{g=1}^{4}{f_{i+1,g}+a_{j,g}}
于是,我们在搜索时记录一个到了当前位置 $pos$ 时已经拥有的权值 $val$,那么能够在位置 $pos$ 放置第 $j$ 个字母并且继续搜索 $pos+1$ 的必要条件就是 $val+f_{pos,j}\ge x$。如果不满足,我们就不进行这一次的搜索。 再加上一开始的贪心之后,我们就可以做到 $\Omicron(nk)$ 的复杂度,可以通过。 ## 复杂度、正确性证明 首先方案合法性显然,其次由于本身搜索方案就是按字典序降序来搜索的,因此方案的第 $k$ 大性也满足。 对于复杂度,首先根据贪心以及取方案的正确性,那么我们刚好会取到前 $k$ 个合法方案,并且取一次的复杂度是 $\Omicron(n)$ 的,因此取方案的复杂度是 $\Omicron(nk)$ 的。 那么还有可能影响复杂度的因素就只剩搜索中途的一种情况:有可能搜索到半路就无法继续往下搜索,然后只能回退,也就是搜索到了一些无用方案。 考虑反证法。假设目前的这个位置为无用方案,这意味着这个位置放任意字母都不满足 $val+f_{pos,j}\ge k$。既然这条搜索方案的已经被堵死了,那么显然我们会在更早的位置就直接终止了对这条方案道路的搜索,也就是所有不合法方案都会第一时间被终止,并不会产生搜索到半路没有造成贡献就回退的情况,因此复杂度是正确的。 ## 代码 ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const ll N=1e3+10,inf=1e15; char s[N]; ll a[6][6]; ll n,x,K; ll base[260]; struct matrix{ ll c[4][4]; void init(){ for(int i=0;i<4;i++){ for(int j=0;j<4;j++) c[i][j]=-inf; } } ll query(ll row){ ll res=-inf; for(int i=0;i<4;i++) res=max(res,c[row][i]); return res; } }t[N],f[N]; matrix operator * (const matrix &a,const matrix &b){ matrix c; c.init(); for(int i=0;i<4;i++){ for(int k=0;k<4;k++){ for(int j=0;j<4;j++){ c.c[i][j]=max(c.c[i][j],a.c[i][k]+b.c[k][j]); } } } return c; } ll cnt; ll lu[N],z[N]; ll ord[4]={3,1,0,2}; char ji[4]={'N','O','I','P'}; bool fl=0; void dfs(ll pos,ll sum,ll lst){ if(fl) return ; if(pos>n){ if(sum>=x){ cnt++; if(cnt==K){ for(int i=1;i<=n;i++) z[i]=lu[i]; fl=1; } } return ; } if(s[pos]=='?'){ for(int i=0;i<4;i++){ ll val=sum+f[pos].query(ord[i])+(lst==-1 ? 0 : a[lst][ord[i]]); if(!fl&&val>=x){ lu[pos]=ord[i]; dfs(pos+1,sum+(lst==-1 ? 0 : a[lst][ord[i]]),ord[i]); } } } else{ ll val=sum+f[pos].query(base[s[pos]])+(lst==-1 ? 0 : a[lst][base[s[pos]]]); if(!fl&&val>=x){ lu[pos]=base[s[pos]]; dfs(pos+1,sum+(lst==-1 ? 0 : a[lst][base[s[pos]]]),base[s[pos]]); } } } void print(matrix a){ for(int i=0;i<4;i++){ for(int j=0;j<4;j++) write(a.c[i][j]),putchar(' '); putchar('\n'); } } int main(){ freopen("perm.in","r",stdin); freopen("perm.out","w",stdout); read(n),read(x),read(K); scanf("%s",s+1); base['N']=0,base['O']=1,base['I']=2,base['P']=3; for(int i=0;i<4;i++){ for(int j=0;j<4;j++) read(a[i][j]); } for(int i=1;i<n;i++){ t[i].init(); if(s[i]=='?'){ for(int j=0;j<4;j++){ for(int k=0;k<4;k++){ t[i].c[j][k]=a[j][k]; } } } else{ for(int j=0;j<4;j++) t[i].c[base[s[i]]][j]=a[base[s[i]]][j]; } } t[n].init(); if(s[n]=='?'){ for(int i=0;i<4;i++){ for(int j=0;j<4;j++) t[n].c[i][j]=0; } } else{ for(int i=0;i<4;i++) t[n].c[base[s[n]]][i]=0; } f[n]=t[n]; for(int i=n-1;i>=1;i--) f[i]=t[i]*f[i+1]; // for(int i=1;i<=n;i++){ // print(f[i]); // putchar('\n'); // } dfs(1,0,-1); if(!fl) write(-1); else for(int i=1;i<=n;i++) putchar(ji[z[i]]); return 0; } /* 考虑维护矩阵乘 + 贪心 + 暴力搜索 如果说我退回了一个位置,说明这个位置放当前字母的情况已经枚举完了 */ ``` # R45 T4 [CF917D Stranger Trees](https://www.luogu.com.cn/problem/CF917D) ## 题意 给定一棵 $n$ 个点的无根树,求当 $x=0,1,\dots,n-1$ 时,恰好有 $x$ 条边和原树重合的 $n$ 个点的生成树有多少。 $1\le n\le 8000$。 ## 思路 遇到这种有”恰好“的题目,考虑转换为“至少”。 若有一个 $n$ 个点,$k$ 个连通块的图,则连通的方案数为
n^{k-1}\prod_{i=1}^{k}s_i
其中 $s_i$ 为第 $i$ 个连通块大小。 那么首先我们记 $f_S$ 表示边集恰好为 $S$ 的方案数,$g_S$ 表示笔记至少包含 $S$ 的方案数,那么我们有
g_S=\sum_{S\in T} f_T
反向考虑 $g$ 对 $f$ 的贡献,类似于容斥,于是我们就有了一个 $\Omicron(2^nn)$ 的暴力: 设 $f_i$ 为恰好重合 $i$ 条边的方案数,$g_i$ 为至少 $i$ 条边的方案数。 暴力枚举重合的是哪些边,算出所有的 $g_i$,然后我们就有
f_i=\sum_{j=i}^{n-1} (-1)^{j-i}\times C_{j}^{j-1}\times g_i
但是这样显然过不了,于是我们考虑是否能够快速求出 $g$。 显然我们有一个简单的 $\Omicron(n^3)$ dp:设 $f_{i,j,k}$ 表示以 $i$ 为根子树数内已经有 $j$ 个连通块并且 $i$ 所处连通块大小为 $k$,转移的时候每枚举一个儿子 $v$ 就进行更新转移即可。转移时枚举当前点与儿子的 $j,k$。复杂度根据树上背包的计算是 $\Omicron(n^3)$。 然后优化计算与状态:设 $f_{i,j,0/1}$ 表示以 $i$ 为根的子树内已经有 $j$ 个连通块且 $i$ 所处的联通块中是否已经有**一个**点被选择出来。转移时考虑当前点到儿子的边是否选择,我们有
f_{u.x,0}\times f_{v,y,1}\to f'{u,x+y,0}\ f{u,x,1}\times f_{v,y,1}\to f'{u,x+y,1}\ f{u,x,0}\times f_{v,y,0}\to f'{u,x+y-1,0}\ f{u,x,0}\times f_{v,y,1}\to f'{u,x+y-1,1}\ f{u,x,1}\times f_{v,y,0}\to f'_{u,x+y-1,1}
依旧树上背包分析,复杂度 $\Omicron(n^2)$。 然后在计算时,$g_i$ 实际上就等于 $f_{1,n-i,1}\times n^{n-j-2}$,这根据 prufer 序列可得(实际上就是 $n-j-2$ 个点的完全图的生成树个数)。 计算也是 $\Omicron(n^2)$ 的。 ## 代码 ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const int N=8e3+5,mod=1e9+7; bool b1; int n,m; int head[N],id; struct edge{ int v,next; }e[N<<1]; void add(int u,int v){ e[++id]=(edge){v,head[u]},head[u]=id; } vector<int>f[N],g[N];//没选,选 int ff[N],gg[N]; int siz[N]; int fac[N],inv[N]; void init(){ fac[0]=fac[1]=inv[0]=inv[1]=1; for(int i=2;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod; for(int i=2;i<=n;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod; } int C(int x,int y){ if(x<y||x<0||y<0) return 0; return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod; } void dfs(int u,int fa){ siz[u]=1; f[u].resize(4),g[u].resize(4); f[u][1]=1,g[u][1]=1; for(int i=head[u];i;i=e[i].next){ int v=e[i].v; if(v==fa) continue; dfs(v,u); for(int j=1;j<=siz[u];j++){ for(int k=1;k<=siz[v];k++){ ff[j+k]=(ff[j+k]+1ll*f[u][j]*g[v][k]%mod)%mod; gg[j+k]=(gg[j+k]+1ll*g[u][j]*g[v][k]%mod)%mod; ff[j+k-1]=(ff[j+k-1]+1ll*f[u][j]*f[v][k]%mod)%mod; gg[j+k-1]=((gg[j+k-1]+1ll*f[u][j]*g[v][k]%mod)%mod+1ll*g[u][j]*f[v][k]%mod)%mod; } } siz[u]+=siz[v]; f[u].resize(siz[u]+3),g[u].resize(siz[u]+3); for(int j=1;j<=siz[u];j++) f[u][j]=ff[j],g[u][j]=gg[j],ff[j]=gg[j]=0; } } int pw[N]; bool b2; int main(){ // freopen("tree.in","r",stdin); // freopen("tree.out","w",stdout); read(n); init(); for(int i=1;i<n;i++){ int x,y; read(x),read(y); add(x,y),add(y,x); } dfs(1,0); pw[0]=1; for(int i=1;i<=n;i++) pw[i]=1ll*pw[i-1]*n%mod; // for(int i=0;i<n;i++){ // cout<<1ll*g[1][i+1]%mod<<" "; // } // cout<<"\n"; for(int i=0;i<n;i++){ int ans=0; for(int j=i;j<n;j++){ if(n-j<2) ans=(ans+1ll*(j-i&1 ? -1 : 1)*C(j,j-i)%mod)%mod; else ans=(ans+1ll*(j-i&1 ? -1 : 1)*C(j,j-i)%mod*g[1][n-j]%mod*pw[n-j-2]%mod)%mod; } write((ans+mod)%mod); putchar(' '); } // putchar('\n'); // cerr<<fabs(&b1-&b2)/1048576.0<<"MB"; return 0; } ``` # CSP T4 [P14364 [CSP-S 2025] 员工招聘 / employ](https://www.luogu.com.cn/problem/P14364) 特别地,补一下题。 ## 题意 不多写了。 ## 思路 膜拜jiangly。 我们考虑反过来 dp 被拒绝若干个人的的方案数。 注意到我们并不关注放到当前天的人是谁,我们只关注有多少个人的 $c$ 与已经**被拒绝**的人数的关系。 我们假设已经知道了到当前天有多少人被拒绝。设到当前已经有 $x$ 个人被拒绝了。 转移时考虑 $s$: - $s_i=0$,此时无论如何无法过审,随便放人。 - $s_i=1$,考虑分类讨论: - 过审:选择的人满足 $c>x$。 - 不过审:选择的人满足 $c\le x$。 注意到 $c>x$ 是一个非常不好算的转移,我们考虑将其容斥为“所有方案数 $-$($c\le x$ 的方案数)“,这样第二种转移的两种分讨就能共用一次转移方案了。 于是我们设 $f_{i,j,k}$ 表示 前 $i$ 个位置已经有 $j$ 个人被拒绝并且已经有 $k$ 个人被钦定了位置且有 $\le x$ 的方案数。 最终的答案就是
\sum_{i=0}^{n-m}\sum_{j=0}^{n} f_{n,i,j}\times (n-j)!
乘上阶乘是剩下的人可以随便放。 复杂度 $\Omicron(n^3)$。 ## 代码 ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; template<typename T> inline void read(T &x){ T w=1; x=0; char c=getchar(); while(!isdigit(c)){ if(c=='-') w=-1; c=getchar(); } while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); x*=w; } template<typename T> inline void write(T x){ if(x<0) putchar('-'),x=(~x)+1; if(x>9) write(x/10); putchar(x%10^48); } const ll N=5e2+10,mod=998244353; ll f[2][N][N]; ll n,a[N],m; char c[N]; void ad(ll &x,ll y){ x=(x+y)%mod; } ll fac[N]; int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); read(n),read(m); scanf("%s",c+1); for(int i=1;i<=n;i++){ ll x; read(x); a[x]++; } for(int i=1;i<=n;i++) a[i]+=a[i-1]; ll opt=0; f[0][0][0]=1; for(int i=0;i<n;i++){ for(int j=0;j<=i;j++){ for(int k=0;k<=i;k++) f[opt^1][j][k]=0; } if(c[i+1]=='0'){ for(int j=0;j<=i;j++){ for(int k=0;k<=i;k++) ad(f[opt^1][j+1][k],f[opt][j][k]); } } else{ for(int j=0;j<=i;j++){ for(int k=0;k<=i;k++){ ad(f[opt^1][j][k],f[opt][j][k]); ll sum=a[j]-k; ad(f[opt^1][j][k+1],-f[opt][j][k]*sum%mod); ad(f[opt^1][j+1][k+1],f[opt][j][k]*sum%mod); } } } opt^=1; } fac[0]=1; for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod; ll ans=0; for(int i=0;i<=n-m;i++){ for(int j=0;j<=n;j++) ad(ans,f[opt][i][j]*fac[n-j]%mod); } write((ans+mod)%mod); return 0; } ```

评论

0 条评论,欢迎与作者交流。

正在加载评论...