2021.9.18考试总结[NOIP模拟56]
T1 爆零
贪心地想,肯定要先走完整个子树再走下一个,且要尽量晚地走深度大的叶子。所以对每个点的儿子以子树树高为关键字排序$DFS$即可。
也可$DP$。
$code:$
T1
#include<bits/stdc++.h>
using namespace std;
namespace IO{
inline int read(){
char ch=getchar(); int x=0,f=1;
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
inline void write(int x,char sp){
char ch[20]; int len=0;
if(x<0){ putchar('-'); x=~x+1; }
do{ ch[len++]=x%10+(1<<4)+(1<<5); x/=10; }while(x);
for(int i=len-1;~i;--i) putchar(ch[i]); putchar(sp);
}
inline int max(int x,int y){ return x<y?y:x; }
inline int min(int x,int y){ return x<y?x:y; }
inline void swap(int &x,int &y){ x^=y^=x^=y; }
inline void chmax(int &x,int y){ x=x<y?y:x; }
inline void chmin(int &x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=1e6+5;
int n,pos,ans,mx[NN];
vector<int>to[NN];
bool cmp(int x,int y){ return mx[x]<mx[y]; }
namespace tree_chain{
int siz[NN],son[NN],dep[NN],fa[NN],top[NN];
void dfs1(int s,int f){
fa[s]=f; siz[s]=1;
for(auto v:to[s]) if(v!=f){
dep[v]=dep[s]+1;
dfs1(v,s);
siz[s]+=siz[v];
if(siz[son[s]]<siz[v]) son[s]=v;
}
}
void dfs2(int s,int t){
top[s]=t;
if(!son[s]) return;
dfs2(son[s],t);
for(auto v:to[s])
if(v!=fa[s]&&v!=son[s]) dfs2(v,v);
}
inline int LCA(int x,int y){
while(top[x]!=top[y])
if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];
else y=fa[top[y]];
return dep[x]<dep[y]?x:y;
}
} using namespace tree_chain;
void dfs3(int s){
mx[s]=dep[s];
for(auto v:to[s]) if(v!=fa[s]){
dfs3(v);
chmax(mx[s],mx[v]);
}
}
inline void calc(int p){
ans+=min(dep[pos]+dep[p]-2*dep[LCA(pos,p)],dep[p]);
// cout<<p<<' '<<pos<<' '<<min(dep[pos]+dep[p]-2*dep[LCA(pos,p)],dep[p])<<endl;
pos=p;
}
void dfs(int s){
if(!son[s]) calc(s);
for(auto v:to[s])
if(v!=fa[s]) dfs(v);
}
signed main(){
FILE *A=freopen("a.in","r",stdin);
FILE *B=freopen("a.out","w",stdout);
n=read(); pos=1;
for(int a,i=2;i<=n;i++)
a=read(), to[a].push_back(i);
dfs1(1,0); dfs2(1,1); dfs3(1);
for(int i=1;i<=n;i++) sort(to[i].begin(),to[i].end(),cmp);
// for(int i=1;i<=n;i++){cout<<i<<": ";for(auto v:to[i])cout<<v<<",";cout<<endl;}
dfs(1);
write(ans,'\n');
return 0;
}
T2 底垫
用$set$(柯朵莉树)维护区间并记录区间内数字最后出现在哪个“梦境”内,然后对询问按右端点排序,每次将“梦境”扫描到右端点位置,相当于单调指针,一共扫了一遍。
每次加入新区间时会覆盖一些区间,先考虑如果询问只是$[L,R]$区间并的总长应怎么做。可以开一个树装数组,记录当前右端点情况下每个左端点的答案。
在覆盖区间时,如果把长度为$k$,最后出现位置为$t$的区间覆盖掉了,那么就在树状数组中把$[t+1,i]$的答案加上$k$,因为它的出现位置更新后有更多的左端点会被这个区间贡献。
再考虑这道题,首先对一个$[L,R]$区间,选择的方案数是一定的,为$\begin{pmatrix}R-L+2\\ 2\end{pmatrix}$。接下来仍考虑覆盖区间对答案的贡献。
仍设覆盖区间长度为$k$,最后出现位置为$t$,另设覆盖后区间出现位置为$i$,也就是当前梦境位置。
如果最终询问的$L\in [t+1,i]$,那么它对答案的贡献为$(i-L+1)\times(r-i+1)\times k$;否则贡献为$(i-t)\times(r-i+1)\times k$。
于是可以建四个求后缀和的树状数组,分别记$lr,l,r$的系数和常数,在$i$处与$t$处修改即可。
$code:$
T2
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace IO{
inline int read(){
char ch=getchar(); int x=0,f=1;
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
inline void write(int x,char sp){
char ch[20]; int len=0;
if(x<0){ putchar('-'); x=~x+1; }
do{ ch[len++]=x%10+(1<<4)+(1<<5); x/=10; }while(x);
for(int i=len-1;~i;--i) putchar(ch[i]); putchar(sp);
}
inline int max(int x,int y){ return x<y?y:x; }
inline int min(int x,int y){ return x<y?x:y; }
inline void swap(int& x,int& y){ x^=y^=x^=y; }
inline void chmax(int& x,int y){ x=x<y?y:x; }
inline void chmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=2e5+5,p=1e9+7,inv2=5e8+4;
int n,m,tmp,l[NN],r[NN],ans[NN];
struct question{
int l,r,id;
bool operator<(const question& a)const{
return r<a.r;
}
}q[NN];
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
struct BIT{
int c[NN];
inline void insert(int pos,int x){
while(pos){
(c[pos]+=p+x)%=p;
pos-=pos&-pos;
}
}
inline int query(int pos){
int res=0;
while(pos<=n){
(res+=c[pos])%=p;
pos+=pos&-pos;
}
return res;
}
}ll,rr,lr,cc;
inline void solve(int i,int t,int k){
lr.insert(i,p-k); ll.insert(i,k*(i-1)%p); rr.insert(i,k*(i+1)%p); cc.insert(i,(p+k-k*i%p*i%p)%p);
lr.insert(t,k); ll.insert(t,k*(p+1-i)%p); rr.insert(t,k*(p-i-1)%p); cc.insert(t,(p-k+k*i%p*i%p)%p);
rr.insert(t,k*(i-t)%p); cc.insert(t,k*(p+i+t*i%p-t-i*i%p)%p);
}
namespace ODT{
#define sit set<node>::iterator
struct node{
int l,r;
mutable int val;
node(){}
node(int a,int b,int c){ l=a; r=b; val=c; }
node(int a){ l=a; }
bool operator<(const node& a)const{
return l<a.l;
}
};
set<node>s;
inline sit split(int pos){
sit it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos) return it;
--it;
int l=it->l,r=it->r,val=it->val;
s.erase(it); s.insert(node(l,pos-1,val));
return s.insert(node(pos,r,val)).first;
}
inline void assign(int l,int r,int val){
sit itr=split(r+1),itl=split(l);
for(sit it=itl;it!=itr;++it){
int ll=it->l,k=it->r-it->l+1;
solve(val,it->val,k);
}
s.erase(itl,itr);
s.insert(node(l,r,val));
}
} using namespace ODT;
signed main(){
FILE *A=freopen("b.in","r",stdin);
FILE *B=freopen("b.out","w",stdout);
n=read(); m=read(); s.insert(node(0,(int)1e9+5,0));
for(int i=1;i<=n;i++) l[i]=read(), r[i]=read()-1;
for(int i=1;i<=m;i++) q[i].l=read(), q[i].r=read(), q[i].id=i;
sort(q+1,q+m+1); tmp=1;
for(int i=1;i<=m;i++){
for(;tmp<=q[i].r;++tmp) assign(l[tmp],r[tmp],tmp);
ans[q[i].id]=q[i].l*q[i].r%p*lr.query(q[i].l)%p+q[i].l*ll.query(q[i].l)%p+q[i].r*rr.query(q[i].l)+cc.query(q[i].l);
ans[q[i].id]%=p;
(ans[q[i].id]*=qpow((q[i].r-q[i].l+1)*(q[i].r-q[i].l+2)%p*inv2%p,p-2))%=p;
}
for(int i=1;i<=m;i++) write(ans[i],'\n');
return 0;
}
T3高考
首先可以发现最后所有局面出现概率相等。
$\frac{\prod(a_i-1)!}{n(n+1)\dots (n+m-1)}\times\frac{m!}{\prod(a_i-1)!}=\frac{1}{\begin{pmatrix}n+m-1\\n-1\end{pmatrix}}$
所以期望题右变成了计数题。考虑设$f_{i,j}$为至少$i$个数字大于等于$j$的方案数,考虑贡献次数,其实对于$r$,答案即为$\sum_{i=1}^r\sum_{j=1}^mf_{i,j}$。
再设$g_{i,j}$为恰有$i$个数字大于等于$j$的方案数,那么$f$即为$g$的后缀和。对于$g$可通过二项式反演求。
$g_{i,j}=\sum_{k=i}^n\begin{pmatrix}k\\ i\end{pmatrix}(-1)^{k-i}\begin{pmatrix}n\\ k\end{pmatrix}\begin{pmatrix}m+n-1-jk\\ n-1\end{pmatrix}$
右边两个组合数可通过划分数的方案来理解。
这样求出的方案默认开始时所有数为$0$,因此每个答案最后要再加$r$。如果要从$1$开始,需要改一改上界和求$g$的式子。
我打的是从$1$开始。
$code:$
T3
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace IO{
inline int read(){
char ch=getchar(); int x=0,f=1;
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
inline void write(int x,char sp){
char ch[20]; int len=0;
if(x<0){ putchar('-'); x=~x+1; }
do{ ch[len++]=x%10+(1<<4)+(1<<5); x/=10; }while(x);
for(int i=len-1;~i;--i) putchar(ch[i]); putchar(sp);
}
inline int max(int x,int y){ return x<y?y:x; }
inline int min(int x,int y){ return x<y?x:y; }
inline void swap(int &x,int &y){ x^=y^=x^=y; }
inline void chmax(int &x,int y){ x=x<y?y:x; }
inline void chmin(int &x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=5005,p=1e9+7,MX=10010;
int n,m,invv,f[NN][NN],g[NN][NN],fac[MX+1],inv[MX+1];
inline int C(int x,int y){ return x<y?0:fac[x]*inv[y]%p*inv[x-y]%p; }
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
signed main(){
FILE *A=freopen("c.in","r",stdin);
FILE *B=freopen("c.out","w",stdout);
n=read(); m=read(); fac[0]=inv[0]=1;
for(int i=1;i<=MX;i++) fac[i]=fac[i-1]*i%p; inv[MX]=qpow(fac[MX],p-2);
for(int i=MX-1;i;i--) inv[i]=inv[i+1]*(i+1)%p;
for(int i=1;i<=n;i++)
for(int j=1;j<=m+1&&i*j<=m+n;j++)
for(int k=i;k<=n&&j*k<=m+n;k++){
int tmp=C(n,k)*C(k,i)%p*C(m+n-1-j*k+k,n-1)%p;
(g[i][j]+=(k-i)&1?p-tmp:tmp)%=p;
}
for(int i=n;i;i--) for(int j=1;j<=m+1;j++){
f[i][j]=(f[i+1][j]+g[i][j])%p;
(f[i][0]+=f[i][j])%=p;
}
invv=qpow(C(n+m-1,n-1),p-2);
for(int i=1;i<=n;i++){
(f[i][0]+=f[i-1][0])%=p;
write((f[i][0])*invv%p,'\n');
}
return 0;
}
T4 种田
枚举$\frac{S}{T}$有$70$,精心$rand$有$100$,无正解。