NOI2023 题解

发布时间 2023-08-02 13:06:06作者: Matutino_Lux

打的太 shaber 了,于是补补题。

D1T1

扫描线。
首先我们可以容斥一下,答案为被一种操作覆盖到的减去被两种操作覆盖到的加上被三种操作覆盖到的。
首先考虑只被一种操作覆盖到的,这很简单,直接上个区间颜色段推平就好了,顺便去了个重。
接下来是有被斜线覆盖到的,这样的点数为 \(O(nk)\),直接枚举暴力存点即可,用 std::map 记下来即可。
比较有难度的是横纵计数,考虑扫描线,那么相当于每次是一个单点修改和区间查询,树状数组即可。
时间复杂度 \(\Theta(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define reg register
#define int long long
inline int read(){
	reg int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();} 
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline int max(reg int x,reg int y){return x>y?x:y;}
const int N=2e5+10,INF=1e18,mod=1e9+7;
int n,m,q,ans,tot,b[N],btot,rt;
struct ODT{int l,r,c;inline bool operator<(const ODT &rhs)const{return l<rhs.l;}};
std::set<ODT> s[N];
std::map<int,int> ysx,ysy,ysd,ys;
std::map<std::pair<int,int>,int> ap1,ap2;
std::vector<std::pair<int,int> > vc[N];
inline void split(reg int id,reg int x){ 
	reg auto it=s[id].lower_bound({x,0,0}); if (it->l==x) return;
	it--; reg int l=it->l,r=it->r,c=it->c; s[id].erase(it);
	s[id].insert({l,x-1,c}),s[id].insert({x,r,c});
}
inline void insert(reg int id,reg int l,reg int r){
	split(id,l),split(id,r+1); 
	reg auto L=s[id].lower_bound({l,0,0}),R=s[id].lower_bound({r+1,0,0});
	s[id].erase(L,R); s[id].insert({l,r,1}); 
}
int idx,ls[N<<5],rs[N<<5],sum[N<<5];
inline void modify(reg int &o,reg int l,reg int r,reg int x,reg int k){
	if (!o) o=++idx; sum[o]+=k; reg int mid=l+r>>1; if (l==r) return;
	x<=mid?modify(ls[o],l,mid,x,k):modify(rs[o],mid+1,r,x,k);
}
inline int query(reg int o,reg int l,reg int r,reg int L,reg int R){
	if (!o) return 0; if (L<=l&&r<=R) return sum[o]; reg int mid=l+r>>1,res=0;
	if (L<=mid) res+=query(ls[o],l,mid,L,R); if (R>mid) res+=query(rs[o],mid+1,r,L,R); return res;
} 
signed main(){
//	freopen("A.in","r",stdin);
	read(),n=read(),m=read(),q=read();
	for (reg int i=1;i<=q;i++){
		reg int op=read(),x1=read(),y1=read(),x2=read(),y2=read();
		if (op==1){
			if (!ysy.count(y1)) ysy[y1]=++tot,s[tot].insert({1,max(n,m)+1,0});
			insert(ysy[y1],x1,x2);
		}else if (op==2){
			if (!ysx.count(x1)) ysx[x1]=++tot,s[tot].insert({1,max(n,m)+1,0});
			insert(ysx[x1],y1,y2);
		}else{
			if (!ysd.count(y1-x1)) ysd[y1-x1]=++tot,s[tot].insert({1,max(n,m)+1,0});
			insert(ysd[y1-x1],x1,x2);			
		}
	}
//	return 0;
	
	for (reg int i=1;i<=tot;i++) for (reg auto it:s[i]) if (it.c) ans+=it.r-it.l+1;
	for (reg auto it:ysy) for (reg auto rec:s[it.second]) if (rec.c){
		if (!ys.count(rec.l)) ys[rec.l]=++btot,b[btot]=rec.l;
		if (!ys.count(rec.r+1)) ys[rec.r+1]=++btot,b[btot]=rec.r+1;
		vc[ys[rec.l]].push_back({it.first,1}),vc[ys[rec.r+1]].push_back({it.first,-1});
	}
	
	for (reg auto it:ysx) b[++btot]=it.first; 
	std::sort(b+1,b+btot+1),btot=std::unique(b+1,b+btot+1)-b-1;
	for (reg int i=1,x;x=b[i],i<=btot;i++){
		if (ys.count(x)) for (reg auto it:vc[ys[x]]) modify(rt,1,m,it.first,it.second);
		if (ysx.count(x)) for (reg auto it:s[ysx[x]]) if (it.c) ans-=query(rt,1,m,it.l,it.r); 
	}
	for (reg auto ID:ysd) for (reg auto it:s[ID.second]) if (it.c){
		for (reg auto id:ysx) for (reg auto rec:s[id.second]) if (rec.c){
			reg int x=id.first,y=x+ID.first;
			if (it.l<=x&&x<=it.r&&rec.l<=y&&y<=rec.r) ap1[{x,y}]=1;
		}
		for (reg auto id:ysy) for (reg auto rec:s[id.second]) if (rec.c){
			reg int y=id.first,x=y-ID.first;
			if (it.l+ID.first<=y&&y<=it.r+ID.first&&rec.l<=x&&x<=rec.r) ap2[{x,y}]=1;
		}
	}
	ans-=ap1.size()+ap2.size();
	for (reg auto it:ap1) if (ap2.count(it.first)) ans++;
	printf("%lld\n",ans);
	return 0;
}

D1T2

找规律。
首先我们考虑 \(k=0\) 这档怎么做。
先考虑 \(k=0,n=1\),此时每次加入一个点,要么认一个父亲,要么插入在一条边中。
答案为 \(\prod_{i=1}^m 2i-1\)
拓展一下,发现如果有 \(n\) 个点,相当于在一开始就多了 \(2n-2\) 种选择(去掉了认 \(1\) 为父亲的那种)。答案为 \(\prod_{i=1}^m 2n+2i-3\)
现在考虑 \(k>0\) 应该怎么做,我们发现每次我们可以考虑从一条边中拉一个点出来。相当于原来有一条 \(x \to y\) 的边,现在要变成 \(x \to z \to y\) 同时 \(z \to w\)。我们先将 \(z\) 空着放到之后填,但是 \(z \le k+w\),所以我们考虑将要填掉的 \(z\) 压起来。
\(dp_{i,s}\) 表示填到 \(n+i\),前 \(k\) 个点的状态是 \(s\),得到转移方程。
有三种转移:

  • \(k=0\) 那样转移,转移系数为 \(2(n+i+pc(s))-1\)
  • 拉出一个点,那么状态要与上一个 \(1\),系数为 \(1\)
  • 选择一个点,考虑消除填掉这个点对应的 \(z\),那么状态要异或上一个数,系数为 \(1\)

特别地,最高位为 \(1\) 时强制转移第三种,且选择的点也是固定的。
答案为 \(dp_{m,0}\),时间复杂度 \(\Theta(m 2^kk)\)

点击查看代码
#include<bits/stdc++.h>
#define reg register
//#define int long long
inline int read(){
	reg int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();} 
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
const int N=5050,INF=1e18,mod=1e9+7;
inline int Add(reg int x,reg int y){return x+y>=mod?x+y-mod:x+y;}
inline int Mul(reg int x,reg int y){return 1ll*x*y>=mod?1ll*x*y%mod:x*y;}
inline void Plus(reg int &x,reg int y){x=Add(x,y);} 
int n,m,k,pc[N],f[N][N];
inline void work(){
	n=read(),m=read(),k=read();
	for (reg int i=1;i<n;i++) read();
	for (reg int i=1;i<(1<<k);i++) pc[i]=pc[i>>1]+(i&1); 
	for (reg int i=1;i<=m;i++) for (reg int j=0;j<(1<<k);j++) f[i][j]=0;
	f[0][0]=1; reg int full=(1<<k)-1;
	for (reg int i=1;i<=m;i++)
		for (reg int s=0,can;can=(s<<1)&full,s<(1<<k);s++)
			if (s>>k-1&1) Plus(f[i][can],f[i-1][s]);
 			else{
				for (reg int j=1;j<=k;j++) if (can>>j-1&1) Plus(f[i][(can^(1<<j-1))],f[i-1][s]); 
				Plus(f[i][can],Mul((n+i+pc[s]<<1)-3,f[i-1][s]));
				Plus(f[i][can|1],Mul(n+i+pc[s]-2,f[i-1][s]));
			}
	printf("%d\n",f[m][0]);
}
signed main(){
	read(); for (reg int _=read();_--;) work();
	return 0;
}

D1T3

首先观察到一个树是 dfs 树,当且仅当所有额外边是返祖边。
那么可以容斥

\[\sum_{T \subseteq S,T \ne \varnothing} (-1)^{|T|-1} 2^{合法边数} \]

那么现在要考虑对于 \(T\) 中每个点都合法的边数了。
考虑特殊性质 \(B\) 应该怎么做,此时只有返祖边。
一条额外边满足条件当且仅当他被虚数上的一条边包含,或者虚数边上的一个点指向不在虚树中出现的子树。
\(f_{u}\) 表示以 \(u\) 为根的子树中 \(u\) 被钦定在虚树中的方案数,那么转移过程形如一个背包。
一个点出现在虚树有两种情况:

  • 存在至少两个不同的子树中有关键点,那么这个点选不选无所谓。
  • 小于两个子树中有关键点,那么这个点必须被选择。

但是注意到,转移的时候选择 \(f_v\),实际上转移过来的值为 \(f_v \times 2^{u,v 路径的合法值}\),于是考率维护实际的转移值 \(g_v\)
对于第一类边 \(u \to x\),转移为对 \(x\) 的整棵子树乘 \(2\)
对于第二类边,我们要处理对于每个 \(u\) 处理出 \(fa_u\)\(u\) 这一方向上所有的额外边 \(A_u\),那么此时的更新为,对于 \(u\) 的一个儿子 \(v\),将不是 \(v\) 的子树都乘上 \(A_u\)
背包的过程中,一棵子树既可以选择子树中某点的 \(g\) 值,也可以选择不选关键点,乘上 \(2^{A_v}\) 转移。
最后在 \(u\) 处统计答案,贡献为 \(f_u\)\(u\)\(fa_u\) 方向的所有点是否选的方案数。
发现对 \(g\) 的操作全是区间乘法,区间求和,可以用线段树维护。

现在我们有横叉边。对于 \(u\) 的维护是没有区别的,问题出在在横叉边 \((u,v)\) 的 LCA 处统计答案时,如果只有 \(u\) 子树和 \(v\) 子树中选择点,那么此时可以选择这条边。
这是一个一个矩形乘,矩形差的形式,要统计子树中恰选两个的方案数,这个过程可以直接扫描线维护,相当于一个矩形面积并。
时间复杂度 \(\Theta((n+m)\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define reg register
//#define int long long
inline int read(){
	reg int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();} 
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline int max(reg int x,reg int y){return x>y?x:y;}
inline int min(reg int x,reg int y){return x<y?x:y;}
const int N=5e5+10,mod=1e9+7;
inline int Add(reg int x,reg int y){return x+y>=mod?x+y-mod:x+y;}
inline int Mul(reg int x,reg int y){return 1ll*x*y>=mod?1ll*x*y%mod:x*y;}
inline void Plus(reg int &x,reg int y){x=Add(x,y);}
int n,m,k,pw2[N],ipw2[N];
std::vector<int> G[N],e1[N],e2[N],sons;
std::vector<std::pair<int,int> > vc[N];
struct Node{int x,l,r,v;}oper[N];
int fa[N],dfn[N],dfc,siz[N],dep[N],son[N],top[N],ys[N];
inline void Dfs1(reg int u,reg int fat=0){
	dep[u]=dep[fa[u]=fat]+1,ys[dfn[u]=++dfc]=u,siz[u]=1;
	for (reg auto v:G[u]) if (v^fat) Dfs1(v,u),siz[u]+=siz[v],siz[v]>siz[son[u]]?son[u]=v:0;
}
inline void Dfs2(reg int u,reg int fst){
	top[u]=fst; if (!son[u]) return; Dfs2(son[u],fst);
	for (reg auto v:G[u]) if ((v^fa[u])&&(v^son[u])) Dfs2(v,v); 
}
inline int LCA(reg int u,reg int v){
	while (top[u]^top[v]){
		if (dep[top[u]]<dep[top[v]]) std::swap(u,v);
		u=fa[top[u]]; 
	}
	return dep[u]<dep[v]?u:v;
}
int sz[N],sz2[N],key[N],ans,f[N];
struct Segment{
	int sum[N<<2],tag[N<<2];
	inline void build(reg int o,reg int l,reg int r){
		tag[o]=1; if (l==r) return; reg int mid=l+r>>1;
		build(o<<1,l,mid),build(o<<1|1,mid+1,r);
	}
	inline void brush(reg int o,reg int k){tag[o]=Mul(tag[o],k),sum[o]=Mul(sum[o],k);}
	inline void pushdown(reg int o){if (tag[o]^1) brush(o<<1,tag[o]),brush(o<<1|1,tag[o]),tag[o]=1;}
	inline void modify(reg int o,reg int l,reg int r,reg int L,reg int R,reg int k){
		if (L<=l&&r<=R) return brush(o,k); pushdown(o); reg int mid=l+r>>1;
		if (L<=mid) modify(o<<1,l,mid,L,R,k); if (R>mid) modify(o<<1|1,mid+1,r,L,R,k); sum[o]=Add(sum[o<<1],sum[o<<1|1]);
	}
	inline int query(reg int o,reg int l,reg int r,reg int L,reg int R){
		if (L<=l&&r<=R) return sum[o]; pushdown(o); reg int mid=l+r>>1,res=0;
		if (L<=mid) Plus(res,query(o<<1,l,mid,L,R)); if (R>mid) Plus(res,query(o<<1|1,mid+1,r,L,R)); return res;
	}
	inline void add(reg int o,reg int l,reg int r,reg int x,reg int k){
		if (l==r) return sum[o]=k,void(); pushdown(o); reg int mid=l+r>>1;
		x<=mid?add(o<<1,l,mid,x,k):add(o<<1|1,mid+1,r,x,k); sum[o]=Add(sum[o<<1],sum[o<<1|1]);
	}
}T;
inline void dfs1(reg int u){
	if (fa[u]) for (reg auto it:e1[fa[u]]) if (dfn[u]<=dfn[it]&&dfn[it]<=dfn[u]+siz[u]-1) sz[u]++;
	for (reg auto v:G[u]) if (v^fa[u]) dfs1(v),sz[u]+=sz[v];
}
inline void dfs2(reg int u){
	sz2[u]+=e2[u].size(); reg int sum=0;
	for (reg auto v:G[u]) if (v^fa[u]) Plus(sum,sz[v]);
	for (reg auto v:G[u]) if (v^fa[u]) sz2[v]=Add(sz2[u],Add(sum,mod-sz[v])),dfs2(v);
}
int A[N],b[N]; 
inline int calc(reg int u){
	reg int tot=0,btot=0; 
	for (reg auto v:G[u]) if (v^fa[u]) b[++btot]=dfn[v]; 
	b[++btot]=dfn[u]+siz[u]; 
	for (reg auto it:vc[u]){
		reg int x=it.first,y=it.second;
		oper[++tot]={dfn[y],dfn[x],dfn[x]+siz[x]-1,2};
		oper[++tot]={dfn[y]+siz[y],dfn[x],dfn[x]+siz[x]-1,(mod+1)>>1};
		b[++btot]=dfn[y],b[++btot]=dfn[y]+siz[y]; 
	}
	std::sort(b+1,b+btot+1),btot=std::unique(b+1,b+btot+1)-b-1;
	std::sort(oper+1,oper+tot+1,[&](Node AA,Node BB){return AA.x<BB.x;});
	for (reg auto v:G[u]) if (v^fa[u]) T.modify(1,1,n,dfn[v],dfn[v]+siz[v]-1,ipw2[sz[v]]);
	for (reg int i=1;i<btot;i++) A[i]=T.query(1,1,n,b[i],b[i+1]-1);
	sons.clear(); for (reg auto v:G[u]) if (v^fa[u]) sons.push_back(v);
	reg int res=0,pos=1;
	for (reg int j=0,i=1;i<btot;i++){
		if (j+1<sons.size()&&b[i]==dfn[sons[j+1]]) j++; 
		while (pos<=tot&&b[i]==oper[pos].x) T.modify(1,1,n,oper[pos].l,oper[pos].r,oper[pos].v),pos++;
		if (dfn[u]+1<=dfn[sons[j]]-1) Plus(res,Mul(A[i],T.query(1,1,n,dfn[u]+1,dfn[sons[j]]-1)));    
	}
	while (pos<=tot) T.modify(1,1,n,oper[pos].l,oper[pos].r,oper[pos].v),pos++;
	return res;
}
inline void Dfs(reg int u){
	for (reg auto v:G[u]) if (v^fa[u]) Dfs(v);
	for (reg auto v:e1[u]) T.modify(1,1,n,dfn[v],dfn[v]+siz[v]-1,2);
	reg int now=1;
	for (reg auto v:G[u]) if (v^fa[u]) now=Mul(now,pw2[sz[v]]);
	reg int dp[5]={0},tmp[5]={0}; dp[0]=1;
	for (reg auto v:G[u]) if (v^fa[u]){
		reg int sum=T.query(1,1,n,dfn[v],dfn[v]+siz[v]-1);
		for (reg int i=0;i<=3;i++) Plus(tmp[i],Mul(dp[i],pw2[sz[v]])),Plus(tmp[min(i+1,3)],Mul(dp[i],sum));
		memcpy(dp,tmp,sizeof(dp)),memset(tmp,0,sizeof(tmp)); 
	}
	Plus(f[u],Add(dp[2],dp[3])); 
	if (key[u]) Plus(f[u],mod-Add(Add(dp[0],dp[1]),Add(dp[2],dp[3])));
	Plus(ans,Mul(Add(Add(f[u],mod-dp[2]),Mul(calc(u),now)),pw2[sz2[u]])); 	
	if (siz[u]>1) T.modify(1,1,n,dfn[u]+1,dfn[u]+siz[u]-1,now);
	T.add(1,1,n,dfn[u],f[u]);
}
signed main(){
	read(),n=read(),m=read(),k=read();
	pw2[0]=1; for (reg int i=1;i<=m;i++) pw2[i]=Add(pw2[i-1],pw2[i-1]);
	ipw2[0]=1; for (reg int i=1;i<=m;i++) ipw2[i]=Mul(ipw2[i-1],(mod+1)>>1);
	for (reg int i=1,u,v;i<n;i++) u=read(),v=read(),G[u].push_back(v),G[v].push_back(u); 
	Dfs1(1),Dfs2(1,1),T.build(1,1,n);
	for (reg int i=1;i<=m;i++){
		reg int u=read(),v=read(),lca=LCA(u,v);
		if (dep[u]>dep[v]) std::swap(u,v);
		if (lca==u) e1[u].push_back(v),e2[v].push_back(u);
		else{
			if (dfn[u]>dfn[v]) std::swap(u,v); 
			vc[lca].push_back({u,v});
			e2[u].push_back(v),e2[v].push_back(u);
		}
	}
	dfs1(1),dfs2(1); while (k--) key[read()]=1; Dfs(1);
	printf("%d\n",(mod-ans)%mod);
	return 0;
}

D2T1

首先发现树高很小,那么每个点暴力跳 \(fa\) 复杂度是对的。
将每次上去一段,再跳回子树,再上去一段的操作看作一条向下的边。由于再次上去的操作不会超过第一次上去的位置(这样成环了),所以转换可行。
统计出每个点到左右子树的最短路,那么一个点对答案的贡献到自己子树和 加上 该点到某祖先另外一个子树的贡献。
至于预处理最短路,可以做一个简单 dp,然后考虑每个点对祖先的贡献。
时间复杂度 \(\Theta((2^n+m)n^2)\)

点击查看代码
#include<bits/stdc++.h>
#define reg register
#define int long long
inline int read(){
	reg int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();} 
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline int min(reg int x,reg int y){return x<y?x:y;}
inline bool cmin(reg int &x,reg int y){return x>y?x=y,1:0;}
const int N=1<<18|10,INF=1e18,mod=998244353;
inline int Add(reg int x,reg int y){return x+y>=mod?x+y-mod:x+y;}
inline int Mul(reg int x,reg int y){return 1ll*x*y>=mod?1ll*x*y%mod:x*y;}
inline void Plus(reg int &x,reg int y){x=Add(x,y);} 
int H,n,m,a[N],len[N][20],dis[N],dep[N];
inline void dfs(reg int x){
	if (x>n) return;
	dep[x]=dep[x>>1]+1,dis[x]=dis[x>>1]+a[x];
	dfs(x<<1),dfs(x<<1|1);
}
inline bool isr(reg int x,reg int y){return y==(x<<1|1);}
int dp[20],Sum[N][2],sz[N][2];
inline void work(reg int x){
	memset(dp,0x3f,sizeof(dp)),dp[0]=0;
	for (reg int i=1;i<dep[x];i++) for (reg int j=0;j<i;j++) cmin(dp[i],dp[j]+len[x>>j][i-j]);
	for (reg int i=1;i<dep[x];i++) if (dp[i]<INF) Plus(Sum[x>>i][isr(x>>i,x>>i-1)],dp[i]%mod),sz[x>>i][isr(x>>i,x>>i-1)]++;
}
signed main(){
	H=read(),n=(1<<H)-1,m=read();
	for (reg int i=2;i<=n;i++) a[i]=read(); dfs(1);
	for (reg int i=1;i<=n;i++) memset(len[i],0x3f,sizeof(int)*(H+1)),len[i][0]=0;
	for (;m--;){
		reg int u=read(),v=read(),w=read(),x=v,h=0;
		while (x^u) h++,x>>=1;
		for (reg int i=0;i<=h;i++) for (reg int j=i+1;j<=h;j++)
			cmin(len[v>>i][j-i],w+dis[v]-dis[v>>i]+dis[v>>j]-dis[u]);
	}
	for (reg int i=1;i<=n;i++) work(i);
	reg int ans=0;
//	for (reg int i=1;i<=n;i++,puts("")) printf("%lld %lld",Sum[i][0],Sum[i][1]);
	for (reg int x=1;x<=n;x++){
		Plus(ans,Add(Sum[x][0],Sum[x][1]));
		reg int sum=a[x];
		for (reg int i=1,op;i<dep[x];i++){
			op=isr(x>>i,x>>i-1)^1;
//			printf("%d %d %d %d\n",x,op,sz[x>>i][op],Sum[x>>i][op]);
			Plus(ans,Add(Mul(sum,sz[x>>i][op]+1),Sum[x>>i][op]));
			Plus(sum,a[x>>i]); 
		}	
	} 
	printf("%lld",ans);
	return 0;
}

D2T2

首先有一种 bitset 的暴力做法是小常数 \(O({n^2 \over \omega})\),可以直接通过,这里就不讲了。
首先,一个方案合法就是前缀的字典序小于后缀的。
这样我们可以对原串和反串的做后缀排序,然后归并起来,这样位置 \(j\) 对于一个询问 \((i,len)\) 合法,当且仅当 \(rk_i<rk_j\)\(i \le j \le i+2len-1\)
这是一个二维数点,可以直接离线树状数组。
但是还是有情况不合法,就是回文串。
怎么样的回文串是会被统计的呢?对于一个回文串 \(S[l:r]\),他会被统计到当且仅当 \(S[l-1]>S[r+1]\)。这样的回文串是 \(O(n)\) 的,他会对 \(l \le i \le mid\)\(i\)\(i\) 对应的 \(len\) 可以覆盖到的询问产生影响,也是一个数点的形式,扫描线即可。
复杂度为 \(\Theta((n+q) \log n)\),代码中用哈希维护了一些东西。

点击查看代码
#include<bits/stdc++.h>
#define reg register
//#define int long long
inline int read(){
	reg int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();} 
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline int min(reg int x,reg int y){return x<y?x:y;}
const int N=2e5+10;
int n,q; 
char str[N];
struct Node{int l,len,id;}qry[N];
struct SA{
	int sa[N],x[N],y[N],c[N],rk[N],st[N][20],h[N],m;
	inline void build(reg char *s){ 
		m=26; memset(y+1,0,n<<4);
		memset(c,0,sizeof(int)*(m+1)); reg int num=0; memset(x,0,sizeof(x));
		for (reg int i=1;i<=n;i++) c[x[i]=s[i]-'a'+1]++;
		for (reg int i=1;i<=m;i++) c[i]+=c[i-1];
		for (reg int i=n;i;i--) sa[c[x[i]]--]=i;
		for (reg int k=1;k<=n;k<<=1){
			num=0;for (reg int i=n;i+k>n;i--) y[++num]=i;
			for (reg int i=1;i<=n;i++) if (sa[i]>k) y[++num]=sa[i]-k;
			memset(c,0,sizeof(int)*(m+1)); for (reg int i=1;i<=n;i++) c[x[i]]++;
			for (reg int i=1;i<=m;i++) c[i]+=c[i-1];
			for (reg int i=n;i;i--) sa[c[x[y[i]]]--]=y[i]; num=0,std::swap(x,y),x[sa[1]]=++num; 
			for (reg int i=2;i<=n;i++) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?num:++num);
			if ((m=num)==n) break;
		}
		for (reg int i=1;i<=n;i++) rk[i]=x[i];
	}
}A,B;
int sa[N],rk[N],ans[N],tot,crk;
std::vector<std::pair<int,int> > vc[N];
struct BIT{
	int c[N];
	inline void clear(){memset(c,0,sizeof(int)*((n<<1)+1));}
	inline void mdf(reg int x,reg int k){for (;x<=n<<1;x+=x&-x) c[x]+=k;}
	inline int qry(reg int x){reg int res=0;for (;x;x-=x&-x) res+=c[x]; return res;}
}T[2],tr;
#define Hash std::pair<int,int>
#define mp std::make_pair
const Hash base={13,131},mod={19260817,1e9+7};
Hash pw[N],pre[N],suf[N];
inline Hash operator+(reg Hash A,reg Hash B){return {(A.first+B.first)%mod.first,(A.second+B.second)%mod.second};}
inline Hash operator-(reg Hash A,reg Hash B){return {(A.first-B.first+mod.first)%mod.first,(A.second-B.second+mod.second)%mod.second};}
inline Hash operator*(reg Hash A,reg Hash B){return {1ll*A.first*B.first%mod.first,1ll*A.second*B.second%mod.second};} 
inline bool chk(reg int l1,reg int r1,reg int l2,reg int r2,reg int op=0){
	reg Hash h1=pre[r1]-pre[l1-1]*pw[r1-l1+1],h2=suf[l2]-suf[r2+1]*pw[r2-l2+1];
	return h1==h2;
}
inline int cmp(reg int L,reg int R){
	reg int now=0,l=1,r=min(n-L+1,R);
	while (l<=r){reg int mid=l+r>>1; if (chk(L,L+mid-1,R-mid+1,R)) l=mid+1,now=mid; else r=mid-1;}
	if (now==n-L+1&&now==R) return -1;
	if (now==n-L+1||now==R) return now==R;
	return str[L+now]>str[R-now];
}
inline void merge(){
	reg int i=1,j=1;
	while (i<=n&&j<=n){
		if (!cmp(A.sa[i],B.sa[j])) sa[++tot]=A.sa[i],i++;
		else sa[++tot]=B.sa[j]+n,j++;
	}
	while (i<=n) sa[++tot]=A.sa[i],i++; while (j<=n) sa[++tot]=B.sa[j]+n,j++;
	rk[sa[1]]=1;
	for (reg int i=2;i<=n<<1;i++){
		reg int x=sa[i],y=sa[i-1];
		if (x<=n&&y<=n||x>n&&y>n) rk[x]=rk[y]+1;
		else{
			if (x>n&&cmp(y,x-n)>=0) rk[x]=rk[y]+1;
			else if (y>n&&cmp(x,y-n)>=0) rk[x]=rk[y]+1;
			else rk[x]=rk[y];  
		}
	} 
}
signed main(){
	read(); reg int _=read();
	while (_--){
		n=read(),q=read(),scanf("%s",str+1);
		pw[0]={1,1},pre[0]=suf[n+1]={0,0},tot=0;
		for (reg int i=1;i<=n;i++) pw[i]=pw[i-1]*base;
		for (reg int i=1;i<=n;i++) pre[i]=pre[i-1]*base+mp(str[i]-'a'+1,str[i]-'a'+1);
		for (reg int i=n;i;i--) suf[i]=suf[i+1]*base+mp(str[i]-'a'+1,str[i]-'a'+1); 	
		A.build(str),std::reverse(str+1,str+n+1),B.build(str),std::reverse(str+1,str+n+1); 
		for (reg int i=1;i<=n;i++) B.sa[i]=n-B.sa[i]+1;
		merge(); 
		for (reg int i=1;i<=q;i++) qry[i].l=read(),qry[i].len=read(),qry[i].id=i;
		T[0].clear(),T[1].clear();
		std::sort(qry+1,qry+q+1,[&](reg Node A,reg Node B){return rk[A.l]>rk[B.l];});
		for (reg int i=1,j=n<<1;i<=q;i++){
			while (j&&rk[sa[j]]>rk[qry[i].l]){
				if (sa[j]>n) T[(sa[j]-n)&1].mdf(sa[j]-n,1);
				j--;
			}
			reg int op=~qry[i].l&1;
			ans[qry[i].id]=T[op].qry(qry[i].l+(qry[i].len<<1)-1)-T[op].qry(qry[i].l);
		}  
		for (reg int i=1;i<=n;i++) vc[i].clear();
		for (reg int i=1;i<=n;i++){
			reg int now=0,l=1,r=min(n-i,i);
			while (l<=r){reg int mid=l+r>>1; if (chk(i-mid+1,i,i+1,i+mid)) l=mid+1,now=mid; else r=mid-1;}
			if (now&&str[i-now]>str[i+now+1]) vc[i-now].push_back({i,-1}),vc[i].push_back({i,1});
		}
		std::sort(qry+1,qry+q+1,[&](reg Node A,reg Node B){return A.l>B.l;});
		for (reg int i=n,j=1;i;i--){
			for (reg auto it:vc[i]) tr.mdf(it.first,it.second); 
// 			while (j<=q&&qry[j].l==i) ans[qry[j].id]-=tr.qry(qry[j].l+qry[j].len-1)-tr.qry(qry[j].l-1),j++;
		}
		for (reg int i=1;i<=q;i++) printf("%d\n",ans[i]);
	} 
	return 0;
}

D2T3

考虑观察操作树:这个操作树类似于线段树,且最优情况下他的长剖和重剖是一样的。
考虑重的书本一定往底下放,证明考虑调整发。
统计贡献时,轻边表示一次合并,长链上的磨损度累加。
将链缩成点,一共有 \(n\) 个,由于类似线段树的性质,他的儿子数为自身长度减 \(1\)
统计答案现在只关心点度和深度了,我们可以搜出新树,然后不断扩展,最大点的点度比较小,直接枚举,搜索过程中考虑先钦定取到最大点度,再每次调整一个最大度数减小。
时间复杂度大约是划分数级别的。

点击查看代码
#include<bits/stdc++.h>
#define reg register
#define int long long
inline int read(){
	reg int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9'){if(ch=='-') k=-1;ch=getchar();} 
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline bool cmin(reg int &x,reg int y){return x>y?x=y,1:0;}
const int N=5e5+10,mod=1e9+7,INF=1e18;
int n,a[N],sum[N],ans; 
#define ar std::vector<int>
inline void dfs(reg std::vector<ar> A,reg int step){
	reg int Sum=0;
	for (reg auto it:A.back()) Sum+=it-1;
	if (step+Sum>n) return; 
	if (step==n){
		reg int now=0,pos=1;
		for (reg int i=0;i<A.size();i++){
			if (i) for (reg auto it:A[i]) now+=sum[it+1]-sum[it]; 
			for (reg auto it:A[i]) now+=i*a[pos++],now+=sum[it];
		}
		return cmin(ans,now),void();
	}
	ar now;
	for (reg auto it:A.back()) for (reg int i=1;i<it;i++) now.push_back(i);
	std::sort(now.begin(),now.end()); if (now.empty()) return; A.push_back(now); 
	while (1){
		dfs(A,step+now.size()); if (A.back().back()==1) break;  
		for (reg int i=A.back().size()-1;i>=0;i--) if (A.back()[i]!=A.back().back()){A.back()[i+1]--;break;}
	}
}
inline void work(){
	n=read(),ans=INF; for (reg int i=1;i<=n;i++) a[i]=read();
	std::sort(a+1,a+n+1,[&](reg int x,reg int y){return x>y;});
	std::vector<ar> st; 
	for (reg int i=1;i<=50;i++){
		ar fi; fi.push_back(i);
		st.push_back(fi),dfs(st,1),st.pop_back();
	}  
	printf("%lld\n",ans);
}
signed main(){
	for (reg int i=2;i<=50;i++) sum[i]=sum[i-1]+(1ll<<i-2)-1; 
	for (reg int _=read();_--;) work();
	return 0;
}