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;
}

浙公网安备 33010602011771号