NOIP 2024 补题记录
全部放到一处过于卡顿,故将补完的比赛单独拿出来。
树的遍历
个人认为是这套题里最难的一道。
记 \(d_u\) 为原树中 \(u\) 的度数。
考虑一个生成树能被哪些根生成,如果考虑去判定就会像我一样炸掉,但是举例可以发现合法的根一定构成一条从叶子到另一个叶子的链。
考虑证明,对于每个原树上的点,其周围的边在生成树上一定形成一条链,那么根就被限定在这条链的首尾两个方向,将所有信息叠加即可得到一条链。
那么考虑对每条链算贡献,此时在链上的点就被确定了首尾,而不在链上的点则被确定了首,答案即为 \(\prod_{x\notin path}(d_x-1)!\prod_{x\in path}(d_x-2)!=\prod(d_x-1)!\prod_{x\notin path}\frac{1}{d_x-1}\)。
前面的东西可以提出来,问题变为:每个点有点权,每条边有 01 边权,求所有叶子到另一个叶子且经过 1 边的链的点权乘积的和。这个直接 dfs 一遍就能算出。
#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define repll(i,l,r) for(ll i=(l);i<=(r);i++)
#define perll(i,r,l) for(ll i=(r);i>=(l);i--)
#define pb push_back
#define ins insert
#define clr clear
using namespace std;
namespace ax_by_c{
typedef long long ll;
typedef pair<int,bool> ND;
const ll mod=1e9+7;
const int N=1e5+5;
ll ksm(ll a,ll b,ll p){
a=a%p;
ll r=1;
while(b){
if(b&1)r=r*a%p;
a=a*a%p;
b>>=1;
}
return r%p;
}
ll fac[N],inv[N];
void Init(int n){
fac[0]=1;
rep(i,1,n)fac[i]=fac[i-1]*i%mod;
inv[n]=ksm(fac[n],mod-2,mod);
per(i,n,1)inv[i-1]=inv[i]*i%mod;
}
int n,k,x[N],y[N];
bool z[N];
vector<ND>g[N];
ll f[N][2],ans;
ll I(int x){
if(!x)return 1;
return inv[x]*fac[x-1]%mod;
}
void dfs(int u,int fa){
f[u][0]=f[u][1]=0;
if(g[u].size()==1){
f[u][0]=I(g[u].size()-1);
return ;
}
for(auto e:g[u]){
if(e.first==fa)continue;
dfs(e.first,u);
if(e.second)ans=(ans+(f[u][0]+f[u][1])*(f[e.first][0]+f[e.first][1])%mod)%mod,f[u][1]=(f[u][1]+(f[e.first][0]+f[e.first][1])*I(g[u].size()-1)%mod)%mod;
else ans=(ans+f[u][1]*(f[e.first][0]+f[e.first][1])%mod+f[u][0]*f[e.first][1]%mod)%mod,f[u][0]=(f[u][0]+f[e.first][0]*I(g[u].size()-1)%mod)%mod,f[u][1]=(f[u][1]+f[e.first][1]*I(g[u].size()-1)%mod)%mod;
}
}
void slv(){
scanf("%d %d",&n,&k);
Init(n);
rep(i,1,n)g[i].clr();
rep(i,1,n-1)scanf("%d %d",&x[i],&y[i]),z[i]=0;
rep(i,1,k){
int x;
scanf("%d",&x);
z[x]=1;
}
rep(i,1,n-1)g[x[i]].pb({y[i],z[i]}),g[y[i]].pb({x[i],z[i]});
if(n==2){
puts("1");
return ;
}
rep(rt,1,n){
if(g[rt].size()>1){
ans=0,dfs(rt,0);
rep(i,1,n)ans=ans*fac[g[i].size()-1]%mod;
printf("%lld\n",ans);
return ;
}
}
}
void main(){
int T=1;
int csid=0;scanf("%d",&csid);
scanf("%d",&T);
while(T--)slv();
}
}
int main(){
string __name="";
if(__name!=""){
freopen((__name+".in").c_str(),"r",stdin);
freopen((__name+".out").c_str(),"w",stdout);
}
ax_by_c::main();
return 0;
}
树上查询
场上想出了支配对 2log 做法但是脑抽以为 cdq 是 3log 于是没写,写了至少也能加个 16 分上个 300 了。(其实更多人没写的原因是以为支配对是 1log 的)
考虑统计每个点作为 LCA 的贡献,对每个点维护出其子树内的所有下标连续段,那么只有连续段的子串会产生贡献。
不难发现只会合并 \(O(n)\) 次,于是连续段个数是 \(O(n)\) 的,求的过程可能要带 log。(场上不知道这个叫支配对)
现在相当于有 \(O(q)\) 个 \((l,r,k)\) 和 \(O(n)\) 个 \((l',r',x)\),要对每个 \((l,r)\) 求 \(\max_{\min(r,r')-\max(l,l')+1\ge k} x\),直接分四种情况讨论 cdq 即可。
以上是场上想法,下面想一下怎么优化到 1log:
直接把最值拆了好像不好做,考虑拆一半:
- \(r-\max(l,l')+1\ge k,r\le r'\)
注意到 \(k\le r-l+1\)。
于是只需 \(r-l'+1\ge k,r\le r'\)。
即 \(l'\le r-k+1,r\le r'\)。
然后对 \(r\) 扫描线即可。
- \(r'-\max(l,l')+1\ge k,r>r'\)
\(r'-l+1\ge k,r'-l'+1\ge k,r>r'\)。
即 \(k+l-1\le r'<r,k\le r'-l'+1\)。
然后对 \(k\) 扫描线即可。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
namespace ax_by_c{
const int N=5e5+5;
struct SEG{
int tr[N*4];
void pu(int u){
tr[u]=max(tr[u<<1],tr[u<<1|1]);
}
void clr(int u,int l,int r){
if(l==r){
tr[u]=0;
return ;
}
int mid=l+((r-l)>>1);
clr(u<<1,l,mid),clr(u<<1|1,mid+1,r);
pu(u);
}
void upd(int u,int l,int r,int p,int v){
if(l==r){
tr[u]=max(tr[u],v);
return ;
}
int mid=l+((r-l)>>1);
if(p<=mid)upd(u<<1,l,mid,p,v);
else upd(u<<1|1,mid+1,r,p,v);
pu(u);
}
int Q(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return tr[u];
int mid=l+((r-l)>>1),res=0;
if(ql<=mid)res=max(res,Q(u<<1,l,mid,ql,qr));
if(mid+1<=qr)res=max(res,Q(u<<1|1,mid+1,r,ql,qr));
return res;
}
}tr;
struct DSU{
struct node{
int fa,sz,L,R;
}a[N];
void Init(int n){
for(int i=1;i<=n;i++)a[i]={i,1,i,i};
}
int find(int x){
if(a[x].fa==x)return x;
return a[x].fa=find(a[x].fa);
}
bool meg(int x,int y){
x=find(x),y=find(y);
if(x==y)return 0;
if(a[x].sz>a[y].sz)swap(x,y);
a[x].fa=y;
a[y].sz+=a[x].sz;
a[y].L=min(a[y].L,a[x].L);
a[y].R=max(a[y].R,a[x].R);
return 1;
}
}dsu;
struct U{
int l,r,x;
};
struct Q{
int l,r,k,id;
}qs[N];
int n,q,fa[N],de[N],sz[N],sn[N],L[N],R[N],Tim,dfa[N],mxr[N],qans[N];
vector<int>g[N];
vector<U>us;
void dfs(int u){
L[u]=++Tim;
dfa[Tim]=u;
sz[u]=1;
for(auto v:g[u]){
if(v==fa[u])continue;
fa[v]=u;
de[v]=de[u]+1;
dfs(v);
sz[u]+=sz[v];
if(sz[v]>sz[sn[u]])sn[u]=v;
}
R[u]=Tim;
}
void add(int p,int x){
int l=dsu.a[dsu.find(p)].L,r=dsu.a[dsu.find(p)].R;
if(mxr[l]<r)mxr[l]=r,us.push_back({l,r,x});
}
void ddfs(int u){
for(auto v:g[u]){
if(v==fa[u]||v==sn[u])continue;
ddfs(v);
}
if(sn[u])ddfs(sn[u]);
add(u,de[u]);
if(u!=1&&L[u]<=L[u-1]&&L[u-1]<=R[u]&&dsu.meg(u-1,u))add(u,de[u]);
if(u!=n&&L[u]<=L[u+1]&&L[u+1]<=R[u]&&dsu.meg(u,u+1))add(u,de[u]);
for(auto v:g[u]){
if(v==fa[u]||v==sn[u])continue;
for(int i=L[v];i<=R[v];i++){
if(dfa[i]!=1&&L[u]<=L[dfa[i]-1]&&L[dfa[i]-1]<=R[u]&&dsu.meg(dfa[i]-1,dfa[i]))add(dfa[i],de[u]);
if(dfa[i]!=n&&L[u]<=L[dfa[i]+1]&&L[dfa[i]+1]<=R[u]&&dsu.meg(dfa[i],dfa[i]+1))add(dfa[i],de[u]);
}
}
}
bool cmp_U_1(U x,U y){
return x.r<y.r;
}
bool cmp_Q_1(Q x,Q y){
return x.r<y.r;
}
bool cmp_U_2(U x,U y){
return x.r-x.l+1<y.r-y.l+1;
}
bool cmp_Q_2(Q x,Q y){
return x.k<y.k;
}
void main(){
scanf("%d",&n);
for(int i=1,u,v;i<n;i++){
scanf("%d %d",&u,&v);
g[u].push_back(v),g[v].push_back(u);
}
de[1]=1,dfs(1);
dsu.Init(n);
ddfs(1);
scanf("%d",&q);
for(int i=1;i<=q;i++)scanf("%d %d %d",&qs[i].l,&qs[i].r,&qs[i].k),qs[i].id=i;
sort(us.begin(),us.end(),cmp_U_1),sort(qs+1,qs+1+q,cmp_Q_1);
tr.clr(1,1,n);
for(int i=q,j=(int)us.size();i>=1;i--){
while(j&&qs[i].r<=us[j-1].r)j--,tr.upd(1,1,n,us[j].l,us[j].x);
if(1<=qs[i].r-qs[i].k+1)qans[qs[i].id]=max(qans[qs[i].id],tr.Q(1,1,n,1,qs[i].r-qs[i].k+1));
}
sort(us.begin(),us.end(),cmp_U_2),sort(qs+1,qs+1+q,cmp_Q_2);
tr.clr(1,1,n);
for(int i=q,j=(int)us.size();i>=1;i--){
while(j&&qs[i].k<=us[j-1].r-us[j-1].l+1)j--,tr.upd(1,1,n,us[j].r,us[j].x);
if(qs[i].k+qs[i].l-1<qs[i].r)qans[qs[i].id]=max(qans[qs[i].id],tr.Q(1,1,n,qs[i].k+qs[i].l-1,qs[i].r-1));
}
for(int i=1;i<=q;i++)printf("%d\n",qans[i]);
}
}
int main(){
ax_by_c::main();
return 0;
}

浙公网安备 33010602011771号