打的太 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;
}

NOI2023 题解的更多相关文章

  1. 2016 华南师大ACM校赛 SCNUCPC 非官方题解

    我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...

  2. noip2016十连测题解

    以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...

  3. BZOJ-2561-最小生成树 题解(最小割)

    2561: 最小生成树(题解) Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1628  Solved: 786 传送门:http://www.lyd ...

  4. Codeforces Round #353 (Div. 2) ABCDE 题解 python

    Problems     # Name     A Infinite Sequence standard input/output 1 s, 256 MB    x3509 B Restoring P ...

  5. 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解

    题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...

  6. 2016ACM青岛区域赛题解

    A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Jav ...

  7. poj1399 hoj1037 Direct Visibility 题解 (宽搜)

    http://poj.org/problem?id=1399 http://acm.hit.edu.cn/hoj/problem/view?id=1037 题意: 在一个最多200*200的minec ...

  8. 网络流n题 题解

    学会了网络流,就经常闲的没事儿刷网络流--于是乎来一发题解. 1. COGS2093 花园的守护之神 题意:给定一个带权无向图,问至少删除多少条边才能使得s-t最短路的长度变长. 用Dijkstra或 ...

  9. CF100965C题解..

    求方程 \[ \begin{array}\\ \sum_{i=1}^n x_i & \equiv & a_1 \pmod{p} \\ \sum_{i=1}^n x_i^2 & ...

  10. JSOI2016R3 瞎BB题解

    题意请看absi大爷的blog http://absi2011.is-programmer.com/posts/200920.html http://absi2011.is-programmer.co ...

随机推荐

  1. SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘

    SpringBoot 集成 SpringSecurity + MySQL + JWT 无太多理论,直接盘 一般用于Web管理系统 可以先看 SpringBoot SpringSecurity 基于内存 ...

  2. Prism Sample 16-RegionContext

    终于发现一个有趣的新知识了. 本例的核心是RegionContext,意思是一个区域的上下文.但与DataContext似乎并不相同. 先看一下整体思路. 在主窗体上只有一个Region: <G ...

  3. 前端模拟“多线程”提交Http请求

    首先说,javascript没有多线程这样一个说法,我说的只是类似那种效果.其次,不建议使用这种方式解决问题,多线程应该交给后台去做. 但是,如果非要这样用,有什么方法呢? 我在工作中就遇到了这样的问 ...

  4. 2023-04-16:给定一个长度为N的数组,值一定在0~N-1范围,且每个值不重复 比如,arr = [4, 2, 0, 3, 1] 0 1 2 3 4 把0想象成洞

    2023-04-16:给定一个长度为N的数组,值一定在0~N-1范围,且每个值不重复 比如,arr = [4, 2, 0, 3, 1] 0 1 2 3 4 把0想象成洞,任何非0数字都可以来到这个洞里 ...

  5. 2021-07-19:给定一个正数N,比如N = 13,在纸上把所有数都列出来如下: 1 2 3 4 5 6 7 8 9 10 11 12 13,可以数出1这个字符出现了6次,给定一个正数N,如果把1

    2021-07-19:给定一个正数N,比如N = 13,在纸上把所有数都列出来如下: 1 2 3 4 5 6 7 8 9 10 11 12 13,可以数出1这个字符出现了6次,给定一个正数N,如果把1 ...

  6. 深入 Hyperf:HTTP 服务启动时发生了什么?

    当我们创建 Hyperf 项目之后,只需要在终端执行 php bin/hyperf.php start 启动命令,等上几秒钟,就可以看到终端输出的 Worker 进程已启动,HTTP 服务监听在 95 ...

  7. 反向传播(Backpropagation)相关思想

    在前面我们学习了SVM损失函数和softmax损失函数,我们优化权重矩阵w的具体思路便是让损失函数最小化,还记得损失函数的定义吗? 没错,损失函数长这样,其中,Wj为权重矩阵的第j个列向量,xi为第i ...

  8. 使用 coding.net 发布你的个人博客

    微信文章不允许外链,本文章的静态示例站点,可在文章左下角 "阅读原文" 进行预览. 很多人喜欢在 github pages / gitee pages 发布自己的个人博客,前者由于 ...

  9. RedHat7.4配置yum源(原创!详细易懂)

    redhat7 .4配置centOS yum源(自带yum文件) 1.定位到yum的配置文件 root@192.168.6.129:/etc# cd yum.repos.d 2.检查yum是否安装,以 ...

  10. 深入理解 Istio 流量管理的超时时间设置

    环境准备 部署 httpbin 服务: kubectl apply -f samples/httpbin/httpbin.yaml 部署 sleep 服务: kubectl apply -f samp ...