noip模拟46

A. 数数

排好序从两头贪心即可


B. 数树

首先很容易想到容斥
如果选择的边集的相关点集有点的度数大于 \(1\) 是不合法的
也就是说一定形成若干条长度不一的链
要给这些链上的点安排排列中的数,方案数其实就是 \((n-k)!\)
因为一条链开头的值确定了整条链的值就确定了

发现暴力算是 \(2^n\),考虑选择边集数量一定时贡献是否可以一起算
树形背包即可,算出以 \(1\) 为根的子树内选 \(k\) 条边的方案数
由于入度出度不超过 \(1\) 的限制,\(dp\) 加两维 \(0/1\) 表示有没有出/入边

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5005;
const int maxm=10005;
const int mod=998244353;
int n,hd[maxn],cnt,f[maxn][maxn][2][2],ans,fa[maxn],x,y,frac[maxn],siz[maxn],g[maxn][2][2];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
struct Edge{
	int nxt,to,val;
}edge[maxm];
void add(int u,int v,int w){
	edge[++cnt].nxt=hd[u];
	edge[cnt].to=v;
	edge[cnt].val=w;
	hd[u]=cnt;
	return ;
}
void dfs(int u){
	siz[u]=1;
	f[u][0][0][0]=1;
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u])continue;
		fa[v]=u;
		dfs(v);
//		memset(g,0,sizeof g);
		for(int j=siz[u];j>=0;j--){
			for(int k=siz[v];k>=0;k--){
				int sum=(f[v][k][0][0]+f[v][k][0][1]+f[v][k][1][0]+f[v][k][1][1])%mod;
				for(int l=0;l<2;l++){
					for(int r=0;r<2;r++){
						g[j+k][l][r]=(g[j+k][l][r]+f[u][j][l][r]*sum%mod)%mod;
					}
				}
				if(edge[i].val){
					g[j+k+1][1][0]=(g[j+k+1][1][0]+f[u][j][0][0]*(f[v][k][0][0]+f[v][k][1][0])%mod)%mod;
					g[j+k+1][1][1]=(g[j+k+1][1][1]+f[u][j][0][1]*(f[v][k][0][0]+f[v][k][1][0])%mod)%mod;
				}
				else{
					g[j+k+1][0][1]=(g[j+k+1][0][1]+f[u][j][0][0]*(f[v][k][0][0]+f[v][k][0][1])%mod)%mod;
					g[j+k+1][1][1]=(g[j+k+1][1][1]+f[u][j][1][0]*(f[v][k][0][0]+f[v][k][0][1])%mod)%mod;
				}
			}
		}
		siz[u]+=siz[v];
		for(int j=0;j<=siz[u];j++){
			for(int l=0;l<2;l++){
				for(int r=0;r<2;r++){
					f[u][j][l][r]=g[j][l][r];
					g[j][l][r]=0;
				}
			}
		}
	}
	return ;
}
signed main(){
	n=read();
	for(int i=1;i<=n-1;i++){
		x=read();
		y=read();
		add(x,y,0);
		add(y,x,1);
	}
	dfs(1);
	frac[0]=1;
	for(int i=1;i<=n;i++)frac[i]=frac[i-1]*i%mod;
	int op=1;
	for(int i=0;i<=n-1;i++){
		int sum=0;
		for(int j=0;j<=1;j++){
			for(int k=0;k<=1;k++){
//				cout<<f[1][i][j][k]<<" ";
				sum=(sum+f[1][i][j][k])%mod;
			}
		}
//		cout<<endl;
//		cout<<sum<<endl;
		ans=(ans+op*sum*frac[n-i]%mod)%mod;
		ans=(ans+mod)%mod;
		op=-op;
	}
	cout<<ans;
	return 0;
}

C. 鼠树

发现单个白点的权值由其归属点的累加可以得出,子树和操作是子树内黑点总和以及一部分白点由其归属点得出
所以只需要维护黑点信息即可
记录每个黑点的权值,管辖点的个数,以及二者的乘积
当加入一个黑点的时候,其管辖点大小由子树内大小之和推出,权值设为归属点权值,并相应地更改归属点大小
当删除一个黑点的时候,由于要保留贡献,所以直接对这棵子树做区间加,并做一次 \(4\) 操作把子树内其他黑点多算的部分减掉
最后是动态维护归属点的问题:可以树剖并在每条重链上开 \(set\) 维护黑点深度,查询时在重链上二分即可
整个算法时间复杂度 \(nlogn\)

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int unsigned int
const int maxn=3e5+5;
const int maxm=3e5+5;
int n,m,fa[maxn],hd[maxn],cnt,son[maxn],siz[maxn],id[maxn],re[maxn],tp[maxn],tot,x,y,w,op,dep[maxn],c[maxn],c1[maxn];
bool col[maxn];
set<int>s[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
struct Edge{
	int nxt,to;
}edge[maxm];
void add_edge(int u,int v){
	edge[++cnt].nxt=hd[u];
	edge[cnt].to=v;
	hd[u]=cnt;
	return ;
}
void dfs(int u){
	siz[u]=1;
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u])continue;
		dep[v]=dep[u]+1;
		fa[v]=u;
		dfs(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
	return ;
}
void dfs1(int u,int top){
	tp[u]=top;
	id[u]=++tot;
	re[tot]=u;
	if(son[u])dfs1(son[u],top);
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==son[u]||v==fa[u])continue;
		dfs1(v,v);
	}
	return ;
}
void add(int x,int w){
	for(;x<=n;x+=x&-x)c[x]+=w;
}
void add1(int x,int w){
	for(;x<=n;x+=x&-x)c1[x]+=w;
}
int ask(int x){
	int ans=0;
	for(;x;x-=x&-x)ans+=c[x];
	return ans;
}
int ask1(int x){
	int ans=0;
	for(;x;x-=x&-x)ans+=c1[x];
	return ans;
}
void change(int l,int r,int w){
	add(l,w);
	add(r+1,-w);
	add1(l,w*(1-l));
	add1(r+1,w*(l-1));
	add1(r+1,w*(r-l+1));
	return ;
}
int query(int l,int r){
	return ask(r)*r+ask1(r)-ask(l-1)*(l-1)-ask1(l-1);
}
int get_t(int x){
	while(1){
		if(!s[tp[x]].empty()){
			if(*s[tp[x]].begin()<=dep[x]){
				set<int>::iterator it=s[tp[x]].upper_bound(dep[x]);
				it--;
				return re[id[x]-(dep[x]-(*it))];
			}
		}
		x=fa[tp[x]];
	}
	return 0;
}
struct seg{
	int l,r,siz,val,sum,lazy;
}t[maxn*4];
void build(int p,int l,int r){
	t[p].l=l;
	t[p].r=r;
	if(l==r)return ;
	int mid=l+r>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	return ;
}
void update(int p){
	t[p].siz=t[p<<1].siz+t[p<<1|1].siz;
	t[p].val=t[p<<1].val+t[p<<1|1].val;
	t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
	return ;
}
void dospread(int p,int w){
	t[p].val+=w;
	t[p].sum+=w*t[p].siz;
	t[p].lazy+=w;
	return ;
}
void spread(int p){
	dospread(p<<1,t[p].lazy);
	dospread(p<<1|1,t[p].lazy);
	t[p].lazy=0;
	return ;
}
void change_val(int p,int l,int r,int w){
	if(l>r)return ;
	if(t[p].l>=l&&t[p].r<=r){
		dospread(p,w);
		return ;
	}
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(l<=mid)change_val(p<<1,l,r,w);
	if(r>mid)change_val(p<<1|1,l,r,w);
	update(p);
	return ;
}
void change_siz(int p,int pos,int w){
	if(t[p].l==t[p].r&&t[p].l==pos){
		t[p].siz+=w;
		t[p].sum+=w*t[p].val;
		return ;
	}
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(pos<=mid)change_siz(p<<1,pos,w);
	else change_siz(p<<1|1,pos,w);
	update(p);
	return ;
}
int ask_val(int p,int pos){
	if(t[p].l==t[p].r&&t[p].l==pos){
		return t[p].val;
	}
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(pos<=mid)return ask_val(p<<1,pos);
	return ask_val(p<<1|1,pos);
}
int ask_siz(int p,int l,int r){
	if(l>r)return 0;
	if(t[p].l>=l&&t[p].r<=r)return t[p].siz;
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1,ans=0;
	if(l<=mid)ans=ask_siz(p<<1,l,r);
	if(r>mid)ans+=ask_siz(p<<1|1,l,r);
	return ans;
}
int ask_sum(int p,int l,int r){
	if(l>r)return 0;
	if(t[p].l>=l&&t[p].r<=r)return t[p].sum;
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1,ans=0;
	if(l<=mid)ans=ask_sum(p<<1,l,r);
	if(r>mid)ans+=ask_sum(p<<1|1,l,r);
	return ans;
}
void cover(int p,int pos,int w){
	if(t[p].l==t[p].r&&t[p].l==pos){
		t[p].val=w;
		t[p].sum=w*t[p].siz;
		return ;
	}
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(pos<=mid)cover(p<<1,pos,w);
	else cover(p<<1|1,pos,w);
	update(p);
	return ;
}
void rev(int x){
	if(col[x]){
		col[x]=false;
		int cnt=siz[x]-ask_siz(1,id[x]+1,id[x]+siz[x]-1);
		int y=get_t(fa[x]);
		s[tp[x]].erase(dep[x]);
		int val1=ask_val(1,id[x]);
		int val2=ask_val(1,id[y]);
		change_siz(1,id[x],-cnt);
		cover(1,id[x],0);
		change_siz(1,id[y],cnt);
		change(id[x],id[x]+siz[x]-1,val1-val2);
		change_val(1,id[x],id[x]+siz[x]-1,val2-val1);
	}
	else{
		col[x]=true;
		int cnt=siz[x]-ask_siz(1,id[x]+1,id[x]+siz[x]-1);
		int y=get_t(x);
		s[tp[x]].insert(dep[x]);
		int val2=ask_val(1,id[y]);
		change_siz(1,id[x],cnt);
		change_siz(1,id[y],-cnt);
		cover(1,id[x],val2);
	}
	return ;
}
signed main(){
	n=read();
	m=read();
	for(int i=2;i<=n;i++){
		fa[i]=read();
		add_edge(fa[i],i);
	}
	dep[1]=1;
	dfs(1);
	dfs1(1,1);
	build(1,1,n);
	col[1]=true;
	change_siz(1,id[1],siz[1]);
	s[1].insert(1);
	while(m--){
		op=read(),x=read();
		if(op==1){
			printf("%u\n",ask_val(1,id[get_t(x)])+query(id[x],id[x]));
		}
		if(op==2){
			w=read();
			change_val(1,id[x],id[x],w);
		}
		if(op==3){
			int ans=ask_val(1,id[get_t(x)])*(siz[x]-ask_siz(1,id[x]+1,id[x]+siz[x]-1));
			ans+=query(id[x],id[x]+siz[x]-1);
			ans+=ask_sum(1,id[x]+1,id[x]+siz[x]-1);
			printf("%u\n",ans);
		}
		if(op==4){
			w=read();
			change_val(1,id[x],id[x]+siz[x]-1,w);
		}
		if(op==5||op==6){
			rev(x);
		}
	}
	return 0;
}
上一篇:20210823 数数,数树,鼠树,ckw的树


下一篇:洛谷 P3379 【模板】最近公共祖先(LCA)题解(树剖做法)