终于把坑给填完了,哈哈。
感谢小魏: f u y u k i fuyuki fuyuki和蒋巨: B J p e r s 2 BJpers2 BJpers2的指导。
感谢小魏: f u y u k i fuyuki fuyuki造的 D a y 2 Day2 Day2数据。
s o l u t i o n solution solution
D 1 T 1 D1T1 D1T1
真丶送分题。
发现每次换到的物品是固定的,具体来说就是对每一维都维护一棵线段树,用线段树二分找到后面第一个不满足的点即可。复杂度 O ( n k l o g n ) O(nklogn) O(nklogn).
代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int s=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s;
}
const int N=201010,K=21;
int w[N][K],p[N],g[K],fa[N],n,k,q;
int find (int x){return fa[x]==x?fa[x]:fa[x]=find(fa[x]);}
void get(int *x,int *y){for(int i=1;i<=k;i++) x[i]=y[i];}
#define mid ((l+r)/2)
#define lc (u<<1)
#define rc (u<<1|1)
#define ls lc,l,mid
#define rs rc,mid+1,r
#define cs 1,1,n
struct SEG
{
int mx[N<<2];
void pushup(int u){mx[u]=max(mx[lc],mx[rc]);}
void change(int u,int l,int r,int k,int x)
{
if(l==r) {mx[u]=x;return ;}
if(k<=mid) change(ls,k,x);
if(mid<k) change(rs,k,x);
pushup(u);
}
int find (int u,int l,int r,int x)
{
if(mx[u]<=x) return n+1;
if(l==r) return l;
int ans=find(ls,x);
return ans!=n+1?ans:find(rs,x);
}
int query(int u,int l,int r,int L,int R,int x)
{
if(mx[u]<=x) return n+1;
if(L<=l&&r<=R) return find(u,l,r,x);
int ans=n+1;
if(L<=mid) ans=query(ls,L,R,x);
if(ans!=n+1) return ans;
if(mid<R) ans=query(rs,L,R,x);
return ans;
}
}s[K];
int GET(int *w,int x)
{
int e=n+1;
for(int i=1;i<=k;i++) e=min(e,s[i].query(cs,x+1,n,w[i]));
return e;
}
int main()
{
freopen("t1.in","r",stdin);
freopen("t1.out","w",stdout);
n=read(),k=read(),q=read();
for(int i=1;i<=n;i++)p[i]=read(),fa[i]=i;
for(int i=1;i<=n;i++)for(int j=1;j<=k;j++) w[i][j]=read();
for(int i=1;i<=n;i++) s[p[i]].change(cs,i,w[i][p[i]]);
for(int i=n-1;i>=1;i--) {
int t=GET(w[i],i);
if(t!=n+1) fa[i]=find(t);
}
for(int i=1;i<=q;i++) {
for(int j=1;j<=k;j++) w[0][j]=read();
int t=fa[GET(w[0],0)]%(n+1);
for(int j=1;j<=k;j++) cout<<w[t][j]<<' ';
cout<<'\n';
}
}
D 1 T 2 D1T2 D1T2
发现任意时刻的边数 < = n <=n <=n,且每一个点最多会有一条出边。
也就是说,图时刻构成了一棵基环内向树。
我们暴力拿 L C T LCT LCT维护之即可。
具体来说就是对于一个普通的树,我们直接维护;对于基环树,我们任选一个环上的点作为根节点并隐式维护其出边即可。
在 q u e r y ( x , k ) query(x,k) query(x,k)的时候就是链上最值/链加的操作,用splay可以很方()便()地解决链上操作。
C U T CUT CUT和 L I N K LINK LINK也很容()易()解决。
细节参见代码。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
int s=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s;
}
#define lc s[x][0]
#define rc s[x][1]
const int N=1002000,INF=1e15;
struct pr{int next,to,w;}a[N];
struct pa{int id,val;pa (int x=0,int y=0){id=x;val=y;}}w[N],mi[N];
bool operator < (pa x,pa y){return x.val<y.val;}
int head[N],tt,s[N][2],fa[N],sta[N],top,sz[N],rev[N],lz[N],st[N],tp,n,m,q;
void add(int u,int v,int w){a[++tt]=(pr){head[u],v,w};head[u]=tt;}
bool chk(int x){return s[fa[x]][1]==x;}
bool nroot(int x){return s[fa[x]][1]==x||s[fa[x]][0]==x;}
int nx(int x) {return a[head[x]].to;}
void pushup(int x){mi[x]=min(w[x],min(mi[lc],mi[rc]));sz[x]=sz[lc]+sz[rc]+1;}
void psp(int x){rev[x]^=1;swap(lc,rc);}
void psp(int x,int v){lz[x]+=v;w[x].val+=v;mi[x].val+=v;}
void pushdown(int x)
{
if(rev[x]) psp(lc),psp(rc),rev[x]=0;
if(lz[x]) psp(lc,lz[x]),psp(rc,lz[x]),lz[x]=0;
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=chk(x),w=s[x][k^1];
if(nroot(y)) s[z][chk(y)]=x;
fa[x]=z;
s[x][k^1]=y;fa[y]=x;
s[y][k]=w;
if(w)fa[w]=y;
pushup(y),pushup(x);
}
void splay(int x,int goal=0)
{
if(!x) return ;
int y=x;
sta[++top]=x;
while(nroot(y)) sta[++top]=fa[y],y=fa[y];
while(top) pushdown(sta[top]),top--;
while(nroot(x)&&goal!=fa[x]) {
if(nroot(y)&&goal!=fa[y]) chk(x)==chk(y)?rotate(x):rotate(y);
rotate(x);
}
}
int kth(int x,int k)
{
if(sz[x]<k) return 0;
while(x) {
pushdown(x);
if(sz[lc]+1==k) return x;
else if(sz[lc]>=k) x=lc;
else k-=sz[lc]+1,x=rc;
}
return 0;
}
int findfirst(int x)
{
splay(x);
pushdown(x);
while(lc) x=lc,pushdown(x);
splay(x);return x;
}
int nxt(int x)
{
splay(x);
int y=s[x][1];
pushdown(y);
while(s[y][0]) y=s[y][0],pushdown(y);
splay(y);
return y;
}
void merge(int x,int y){splay(x);splay(y,x);}
int access(int x){int y=0;for(;x;y=x,x=fa[x]) splay(x),rc=y,pushup(x);return y;}
void makeroot(int x){access(x);splay(x);psp(x);pushdown(x);}
int rt(int x){x=access(x);return findfirst(x);}
void link(int x,int y){makeroot(x);fa[x]=y;}
void cut(int x,int y)
{
int d=rt(y);
makeroot(x);
access(y);
splay(y);
fa[x]=s[y][0]=0;
pushup(y);
makeroot(d);
}
int inring(int x)
{
int t=rt(x);
if(!nx(t)) return 0;
access(nx(t));
return findfirst(x)==t;
}
void CUT(int x)
{
int RT=rt(x);
if(x!=RT) {
if(inring(x)) cut(x,nx(x)),link(RT,nx(RT));
else cut(x,nx(x));
}
makeroot(x);
head[x]=a[head[x]].next;
}
void LINK(int x)
{
int y=nx(x),k=a[head[x]].w;
if(!y) return ;
w[x].val=k;
if(rt(x)!=rt(y)) link(x,y);
else makeroot(x);
}
void tick(int x){CUT(x);LINK(x);}
void DEL(int x){if(mi[x].val) return ;pushdown(x);if(w[x].val==0) st[++tp]=x;DEL(lc);DEL(rc);}
void del(int x,int y,int v)//order in x<-..<-y
{
splay(x);
w[x].val-=v;
if(y!=x) {
int t=nxt(y);
(!t)?merge(x,y),psp(y,-v):merge(x,t),psp(s[t][0],-v);
}
pushup(x);
DEL(x);
while(st[tp]) tick(st[tp]),tp--;
}
int query(int x,int k)
{
if(!nx(x)||!k) return x;
int y=rt(x);
if(y==x) {
int d=access(nx(x)),tme=min(mi[d].val,k/sz[d]);
if(!tme) {
int To=nx(x);
del(x,x,1);
return query(To,k-1);
}
else {
k-=sz[d]*tme;
del(x,nx(x),tme);
return query(x,k);
}
}
merge(y,x);
int len=min(sz[lc]+1,k),FI=kth(x,sz[lc]+1-len+1),To=nx(FI);
del(FI,x,1);
return query(To,k-len);
}
void pre()
{
for(int i=0;i<=n;i++) mi[i]=w[i]=pa(i,INF);
for(int i=1;i<=n;i++) sz[i]=1,LINK(i);
}
signed main()
{
freopen("data.in","r",stdin);
freopen("t2.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=m;i++) {
int x=read(),y=read(),w=read();
add(x,y,w);
}
pre();
q=read();
for(int i=1;i<=q;i++) {
int x=read(),k=read();
cout<<query(x,k)<<'\n';
}
}
/*
2 2
2 1 9
1 2 9
5
1 2
2 1
2 7
1 3
2 2
*/
D 1 T 2 D1T2 D1T2
题意简述:如果两点在树上的距离 < = X <=X <=X则连一条边,求点集为 [ l , r ] [l,r] [l,r]所得到的导出子图的联通块个数。
设图为 G G G。
为了简化问题,我们可以考虑给 G G G赋一组拓扑序,再将每一条边变成沿拓扑序小的点指向拓扑序较大的点的一条有向边。
这样做有什么好处?我们发现,连通块的个数=0入度点的个数。这样我们统计的就变成0度点的个数。
由于点构成树形结构,一个显然的拓扑序就是按照原图以任意一点为根所构成的树。
根据扫描线的套路,我们计算一个点 x x x是否是 0 0 0度点,则维护一组最小的满足条件的取值。换句话说,我们只需要找到能够使得 x x x不为 0 0 0度点的离 x x x最近的两个点即可,记为 l x , r x lx,rx lx,rx。只有当 l x > L lx>L lx>L&& R < r x R<rx R<rx, [ L , R ] [L,R] [L,R]的贡献++。不难发现这个点需要满足的条件有以下两条:1.拓扑序比 x x x小;2. d i s ( x , y ) < = X dis(x,y)<=X dis(x,y)<=X。
则我们按照拓扑序依次加入点,设当前插入到 x x x,则我们需要每次查找前面加入中的满足 d i s ( x , y ) < = X dis(x,y)<=X dis(x,y)<=X 的 x x x的前驱 y y y和后继 y y y。
可以通过点分树+线段树二分实现。具体来说就是每一个点分树上的点 x x x维护一个以标号为下标到 x x x距离为权值的平衡树(动态开点线段树空间会炸)。
还有一种方法是在每一个节点上把对应节点下标离散化一波。这样每一棵线段树都是满线段树了,空间复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn).
处理出 l x , r x lx,rx lx,rx就变成一个三维关系:
设查询区间为 [ L , R ] [L,R] [L,R]。
则 a n s = ∑ x = L R l x < L & & R < r x ans=\sum_{x=L}^Rlx<L \&\& R<rx ans=∑x=LRlx<L&&R<rx
自由发挥即可。
总复杂度 O ( n l o g 2 n + q l o g 2 n ) O(nlog^2n+qlog^2n) O(nlog2n+qlog2n)。
但是这样还是不够优秀,我们考虑询问的部分:
计算他的补集,答案为 a n s = R − L + 1 − ∑ x = L R L < = l x ∣ ∣ r x < = R ans=R-L+1-\sum_{x=L}^R L<=lx||rx<=R ans=R−L+1−∑x=LRL<=lx∣∣rx<=R
后面那一些: ∑ x ( L < = l x & & x < = R ) ∣ ∣ ( L < = x & & r x < = R ) \sum_x (L<=lx \&\& x<=R)||(L<=x\&\&rx<=R) ∑x(L<=lx&&x<=R)∣∣(L<=x&&rx<=R)
∑ x ( L < = l x & & x < = R ) + ∑ x ( L < = x & & r x < = R ) − ∑ x ( L < = l x & & r x < = R ) \sum_x (L<=lx \&\& x<=R)+\sum_x(L<=x\&\&rx<=R)-\sum_x(L<=lx\&\&rx<=R) ∑x(L<=lx&&x<=R)+∑x(L<=x&&rx<=R)−∑x(L<=lx&&rx<=R)
这样就变成了三个二维关系,复杂度就是 O ( n l o g 2 n + q l o g n ) O(nlog^2n+qlogn) O(nlog2n+qlogn)了
完整代码 ( n 2 l o g n + q l o g n ) (n^2logn+qlogn) (n2logn+qlogn):
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)/2)
#define ls lc,l,mid
#define rs rc,mid+1,r
#define REP(x) for(int i=head[x],y=a[i].to;i;i=a[i].next,y=a[i].to) if(y!=fa&&!vis[y])
//#define gc getchar()
#define gc PA == PB && (PB = (PA = buf) + fread(buf, 1, 1000000, stdin), PA == PB) ? EOF : *PA++
char buf[1000000], *PA = buf, *PB = buf;
inline int read()
{
int s=0;
char ch=gc;
while(ch<'0'||ch>'9') ch=gc;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=gc;
return s;
}
const int N=1000010,INF=N;
struct pr{int next,to;}a[N<<1];
int head[N],dis[N],le[N],D[N][21],tt,n,X,q,id[N],f[N],sz[N],F[N],vis[N],L[N],R[N],sum,rt,tot,ans[N];
struct bit{
int c[N];
void add(int x,int w){while(x<=n+1) c[x]+=w,x+=x&(-x);}
int qu(int x){if(x==-1) return 0;int sum=0;while(x) sum+=c[x],x-=x&(-x);return sum;}
int query(int l,int r){return qu(r)-qu(l-1);}
}BIT;
struct pa{int l,r,id,w;}e[N<<2];
bool operator < ( pa x, pa y ) {
if(x.l!=y.l) return x.l>y.l;
if(x.r!=y.r) return x.r<y.r;
return x.id<y.id;
}
#define pb push_back
void clear(vector<pa>&s) {vector<pa>t;swap(s,t);}
void add(int u,int v){a[++tt]=(pr){head[u],v};head[u]=tt;}
bool cmp(int x,int y){return dis[x]<dis[y];}
void getdis(int x,int fa){dis[x]=dis[fa]+1;REP(x) getdis(y,x);}
void getrt(int x,int fa)
{
f[x]=0;sz[x]=1;
REP(x) getrt(y,x),f[x]=max(f[x],sz[y]),sz[x]+=sz[y];
f[x]=max(f[x],sum-sz[x]);
if(f[x]<f[rt]) rt=x;
}
void getdis(int x,int fa,int lv){D[x][lv]=D[fa][lv]+1;REP(x) getdis(y,x,lv);}
void solve(int x,int fa,int lv)
{
le[x]=lv;
vis[x]=1;
F[x]=fa;
getrt(x,0);
getdis(x,0,lv);
REP(x) {
sum=sz[y];rt=0;
getrt(y,0);
solve(rt,x,lv+1);
}
}
#define lc s[x][0]
#define rc s[x][1]
int s[N*10][2],fa[N*10],zb[N*10],w[N*10],mi[N*10],cnt;
struct SY
{
int rt;
void pushup(int x){mi[x]=min(w[x],min(mi[lc],mi[rc]));}
bool chk(int x){return s[fa[x]][1]==x;}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=chk(x),w=s[x][k^1];
s[z][chk(y)]=x;fa[x]=z;
s[x][k^1]=y;fa[y]=x;
s[y][k]=w;fa[w]=y;
pushup(y),pushup(x);
}
void splay(int x,int goal=0)
{
while(fa[x]!=goal) {
int y=fa[x];
if(fa[y]!=goal) chk(y)==chk(x)?rotate(y):rotate(x);
rotate(x);
}
pushup(x);
if(!goal) rt=x;
}
int NEW(int k,int t){++cnt;zb[cnt]=k;w[cnt]=t;return cnt;}
int insert(int k,int t)
{
if(!rt) {rt=NEW(k,t);return rt;}
int x=rt,ff=0;
while(x&&zb[x]!=k) ff=x,x=s[x][zb[x]<k];
int g=NEW(k,t);
fa[g]=ff;s[ff][zb[ff]<k]=g;
splay(g);
return g;
}
int findl(int x,int k)
{
while(x) {
if(mi[x]>k) return n+1;
if(mi[lc]<=k) x=lc;
else if(w[x]<=k) return zb[x];
else x=rc;
}
return n+1;
}
int findr(int x,int k)
{
while(x) {
if(mi[x]>k) return 0;
if(mi[rc]<=k) x=rc;
else if(w[x]<=k) return zb[x];
else x=lc;
}
return 0;
}
}T[N];
void math(int x)
{
int d=le[x],g=x;
L[x]=0;R[x]=n+1;
while(d) {
int t=T[g].insert(x,D[x][d]);
L[x]=max(L[x],T[g].findr(s[t][0],X-D[x][d]));
R[x]=min(R[x],T[g].findl(s[t][1],X-D[x][d]));
d--,g=F[g];
}
}
void work()
{
for(int i=1;i<=q;i++) {
++tot;
e[i].l=read(),e[i].r=read();e[i].id=i;
ans[e[i].id]+=(e[i].r-e[i].l+1);
}
for(int i=1;i<=n;i++) e[++tot]=(pa){L[i],R[i],0,-1},e[++tot]=(pa){L[i],i,0,1},e[++tot]=(pa){i,R[i],0,1};
sort(e+1,e+tot+1);
for(int i=1;i<=tot;i++) {
if(e[i].id==0) BIT.add(e[i].r,e[i].w);
else ans[e[i].id]-=BIT.query(1,e[i].r);
}
}
int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n=read(),X=read();q=read();
for(int i=1;i<n;i++) {int x=read(),y=read();add(x,y);add(y,x);}
dis[0]=-1;getdis(1,0);
for(int i=0;i<=20;i++) D[0][i]=-1;
sum=f[0]=n;
getrt(1,0);
solve(rt,0,1);
cerr<<clock()<<'\n';
for(int i=1;i<=n;i++) id[i]=i;
sort(id+1,id+n+1,cmp);
mi[0]=INF;
for(int i=1;i<=n;i++) T[i].insert(0,INF),T[i].insert(n+1,INF);
for(int i=1;i<=n;i++) math(id[i]);
// for(int i=1;i<=n;i++) cerr<<L[i]<<' ';cerr<<'\n';
// for(int i=1;i<=n;i++) cerr<<R[i]<<' ';cerr<<'\n';
cerr<<clock()<<'\n';
work();
for(int i=1;i<=q;i++) cout<<ans[i]<<'\n';
cerr<<clock()<<'\n';
return 0;
}
D 2 T 1 D2T1 D2T1
考虑如果没有 c c c的话,我们只需要存四个量即可解决问题,绝对值最大的正、负数,最小的正、负数。之所以还要记录绝对值最小的情况,是因为会出现如 ( − 1 , 0 , 0 ) (-1,0,0) (−1,0,0)这种东西,你必须用一个较靠近 0 0 0的东西来抵消这个贡献。
如果有 c c c的话,因为前面的 a ∣ x ∣ + b x a|x|+bx a∣x∣+bx,除非 ( 0 , 0 , . . . ) (0,0,...) (0,0,...),否则新的 ∣ x ∣ |x| ∣x∣一定 > = ∣ x ∣ >=|x| >=∣x∣。当前者为 0 0 0时是平凡的,不予考虑。
c c c给我们带来的影响,即把所有答案集合沿着数轴平移 c c c。为了维护绝对值最小值,我们额外记录一个数组来表示 ( − n c , n c ) (-nc,nc) (−nc,nc)是否可能得到即可。
复杂度 O ( 2 n ∗ n ∗ n c ∗ 2 ) O(2^n*n*nc*2) O(2n∗n∗nc∗2),跑的飞快。
#include<bits/stdc++.h>
using namespace std;
typedef __int128 LL;
typedef long long ll;
const int N=17,S=1<<N,V=550,B=230;
bool g[S][V];
LL f[S][2],INF;
int a[N],b[N],c[N],n,blo,x;
ll bin[110];
bool in(int i,int s) {
return bin[i]&s;
}
LL math(LL S,int k){return a[k]*abs(S)+b[k]*S+c[k];}
void upd(int S,LL k){f[S][0]=max(f[S][0],k);f[S][1]=min(f[S][1],k);}
void out(LL S)
{
if(S<0) cout<<'-',S=abs(S);
if(S>9) out(S/10);
cout<<(int)(S%10);
}
int main() {
freopen("report.in","r",stdin);
freopen("report.out","w",stdout);
cin>>n>>x;
for(int i=0; i<n; i++) cin>>a[i]>>b[i]>>c[i];
blo=225;
bin[0]=1;for(int i=1; i<=60; i++) bin[i]=bin[i-1]*2;
INF=15;for(int i=0;i<15;i++) INF=INF*30+15;
g[0][B+x]=1;f[0][1]=f[0][0]=x;
int mx = (1<<n)-1;
for(int S=1; S<=mx; S++) {
f[S][0]=-INF,f[S][1]=INF;
for(int i=0; i<n; i++) {
if(in(i,S)) {
int T=S^bin[i];
for(int j=B-blo; j<=B+blo; j++) {
if(g[T][j]) {
int k=math(j-B,i);
if(abs(k)<=blo) g[S][B+k]=1;
upd(S,k);
}
}
upd(S,math(f[T][0],i));upd(S,math(f[T][1],i));
}
}
}
out(f[mx][0]);
}
D 2 T 2 D2T2 D2T2
写的是求半支配点法。
有一个小错误调到自闭,调了我2h。。。
如果知道了 f [ i ] f[i] f[i]表示 i i i的最浅祖先,满足去掉 f [ i ] f[i] f[i]到 r t rt rt的路径上的树边 1 1 1仍能到达 i i i,就可以方便地利用线段树合并解决这个问题。
发现这个定义很类似半支配点,唯一不同的是这个是边,而半支配点求出来的其实是点。我们只需要在用边更新当前点的时候不考虑 f a [ i ] fa[i] fa[i]即可。
考虑为什么这样做,可以把边附在出点上,这样的话初始状态就是 f [ i ] = i f[i]=i f[i]=i而不是 f [ i ] = f a [ i ] f[i]=fa[i] f[i]=fa[i]。
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
inline int read() {
int s=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s;
}
const int N=100100;
vector<int> e[N],g[N],r[N];
int n,m,q,ans[N],dep[N],A[N],head[N],dfn[N],id[N],fa[N],b[N],mi[N],sdm[N],SZ[N],tot,tt;
int gmi(int x,int y) {
return id[x]<id[y]?x:y;
}
struct pr {
int next,to;
} a[N<<2];
void add(int u,int v) {
a[++tt]=(pr) {
head[u],v
};
head[u]=tt;
}
void dfs(int x) {
dep[x]=dep[fa[x]]+1;
id[x]=++tot;
dfn[tot]=x;
for(int i=0; i<(int)e[x].size(); i++) {
int y=e[x][i];
if(id[y]) continue;
g[x].pb(y),fa[y]=x,dfs(y);
}
}
int find(int x) {
if(b[x]!=x) {
int y=b[x];
b[x]=find(y);
mi[x]=id[sdm[mi[x]]]<id[sdm[mi[y]]]?mi[x]:mi[y];
}
return b[x];
}
int getmi(int x) {
find(x);
return mi[x];
}
int T[N],LC[N*30],RC[N*30],sz[N*30],cnt;
#define mid ((l+r)/2)
#define lc LC[u]
#define rc RC[u]
#define ls lc,l,mid
#define rs rc,mid+1,r
void pushup(int u) {
sz[u]=sz[lc]+sz[rc];
}
void insert(int &u,int l,int r,int k,int x) {
if(!u) u=++cnt;
if(l==r) return sz[u]+=x,void();
if(k<=mid) insert(ls,k,x);
else insert(rs,k,x);
pushup(u);
}
int query(int u,int l,int r,int L,int R) {
if(!u||(L<=l&&r<=R)) return sz[u];
int ans=0;
if(L<=mid) ans+=query(ls,L,R);
if(mid<R) ans+=query(rs,L,R);
return ans;
}
void merge(int &x,int y,int l,int r) {
if(!x||!y) return x=x|y,void();
if(l==r) return sz[x]+=sz[y],void();
merge(LC[x],LC[y],l,mid);
merge(RC[x],RC[y],mid+1,r);
pushup(x);
}
void del(int &u,int l,int r,int L,int R) {
if(!u||(L<=l&&r<=R)) {
u=0;
return ;
}
if(L<=mid) del(ls,L,R);
if(mid<R) del(rs,L,R);
pushup(u);
}
void getans(int x) {
SZ[x]=1;
for(int i=0; i<(int)g[x].size(); i++) {
int y=g[x][i];
getans(y);
merge(T[x],T[y],1,n);
SZ[x]+=SZ[y];
}
int d=query(T[x],1,n,dep[sdm[x]]+1,n);
del(T[x],1,n,dep[sdm[x]]+1,n);
insert(T[x],1,n,dep[sdm[x]],1+d);
for(int i=head[x]; i; i=a[i].next) ans[i]=SZ[x]-query(T[x],1,n,1,dep[a[i].to]);
}
int main() {
freopen("modify.in","r",stdin);
freopen("modify.out","w",stdout);
n=read(),m=read(),q=read();
for(int i=1,x,y; i<=m; i++) x=read(),y=read(),e[x].pb(y),r[y].pb(x);
for(int i=1; i<=n; i++) sort(e[i].begin(),e[i].end());
for(int i=1,x,y; i<=q; i++) {
x=read(),y=read(),add(y,x);
}
dfs(1);
for(int i=1; i<=n; i++) b[i]=mi[i]=sdm[i]=i;
for(int E=n,x=dfn[E]; E>=2; E--,x=dfn[E]) {
for(int i=0; i<(int)r[x].size(); i++) {
if(r[x][i]==fa[x]) continue;
sdm[x]=gmi(sdm[x],sdm[getmi(r[x][i])]);
}
b[x]=fa[x];
}
for(int x=1; x<=n; x++) sdm[x]=sdm[sdm[x]];
getans(1);
for(int i=1; i<=q; i++) cout<<ans[i]<<'\n';
}
D 2 T 3 D2T3 D2T3
好题。
首先我们考虑询问的逆问题:给出一个序列 a a a,长为 n n n,进行 k k k次冒泡排序后,求得到的序列 b b b。
我们可以逐位确定每一位元素的值。显然,每一个元素 a [ i ] a[i] a[i]最多会前进 k k k步。那么 b [ 1 ] b[1] b[1]即由: a [ 1 ] , . . . a [ k + 1 ] a[1],...a[k+1] a[1],...a[k+1]中的最小值构成。我们再看 b [ 2 ] b[2] b[2], b [ 2 ] b[2] b[2]的构成多了一个 a [ k + 2 ] a[k+2] a[k+2],但相应的,填在 b [ 1 ] b[1] b[1]的 a [ j ] a[j] a[j]无法填在 b [ 2 ] b[2] b[2]。
以此类推,我们每次手上都有 k k k个元素,假设处理到第 i i i位,那么会插入 a [ i + k ] a[i+k] a[i+k],然后再删除最小值放在 b [ i ] b[i] b[i]上。最后剩下的 k k k从小到大填在最后 k k k位上。
发现上面的过程可以拿堆很好的维护。
再回头看本题的询问,问题变成了给定 b b b,求有多少种 a a a满足条件。
首先需要判断是否合法。根据上述过程,发现合法的条件即为:最后 k k k位为最大的 k k k位且从小到大递增。
假设是合法的,那么我们从 b [ n − k ] b[n-k] b[n−k]开始回退。
假设当前到了第 i i i个,如果 b [ i − 1 ] > b [ i ] b[i-1]>b[i] b[i−1]>b[i],则证明 b [ i ] b[i] b[i]一定是由 a [ i + k ] a[i+k] a[i+k]构成的。也就是说,如果 b [ i − 1 ] > b [ i ] b[i-1]>b[i] b[i−1]>b[i],则堆中元素保持不变,每次 b [ i ] b[i] b[i]由唯一的 a [ i + k ] a[i+k] a[i+k]得到。
更进一步的,我们发现堆中元素改变当且仅当 b [ i ] b[i] b[i]为前缀中的最大值。在这种情况下,它的取值为 k + 1 k+1 k+1种;否则为 1 1 1.
至此,我们得到了答案的式子: k ! ∗ ( k + 1 ) s z k! * (k+1)^{sz} k!∗(k+1)sz,其中 s z sz sz为 [ 1 , n − k ] [1,n-k] [1,n−k]中前缀最大值个数。
那么,我们现在的问题变成了:树上前缀最大值查询,要求复杂度一个 l o g log log。
考虑点分树,设询问一条路径 S − T S-T S−T, S , T S,T S,T的点分树 l c a lca lca设为 p p p,则我们问题分成了两段 S − p S-p S−p和 p − T p-T p−T。 p − T p-T p−T的做法很简单,我们可以直接 d f s dfs dfs的过程中维护最大值;现在着重讲 S − p S-p S−p的做法。
S − p S-p S−p前缀最大值个数同样可以拆分成两个部分:设 S , p S,p S,p的原树 l c a lca lca为 q q q,则路径又被分成两个部分: S − q S-q S−q和 q − p q-p q−p。
S − q S-q S−q的前缀最大值个数可以用倍增求,具体求法就是求出 l a [ x ] la[x] la[x]表示 x x x的祖先中第一个比他大的数;然后维护 l a la la的倍增数组。
q − p q-p q−p的前缀最大值的求法:我们对于所有相同的 p p p离线下来一起处理。每次处理方法就是维护一个单调栈,每次从 p p p往暴力往上跳即可。因为 q − p q-p q−p路径一定不会超出 p p p的点分树连通块,所以这样做的复杂度是 O ( ∑ s i z e ) O(\sum size ) O(∑size)的。
至于合法性的判断,我们可以把求一个 a [ 1 ] − a [ n − k ] a[1]-a[n-k] a[1]−a[n−k]的 a n s ans ans,和 a [ 1 ] − a [ n ] a[1]-a[n] a[1]−a[n]的作差即可(也有其他做法,这里选择比较简单粗暴的一种)。
总复杂度 O ( n l o g n + m l o g n ) O(nlogn+mlogn) O(nlogn+mlogn),可以通过本题。
#include<bits/stdc++.h>
#define pb push_back
#define REP(x) for(int i=head[x],y=a[i].to;i;i=a[i].next,y=a[i].to) if(y!=fa&&!vis[y])
using namespace std;
inline int read() {
int s=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s;
}
const int N=1050000,INF=N;
int n,m,tt,head[N],dep[N],LA[N][21],MX[N][21],F[N][21],st[N],en[N],cnt;//树
int sum,lv[N],f[N],sz[N],vis[N],FA[N],rt;//点分树
struct pr {
int next,to;
} a[N<<1];
void add(int u,int v) {
a[++tt]=(pr) {
head[u],v
};
head[u]=tt;
}
vector<int>e[N];//eular
struct Q {
int x,y,k;
Q(int xx=0,int yy=0,int zz=0) {
x=xx;
y=yy;
k=zz;
}
} q[N<<1];
vector<Q>pe[N<<1],re[N<<1],qu[N<<1];
void clear(vector<Q> &s) {
vector<Q> t;
swap(s,t);
}
struct pa {
int sz,mx;
pa(int xx=0,int yy=0) {
sz=xx;
mx=yy;
}
} ans[N<<1];
pa operator + (pa x,pa y) {
if(!y.sz) return x;
return pa(x.sz+y.sz,y.mx);
}
int gfa(int x,int k) {
if(k==-1) return 0;
for(int i=20; i>=0; i--) if(k&(1<<i)) x=F[x][i];
return x;
}
int LCA(int x,int y) {
if(x==y) return x;
if(dep[x]<dep[y]) swap(x,y);
x=gfa(x,dep[x]-dep[y]);
if(x==y) return x;
for(int i=20; i>=0; i--) if(F[x][i]!=F[y][i]) x=F[x][i],y=F[y][i];
return F[x][0];
}
void getla(int x) {
int t=x;
x=F[x][0];
for(int i=20; i>=0; i--) if(MX[x][i]<t) x=F[x][i];
LA[t][0]=x;
for(int i=1; i<=20; i++) LA[t][i]=LA[LA[t][i-1]][i-1];
}
pa getL(int x,int d,int lim) {
int sum=1;
for(int i=20; i>=0; i--)
if(dep[LA[x][i]]>=d&&LA[x][i]>lim) x=LA[x][i],sum+=(1<<i);
return pa(sum,x);
}
void dfs(int x,int fa) {
e[0].pb(x);
st[x]=++cnt;
dep[x]=dep[fa]+1;
F[x][0]=fa;
for(int i=1; i<=20; i++) F[x][i]=F[F[x][i-1]][i-1];
MX[x][0]=x;
for(int i=1; i<=20; i++) MX[x][i]=max(MX[x][i-1],MX[F[x][i-1]][i-1]);
getla(x);
REP(x) dfs(y,x);
en[x]=cnt;
e[0].pb(x);
}
int LCA_P(int x,int y) {
while(lv[x]>lv[y]) x=FA[x];
while(lv[y]>lv[x]) y=FA[y];
while(x!=y) x=FA[x],y=FA[y];
return x;
}
void getrt(int x,int fa) {
sz[x]=1;
f[x]=0;
REP(x) getrt(y,x),sz[x]+=sz[y],f[x]=max(f[x],sz[y]);
f[x]=max(f[x],sum-sz[x]);
if(f[x]<f[rt]) rt=x;
}
void getsta(int x,int fa,int RT) {
e[RT].pb(x);
REP(x) getsta(y,x,RT);
e[RT].pb(x);
}
void build(int x,int fa) {
lv[x]=lv[fa]+1;
vis[x]=1;
FA[x]=fa;
getrt(x,0);
getsta(x,0,x);
REP(x) {
sum=f[y];
rt=0;
getrt(y,0);
build(rt,x);
}
}
const int mo = 998244353;
int jc[N];
int M(int x,int y) {
return 1ll*x*y%mo;
}
int ksm(int x,int y) {
int s=1,t=x;
while(y) {
if(y&1) s=M(s,t);
y>>=1,t=M(t,t);
}
return s;
}
void prejc(int n) {
jc[0]=1;
for(int i=1; i<=n; i++) jc[i]=M(jc[i-1],i);
}
int ton[N],sta[N],dis[N],c[N],now;
void add(int x) {
while(x<=n) c[x]++,x+=x&(-x);
}
int query(int x) {
int sum=0;
while(x)sum+=c[x],x-=x&(-x);
}
bool cmp1(int x,int y) {
return x<y;
}
bool cmp2(int x,int y) {
return dis[x]<dis[y];
}
bool cmp3(Q x,Q y) {
return dep[x.x]>dep[y.x];
}
void insert(int x) {
while(now&&sta[now]<x) now--;
sta[++now]=x;
}
#define mid ((l+r)/2)
int find(int x) {
int l=1,r=now,ans=0;
while(l<=r) (sta[mid]>x)?ans=mid,l=mid+1:r=mid-1;
return ans;
}
void getans(int x) {
now=0;
sort(pe[x].begin(),pe[x].end(),cmp3);
int d=x;
for(int i=0; i<pe[x].size(); i++) {
int y=pe[x][i].x,k=pe[x][i].k;
while(dep[d]>=dep[y]+1) insert(d),d=F[d][0];
int p=find(ans[k].mx);
if(p) ans[k]=pa(ans[k].sz+p,sta[1]);
}
}
void work(int x) {
if(!re[x].size()) return ;
for(int i=0; i<(int)re[x].size(); i++) qu[re[x][i].y].pb(re[x][i]);
int sum=0;
now=0;
for(int i=0; i<(int)e[x].size(); i++) {
int y=e[x][i];
ton[y]^=1;
if(ton[y]==1) {
dis[y]=++sum;
if(y>sta[now]) sta[++now]=y;
} else {
for(int j=0; j<(int)qu[y].size(); j++) {
int t=qu[y][j].x,k=qu[y][j].k,p;
p=upper_bound(sta+1,sta+now+1,t,cmp2)-sta;
p=upper_bound(sta+p,sta+now+1,ans[k].mx,cmp1)-sta;
if(p<=now) ans[k]=pa(ans[k].sz+now-p+1,sta[now]);
}
if(sta[now]==y) now--;
sum--;
}
}
for(int i=0; i<(int)re[x].size(); i++) clear(qu[re[x][i].y]);
for(int i=0; i<(int)e[x].size(); i++) dis[e[x][i]]=0;
}
int main() {
freopen("sorting.in","r",stdin);
freopen("sorting.out","w",stdout);
n=read(),m=read();
for(int i=1,x,y; i<n; i++) x=read(),y=read(),add(x,y),add(y,x);
prejc(n);
for(int i=0; i<=20; i++) MX[0][i]=INF;
dfs(1,0);
sum=f[0]=n;
getrt(1,0);
build(rt,0);
for(int i=1,x,y,k,g; i<=m; i++) {
q[i].x=x=read(),q[i].y=y=read(),q[i].k=k=read();
int lca=LCA(q[i].x,q[i].y),dis=dep[x]+dep[y]-dep[lca]*2+1;
if(dis>k) {
g=(dep[y]-dep[lca]<=k)?gfa(x,dis-k-1):gfa(y,k);
q[i+m].x=q[i].x;
q[i+m].y=g;
q[i+m].k=q[i+m].k;
} else q[i+m].k=dis;
}
for(int i=1,x,y; i<=(m<<1); i++) {
x=q[i].x,y=q[i].y;
if(x==0||y==0) continue;
int g=LCA_P(x,y),lca=LCA(x,g);
ans[i]=getL(x,dep[lca],0);
if((!(st[g]<=st[x]&&en[x]<=en[g]))) pe[g].pb(Q(lca,g,i));
re[g].pb(Q(g,y,i));
}
for(int i=1; i<=n; i++) getans(i);
for(int i=1; i<=n; i++ ) work(i);
for(int i=1,k=q[i].k; i<=m; i++,k=q[i].k) {
if(q[i+m].x==0||q[i+m].y==0) {
if(ans[i].sz==q[i+m].k) cout<<jc[q[i+m].k]<<'\n';
else cout<<0<<'\n';
} else {
if(ans[i].sz-ans[i+m].sz==k) cout<<M(ksm(k+1,ans[i+m].sz),jc[k])<<'\n';
else cout<<0<<'\n';
}
}
}