Loading

20260403 紫题训练

P2487 [SDOI2011] 拦截导弹

考虑树剖,线段树维护区间内的颜色段数和每个点的颜色。

查询时每次遇到不同链判断相邻的颜色是否相同,如果相同将答案减一。

#include<bits/stdc++.h>
#define N 100005
#define getc getchar
using namespace std;
int n,m;
class SGT{
	#define l(i) ((i)<<1)
	#define r(i) ((i)<<1|1)
	#define c(i) tr[i].cnt
	#define t(i) tr[i].tag
	#define lc(i) tr[i].lcol
	#define rc(i) tr[i].rcol
	private:
		struct node{
			int cnt,tag,lcol,rcol;
		}tr[N<<2];
		void up(int x){
			lc(x)=lc(l(x)),rc(x)=rc(r(x));
			c(x)=c(l(x))+c(r(x))-(rc(l(x))==lc(r(x)));
		}
		void down(int x){
			if(!t(x)) return;
			c(l(x))=c(r(x))=1;
			t(l(x))=t(r(x))=t(x);
			lc(l(x))=lc(r(x))=t(x);
			rc(l(x))=rc(r(x))=t(x);
			t(x)=0;
		}
	public:
		void upd(int ql,int qr,int v,int x=1,int l=1,int r=n){
			if(ql<=l&&qr>=r) return t(x)=lc(x)=rc(x)=v,c(x)=1,void();
			int mid=l+r>>1;down(x);
			if(ql<=mid) upd(ql,qr,v,l(x),l,mid);
			if(qr>mid) upd(ql,qr,v,r(x),mid+1,r);
			up(x);
		}
		int query(int ql,int qr,int x=1,int l=1,int r=n){
			if(ql<=l&&qr>=r) return c(x);
			int mid=l+r>>1;down(x);
			if(ql<=mid&&qr>mid)
				return query(ql,qr,l(x),l,mid)
				+query(ql,qr,r(x),mid+1,r)-(rc(l(x))==lc(r(x)));
			if(ql<=mid) return query(ql,qr,l(x),l,mid);
			if(qr>mid) return query(ql,qr,r(x),mid+1,r);
			return 0;
		}
		int get(int p,int x=1,int l=1,int r=n){
			if(l==r) return t(x);
			int mid=l+r>>1;down(x);
			if(p<=mid) return get(p,l(x),l,mid);
			return get(p,r(x),mid+1,r);
		}
	#undef l
	#undef r
	#undef c
	#undef t
	#undef lc
	#undef rc
}tr;
vector<int>s[N];
int dfn[N],top[N],hson[N];
int idx,a[N],fa[N],siz[N],dep[N];
void dfs1(int x){
	dep[x]=dep[fa[x]]+(siz[x]=1);
	for(auto p:s[x])
		if(p^fa[x]){
			fa[p]=x,dfs1(p);
			siz[x]+=siz[p];
			if(siz[p]>siz[hson[x]])
				hson[x]=p;
		}
}
void dfs2(int x,int t){
	dfn[x]=++idx;top[x]=t;
	if(hson[x]) dfs2(hson[x],top[x]);
	for(auto p:s[x])
		if(p^fa[x]&&p^hson[x])
			dfs2(p,p);
}
void upd(int u,int v,int c){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		tr.upd(dfn[top[u]],dfn[u],c),u=fa[top[u]];
	}if(dep[u]>dep[v]) swap(u,v);tr.upd(dfn[u],dfn[v],c);
}
int query(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res+=tr.query(dfn[top[u]],dfn[u]);
		res-=tr.get(dfn[top[u]])==tr.get(dfn[u=fa[top[u]]]);
	}if(dep[u]>dep[v]) swap(u,v);
	return res+tr.query(dfn[u],dfn[v]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,x;i<=n;i++) scanf("%d",a+i);
	for(int i=1,u,v;i<n;i++)
		scanf("%d%d",&u,&v),
		s[u].emplace_back(v),
		s[v].emplace_back(u);
	dfs1(1),dfs2(1,1);
	for(int i=1,x;i<=n;i++)
		tr.upd(dfn[i],dfn[i],a[i]);
	while(m--){
		char c=getc();int a,b,v;
		while(c!='C'&&c!='Q') c=getc();
		if(c=='C')
			scanf("%d%d%d",&a,&b,&v),upd(a,b,v);
		if(c=='Q')
			scanf("%d%d",&a,&b),
			printf("%d\n",query(a,b));
	}
	return 0;
}

P3687 [ZJOI2017] 仙人掌

当原图不是仙人掌时,再加边也不是仙人掌,答案为 \(0\)

考虑怎么判断仙人掌,当建出 DFS 序,给每条树边记录 tag 表示是否处于环中。

当存在一条 \((u,v)\)非树边时,树上 \(u\)\(v\) 的这条链就处于一个环内,给这条链上的边的 tag 设为 true

若存在一条边的 tag 已经为 true,则这条边已经处于一个环内,不是仙人掌,直接退出即可。

给这条链上的边的 tag 设为 true 可以直接暴力做,分析上述过程,每个点和边都最多被访问一次,时间复杂度为 \(\mathcal O(n+m)\)

判掉不是仙人掌的情况后,考虑处于环中的边,由于不能再让它处于环中了,故肯定不对答案有贡献,可以全部删掉。

这样就变成了若干棵树,由于树之间是独立的,答案为所有树方案数的乘积。

由上面判非仙人掌的过程可以发现,连接一条边相当于选中一条链。

问题转化为在一棵树中选出若干条链,每条边最多被选中一次的方案数。

\(f_i\) 为只考虑 \(i\) 的子树的答案,转移式为:

\[f_i=h_{|son_i|+1}\prod_{p\in son_i}f_p \]

其中的 \(h_i\) 是一个系数,现在分析它的值:

考虑加入第 \(j\) 的过程,它与 \(i\) 连的边所在的链有跨过 \(i\) 和没跨过 \(i\) 两种。

方案数分别为 \(h_{j-1}\)(没跨过)和\((j-1)h_{j-2}\)(可以在其他 \(j-1\) 个选一个连成一条链)。

因此有递推式:

\[h_i=h_{i-1}+(i-1)h_{i-2} \]

为什么转移式中有 \(+1\) 呢?因为还有和 \(i\) 的父亲组成链的情况。

注意根节点没有父亲,所以也不需要 \(+1\),要特殊计算。

#include<bits/stdc++.h>
#define N 500005
#define M 1000005
using namespace std;
struct edge{int x,id;};
vector<edge>s[N];
bool vis[N],tag[M];
const int P=998244353;
int n,m,f[N],fa[N],id[N],dep[N];
bool mark(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	while(dep[u]>dep[v]){
		if(tag[id[u]]) return false;
		tag[id[u]]=true,u=fa[u];
	}
	while(u^v){
		if(tag[id[u]]||tag[id[v]])
			return false;
		tag[id[u]]=true,u=fa[u];
		tag[id[v]]=true,v=fa[v];
	}return true;
}
bool dfs(int x){
	dep[x]=dep[fa[x]]+1;
	for(auto p:s[x])
		if(p.x^fa[x])
			if(dep[p.x]){
				if(!tag[p.id]){
					tag[p.id]=true;
					if(!mark(x,p.x))
						return false;
				}
			}
			else{
				fa[p.x]=x,id[p.x]=p.id;
				if(!dfs(p.x)) return false;
			}
	return true;
}
int DP(int x,bool rt){
	vis[x]=true;
	int ch=0,mul=1;
	for(auto p:s[x])
		if(!vis[p.x]&&!tag[p.id])
			ch++,mul=1ll*mul*DP(p.x,false)%P;
	return 1ll*mul*f[ch+!rt]%P;
}
int main(){
	int T;scanf("%d",&T);
	f[0]=f[1]=1;for(int i=2;i<N;i++)
		f[i]=(f[i-1]+(i-1ll)*f[i-2])%P;
	while(T--){
		int ans=1;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			dep[i]=vis[i]=0,s[i].clear();
		for(int i=1;i<=m;i++) tag[i]=false;
		for(int i=1,u,v;i<=m;i++)
			scanf("%d%d",&u,&v),
			s[u].push_back({v,i}),
			s[v].push_back({u,i});
		if(!dfs(1)){puts("0");continue;}
		for(int i=1;i<=n;i++)
			if(!vis[i]) ans=1ll*ans*DP(i,true)%P;
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2026-04-03 21:33  Jokersen  阅读(11)  评论(0)    收藏  举报