MX 暑假集训 7.29

DECO*27 - 乙女解剖 feat. 初音未来

很难很难的数据结构。T-T

[LNOI2014] LCA

题意

给定一棵 \(n\) 个点的有根树,\(m\) 次询问,每次询问给定 \(l,r,z\),求 \(\sum\limits_{i=l}^{r}dep_{\operatorname{lca}(i,z)}\)

\(1\le n,m\le 5\times 10^4\)

solution

题单里最简单的题。

首先令 \(f(r,z)=\sum\limits_{i=1}^{r}dep_{\operatorname{lca}(i,z)}\),那么每个询问的答案就是 \(f(r,z)-f(l-1,z)\),考虑使用扫描线求出 \(f(r,z)\)

两个点的 LCA 深度就是两个点到根的路径交集点数,对 \(r\) 扫描线,每次加入一个 \(r\) 相当于对于 \(r\) 到根节点的路径上所有点权值加 \(1\),处理一个挂在 \(r\) 上的询问 \(f(r,z)\) 的答案就是 \(z\) 到根节点路径权值和。

树剖维护一下,时间复杂度 \(O((n+m)\log^2 n)\)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e4+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,idx;
ll ans[N];
vector<int> e[N];

int f[N],dep[N],siz[N],son[N];
int top[N],dfn[N];

inline void dfs1(int now,int fa) {
	dep[now]=(f[now]=fa)+(siz[now]=1);
	for (int v:e[now]) {
		dfs1(v,now);
		siz[now]+=siz[v];
		if (siz[v]>siz[son[now]]) son[now]=v;
	}
}

inline void dfs2(int now,int tp) {
	top[now]=tp;
	dfn[now]=++idx;
	if (!son[now]) return;
	dfs2(son[now],tp);
	for (int v:e[now]) if (v^son[now]) dfs2(v,v);
}

struct Segment_Tree {
	ll s[N<<2],tg[N<<2];
	inline int ls(int x) {return x<<1;}
	inline int rs(int x) {return x<<1|1;}
	
	inline void modify(int x,int l,int r,int k) {s[x]+=1ll*(r-l+1)*k;tg[x]+=k;}
	inline void push_down(int x,int l,int r) {
		if (!tg[x]) return;
		int mid=l+r>>1,k=tg[x];tg[x]=0;
		modify(ls(x),l,mid,k);
		modify(rs(x),mid+1,r,k);
	}
	
	inline void update(int x,int y,int l,int r,int now) {
		if (x<=l&&y>=r) return modify(now,l,r,1);
		push_down(now,l,r);
		int mid=l+r>>1;
		if (x<=mid) update(x,y,l,mid,ls(now));
		if (y>mid) update(x,y,mid+1,r,rs(now));
		s[now]=s[ls(now)]+s[rs(now)];
	}
	
	inline ll query(int x,int y,int l,int r,int now) {
		if (x<=l&&y>=r) return s[now];
		push_down(now,l,r);
		int mid=l+r>>1;ll ret=0;
		if (x<=mid) ret=query(x,y,l,mid,ls(now));
		if (y>mid) ret+=query(x,y,mid+1,r,rs(now));
		return ret;
	}
}tr;

inline void update(int x) {
	while (x) {
		tr.update(dfn[top[x]],dfn[x],1,n,1);
		x=f[top[x]];
	}
}

inline ll query(int x) {
	ll ret=0;
	while (x) {
		ret+=tr.query(dfn[top[x]],dfn[x],1,n,1);
		x=f[top[x]];
	}
	return ret;
}

struct ques{int x,id,op;};
vector<ques> vec[N];

int main() {
	read(n);read(m);
	f(i,2,n) {
		int fa;read(fa);fa++;
		e[fa].push_back(i);
	}
	dfs1(1,0);dfs2(1,1);
	f(i,1,m) {
		int l,r,x;
		read(l);read(r);read(x);
		l++;r++;x++;
		vec[r].push_back({x,i,1});
		vec[l-1].push_back({x,i,-1});
	}
	f(i,1,n) {
		update(i);
		for (ques k:vec[i]) ans[k.id]+=query(k.x)*k.op;
	}
	f(i,1,m) printf("%lld\n",ans[i]%201314);
	return 0;
}

The Classic Problem

题意

给定一张 \(n\) 个节点 \(m\) 条边的无向图,一条边 \((u,v,x)\) 表示 \(u\)\(v\) 存在一条边权为 \(2^x\) 的无向边,求 \(s\)\(t\) 的最短路,输出最短路权值对 \(10^9+7\) 取模的结果并给出一条具体路径。

\(1\le n,m,x\le 10^5\)

solution

显然要跑 Dijkstra,我们需要实现一个 __int100018,但是直接高精度显然是不行的,考虑使用数据结构表示一个数。

我们在 Dijkstra 中对于每个点的距离要实现的操作有:

  • 对一个数加上 \(2^x\)
  • 比较两个数的大小。

发现可以使用可持久化线段树来维护一个数,维护 \(10^5+18\) 个二进制位下的数是 \(0\) 还是 \(1\),考虑如何实现上面的两个操作。

  • \(2^x\),找到最高的位置 \(p\) 满足 \([x,p]\) 位上面均为 \(1\),那么加法进位就变成了 \([x,p]\) 均为 \(0\),第 \(p\) 位上为 \(1\);
  • 比较两个数的大小,可以找到最高的不相等位置,线段树二分即可,判断两个区间是否相同可以用哈希。

可持久化线段树支持区间覆盖,单点修改,找到最高不同位,于是做完了,梳理一下每个节点要维护哪些信息:左右儿子,权值模 \(10^9+7\) 意义下的值(这个值可以直接作为哈希值),哈希值(双模哈希是必要的),整个区间是否全部为 \(1\),区间长度(维护哈希值要用)。

对于区间覆盖为 \(0\),可以使用初始时 \(s\) 对应的树(全为 \(0\))来覆盖子节点,直接将子节点指向这棵全为 \(0\) 的树,避免使用懒标记导致空间复杂度太高。

比较两个数和加法都是 \(O(\log x)\) 的,所以时间复杂度 \(O(m\log m\log x)\),空间复杂度 \(O(m\log x)\)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e5+110;
constexpr int V=1e5+100;
constexpr int P=(N<<8);
constexpr int inf=1e9+10;

constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,cnt;
int fr[N];

struct Edge{int v,w;};
vector<Edge> e[N];

int rt[N],pw1[N],pw2[N];
struct Segment_Tree {
	int tot;
	struct Node{int ls,rs,val,fl,len,H;}tr[P];
	#define ls(x) tr[x].ls
	#define rs(x) tr[x].rs
	
	inline void push_up(int x) {
		int l=ls(x),r=rs(x);
		tr[x].val=(1ll*tr[r].val*pw1[tr[l].len]+tr[l].val)%mod;
		tr[x].H=(1ll*tr[r].H*pw2[tr[l].len]+tr[l].H)%998244853;
		tr[x].fl=(tr[l].fl&tr[r].fl);
	}
	
	inline int build(int l,int r) {
		int now=++tot,mid=l+r>>1;
		tr[now].len=r-l+1;
		if (l==r) return now;
		tr[now].ls=build(l,mid);
		tr[now].rs=build(mid+1,r);
		return now;
	}
	
	inline int newNode(int x) {
		tr[++tot]=tr[x];
		return tot;
	}
	
	inline void assign(int x,int y,int l,int r,int &now,int p2) {
		now=newNode(now);
		if (x<=l&&y>=r) return now=p2,void();
		int mid=l+r>>1;
		if (x<=mid) assign(x,y,l,mid,ls(now),ls(p2));
		if (y>mid) assign(x,y,mid+1,r,rs(now),rs(p2));
		push_up(now);
	}
	
	inline void update(int x,int l,int r,int &now) {
		now=newNode(now);
		if (l==r) return tr[now].fl=tr[now].val=tr[now].H=1,void();
		int mid=l+r>>1;
		if (x<=mid) update(x,l,mid,ls(now));
		else update(x,mid+1,r,rs(now));
		push_up(now);
	}
	
	inline bool chk(int x,int y) {return (tr[x].val==tr[y].val)&&(tr[x].H==tr[y].H);}
	inline bool cmp(int x,int y,int l,int r) {
		if (l==r) return tr[x].val<tr[y].val;
		int mid=l+r>>1;
		if (!chk(rs(x),rs(y))) return cmp(rs(x),rs(y),mid+1,r);
		return cmp(ls(x),ls(y),l,mid);
	}
	
	inline int find_first(int x,int l,int r,int now) {
		if (tr[now].fl) return 0;
		if (l==r) return l;
		int mid=l+r>>1;
		if (x<=mid) {
			int ret=find_first(x,l,mid,ls(now));
			if (ret) return ret;
		}
		return find_first(x,mid+1,r,rs(now));
	}
	
	#undef ls
	#undef rs
}tr;

struct int1e5 {int v;}dis[N];
inline bool operator ==(const int1e5 &x,const int1e5 &y) {return tr.chk(rt[x.v],rt[y.v]);}
inline bool operator <(const int1e5 &x,const int1e5 &y) {
	if (x==y) return 0;
	return tr.cmp(rt[x.v],rt[y.v],1,V);
}
inline bool operator >(const int1e5 &x,const int1e5 &y) {return (!x.v)||(!((x==y)||(x<y)));}
inline int1e5 operator +(const int1e5 &x,const int y) {
	int p=tr.find_first(y,1,V,rt[x.v]);
	rt[++cnt]=rt[x.v];
	if (p!=y) tr.assign(y,p-1,1,V,rt[cnt],1);
	tr.update(p,1,V,rt[cnt]);
	return {cnt};
}

struct queue_Node {
	int id;int1e5 val;
	inline bool operator <(const queue_Node &x)const {return val>x.val;}
};

inline int dij(int s,int t) {
	rt[++cnt]=dis[s].v=tr.build(1,V);
	priority_queue<queue_Node> q;
	q.push({s,dis[s]});
	bitset<N> vis;
	while (!q.empty()) {
		int now=q.top().id;
		q.pop();
		if (vis[now]) continue;
		vis.set(now);
		for (Edge E:e[now]) {
			int v=E.v,w=E.w;
			if (vis[v]) continue;
			int1e5 d=dis[now]+w;
			if (dis[v]>d) q.push({v,dis[v]=d}),fr[v]=now;
		}
	}
	if (!dis[t].v) return -1;
	return tr.tr[rt[dis[t].v]].val;
}

int main() {
	read(n);read(m);
	pw1[0]=pw2[0]=1;
	f(i,1,V-1) {
		pw1[i]=ad(pw1[i-1],pw1[i-1]);
		pw2[i]=pw2[i-1]+pw2[i-1];
		pw2[i]-=(pw2[i]>=998244853?998244853:0);
	}
	f(i,1,m) {
		int u,v,w;
		read(u);read(v);read(w);w++;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	int s,t;read(s);read(t);
	int ret=dij(s,t);
	printf("%d\n",ret);
	if (ret==-1) return 0;
	vector<int> path;
	while (t!=s) path.push_back(t),t=fr[t];
	path.push_back(s);
	reverse(path.begin(),path.end());
	printf("%d\n",(int)(path.size()));
	for (int x:path) printf("%d ",x);
	putchar(10);
	return 0;
}

【模板】动态 DP

题意

给定一棵 \(n\) 个节点的树,点有点权,\(m\) 次修改,每次单点修改点权,每次修改后输出最大权独立集权值。

\(1\le n,m\le 10^5\)

solution

其实我之前一直不会 DDP,今天终于学懂了。

首先考虑 dp,令 \(f_{i,0/1}\) 表示选或不选 \(i\)\(i\) 子树内最大权独立集权值,转移显然,考虑树剖维护这个过程。

\(g_{i,0/1}\) 表示不考虑 \(i\) 的重儿子,选或不选 \(i\)\(i\) 子树内最大权独立集权值,用 max-plus 矩阵维护转移。

\[(A\otimes B)_{i,j}=\max_{k\in{0,1}}(A_{i,k}+B_{k,j}). \]

对于一个点 \(u\)\(v\) 为它的重儿子:

\[\begin{pmatrix} g_{u,0} & g_{u,0}\\ g_{u,1} & -\infty \end{pmatrix} \otimes \begin{pmatrix} f_{v,0}\\ f_{v,1} \end{pmatrix} = \begin{pmatrix} f_{u,0}\\ f_{u,1} \end{pmatrix} \]

那么我们维护每条重链上的转移矩阵,对于一条重链顶节点 \(x\),设 \(y\) 为这条重链的链底节点,显然 \(y\) 为叶子,则 \(f_x\) 即为这条重链上从上到下转移矩阵的乘积得到矩阵的第 \(0\) 列,所以修改时暴力向上跳链顶,更新链顶 \(f\) 值同时更新链顶父亲的 \(g\) 值,向上更新直到将根节点更新即可。

时间复杂度 \(O(k^3 m\log^2 n)\),其中 \(k=2\)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;int f=1;
	char ch=getchar();
	while (ch<48) {if(ch=='-')f=-1;ch=getchar();} 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}

int n,m;
int a[N];
vector<int> e[N];

struct Matrix {
	int v[2][2];
	inline Matrix() {v[0][0]=v[0][1]=v[1][0]=v[1][1]=0;}
	inline Matrix(int a1,int a2,int a3,int a4) {v[0][0]=a1;v[0][1]=a2;v[1][0]=a3;v[1][1]=a4;}
	inline bool operator ==(const Matrix &x)const {return ((v[0][0]==x.v[0][0])&&(v[0][1]==x.v[0][1])&&(v[1][0]==x.v[1][0])&&(v[1][1]==x.v[1][1]));}
	
	inline Matrix operator *(const Matrix &x)const {
		Matrix res;
		res.v[0][0]=max(0,max(v[0][0]+x.v[0][0],v[0][1]+x.v[1][0]));
		res.v[0][1]=max(0,max(v[0][0]+x.v[0][1],v[0][1]+x.v[1][1]));
		res.v[1][0]=max(0,max(v[1][0]+x.v[0][0],v[1][1]+x.v[1][0]));
		res.v[1][1]=max(0,max(v[1][0]+x.v[0][1],v[1][1]+x.v[1][1]));
		return res;
	}
};

int fa[N],siz[N],dep[N],son[N];
int dfn[N],top[N],ed[N],idx;

inline void dfs1(int now,int fat) {
	dep[now]=dep[fa[now]=fat]+(siz[now]=1);
	for (int v:e[now]) if (v^fat) {
		dfs1(v,now);
		siz[now]+=siz[v];
		if (siz[v]>siz[son[now]]) son[now]=v;
	}
}

inline void dfs2(int now,int tp) {
	top[now]=tp;
	dfn[now]=++idx;
	ed[tp]=now;
	if (!son[now]) return;
	dfs2(son[now],tp);
	for (int v:e[now]) if (v!=fa[now]&&v!=son[now]) dfs2(v,v);
}

struct Segment_Tree {
	Matrix tr[N<<2];
	inline int ls(int x) {return x<<1;}
	inline int rs(int x) {return x<<1|1;}
	
	inline void update(int x,int l,int r,int now,Matrix k) {
		if (l==r) return tr[now]=k,void();
		int mid=l+r>>1;
		if (x<=mid) update(x,l,mid,ls(now),k);
		else update(x,mid+1,r,rs(now),k);
		tr[now]=tr[ls(now)]*tr[rs(now)];
	}
	
	inline Matrix query(int x,int y,int l,int r,int now) {
		if (x<=l&&y>=r) return tr[now];
		int mid=l+r>>1;
		if (x<=mid&&y>mid) return query(x,y,l,mid,ls(now))*query(x,y,mid+1,r,rs(now));
		if (x<=mid) return query(x,y,l,mid,ls(now));
		return query(x,y,mid+1,r,rs(now));
	}
}tr;

int f[N][2],g[N][2];
inline void dfs3(int now) {
	g[now][0]=f[now][0]=0;
	g[now][1]=f[now][1]=a[now];
	for (int v:e[now]) if (v!=fa[now]) {
		dfs3(v);
		if (v!=son[now]) {
			g[now][0]+=max(f[v][0],f[v][1]);
			g[now][1]+=f[v][0];
		}
		f[now][0]+=max(f[v][0],f[v][1]);
		f[now][1]+=f[v][0];
	}
	tr.update(dfn[now],1,n,1,Matrix(g[now][0],g[now][0],g[now][1],-inf));
}

inline void update(int x,int k) {
	g[x][1]+=k-a[x];
	a[x]=k;
	tr.update(dfn[x],1,n,1,Matrix(g[x][0],g[x][0],g[x][1],-inf));
	x=top[x];
	while (x) {
		int y=fa[x];
		Matrix res=tr.query(dfn[x],dfn[ed[x]],1,n,1);
		g[y][0]-=max(f[x][0],f[x][1]);
		g[y][1]-=f[x][0];
		f[x][0]=res.v[0][0];
		f[x][1]=res.v[1][0];
		g[y][0]+=max(f[x][0],f[x][1]);
		g[y][1]+=f[x][0];
		if (y) tr.update(dfn[y],1,n,1,Matrix(g[y][0],g[y][0],g[y][1],-inf));
		x=top[y];
	}
}

int main() {
	read(n);read(m);
	f(i,1,n) read(a[i]);
	f(i,2,n) {
		int u,v;read(u);read(v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1,0);dfs2(1,1);dfs3(1);
	while (m--) {
		int x,k;read(x);read(k);
		update(x,k);
		printf("%d\n",max(f[1][0],f[1][1]));
	}
	return 0;
}
posted @ 2026-07-29 14:57  Ff472130  阅读(10)  评论(0)    收藏  举报