【学习笔记】NOIP暴零赛2

数据结构

这题的修改是强行加进去迷惑你的。

考虑怎么求树的带权重心。

结论:设 u u u的子树和为 s z u sz_u szu,所有点权值和为 s s s,那么树的带权重心等价于,满足 s z u ≥ ⌈ s 2 ⌉ sz_u\ge \lceil\frac{s}{2}\rceil szu2s且深度最大的点 u u u

道理很简单。显然这个条件是必要的。其次, u u u肯定在根节点所在的重链上,并且满足条件的点一定是重链的一段前缀,容易发现只有这一段前缀的结尾那个点能作为树的重心。

下面的分析就很简单了。

第一种方法,我们只要能确定一个点一定在重心的子树内,然后从这个点往上跳即可。下一步比较构造,考虑在 dfn \text{dfn} dfn序中找到第一个满足前缀和 ≥ ⌈ s 2 ⌉ \ge \lceil\frac{s}{2}\rceil 2s的点,这个点一定在重心的子树对应的那段 dfn \text{dfn} dfn序上,换句话说一定在重心的子树内,往上跳即可。注意树的重心可能有两个,因此 u u u的父亲也可能是重心,所以当子树和恰好是 s 2 \frac{s}{2} 2s时要返回 u u u的父亲。又因为点权可能为零,因此最后要先跳一段 0 0 0再返回父亲。

第二种方法,记录上一次重心的位置,如果是对链操作,那么新的重心一定在链端点的祖先上,如果是对子树操作,那么新的重心可能在 u u u的祖先上,也可能在 u u u的重链上,直接在重链上跳即可。

复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N=3e5+5;
int n,Q,f[N][20],dfn[N],dep[N],tp[N],sz[N],son[N],rk[N],num;
ll sum;
vector<int>g[N];
struct node{
	ll sum,dat;
}t[N<<2];
void add(int p,int l,int r,ll x){
	t[p].sum+=(r-l+1)*x,t[p].dat+=x;
}
void pushdown(int p,int l,int r){
	int mid=l+r>>1;
	if(t[p].dat){
		add(p<<1,l,mid,t[p].dat),add(p<<1|1,mid+1,r,t[p].dat),t[p].dat=0;
	}
}
void pushup(int p){
	t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
}
void upd(int p,int l,int r,int ql,int qr,ll x){
	if(ql<=l&&r<=qr){
		add(p,l,r,x);return;
	}int mid=l+r>>1;pushdown(p,l,r);
	if(ql<=mid)upd(p<<1,l,mid,ql,qr,x);
	if(mid<qr)upd(p<<1|1,mid+1,r,ql,qr,x);
	pushup(p);
}
ll qry(int p,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr)return t[p].sum;
	int mid=l+r>>1;pushdown(p,l,r);
	if(qr<=mid)return qry(p<<1,l,mid,ql,qr);
	if(mid<ql)return qry(p<<1|1,mid+1,r,ql,qr);
	return qry(p<<1,l,mid,ql,qr)+qry(p<<1|1,mid+1,r,ql,qr);
}
void dfs(int u,int topf){
	sz[u]=1,f[u][0]=topf,dep[u]=dep[topf]+1;
	for(int i=1;i<20;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(auto v:g[u]){
		if(v!=topf){
			dfs(v,u),sz[u]+=sz[v];
			if(sz[v]>sz[son[u]])son[u]=v;
		}
	}
}
void dfs2(int u,int topf){
	tp[u]=topf,dep[u]=dep[topf]+1,dfn[u]=++num,rk[num]=u;
	if(son[u])dfs2(son[u],topf);
	for(auto v:g[u]){
		if(!dfn[v])dfs2(v,v);
	}
}
int Lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=19;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i];
	if(x==y)return x;
	for(int i=19;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
int query(){
	int l=1,r=n,res=0;sum=t[1].sum;
	while(l<=r){
		int mid=l+r>>1;
		if(qry(1,1,n,1,mid)>=(sum+1)/2)res=mid,r=mid-1;
		else l=mid+1;
	}res=rk[res];
	if(qry(1,1,n,dfn[res],dfn[res]+sz[res]-1)<(sum+1)/2){
		for(int i=19;i>=0;i--){
			int u=f[res][i];
			if(u&&qry(1,1,n,dfn[u],dfn[u]+sz[u]-1)<(sum+1)/2)res=u;
		}res=f[res][0];
	}
	if(qry(1,1,n,dfn[res],dfn[res]+sz[res]-1)==sum/2){
		for(int i=19;i>=0;i--){
			int u=f[res][i];
			if(u&&qry(1,1,n,dfn[u],dfn[u]+sz[u]-1)==sum/2)res=u;
		}if(f[res][0])return f[res][0];
		return res;
	}return res;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v,g[u].pb(v),g[v].pb(u);
	}cin>>Q;dfs(1,0),dfs2(1,1);
	for(int i=1;i<=Q;i++){
		int op,x,y,w;
		cin>>op>>x>>y;
		if(op==1){
			upd(1,1,n,dfn[x],dfn[x]+sz[x]-1,y);
		}
		else{
			cin>>w;
			int fx=tp[x],fy=tp[y];
			while(fx!=fy){
				if(dep[fx]>dep[fy])upd(1,1,n,dfn[fx],dfn[x],w),x=f[fx][0];
				else upd(1,1,n,dfn[fy],dfn[y],w),y=f[fy][0];
				fx=tp[x],fy=tp[y];
			}if(dfn[x]>dfn[y])swap(x,y);
			upd(1,1,n,dfn[x],dfn[y],w);
		}
		cout<<query()<<"\n";
	} 
} 

签到

把容斥的式子写出来: ( − 1 ) ∣ S ∣ ( n + m + ( c − 1 ) ∣ S ∣ − ∑ i ∈ S b i m ) (-1)^{|S|}\binom{n+m+(c-1)|S|-\sum_{i\in S}{b^i}}{m} (1)S(mn+m+(c1)SiSbi)

然而直接组合数非常难算。但是注意到 m m m很小,因此我们可以把组合数看成一个多项式

刚开始想的是一些比较特殊的情况,可以简单递推求出,但是发现对于普通的情况难以处理,感觉数位 d p dp dp部分又比较麻烦于是就一无所获了。

首先把 n n n转化成 b b b进制,然后枚举 ∣ S ∣ |S| S。 注意到 ∑ i ∈ S b i \sum_{i\in S}b^i iSbi的数位上都是 1 1 1,因此我们枚举一段前缀就不用考虑负数的情况。问题转化为,从 1 , 2 , . . . , m 1,2,...,m 1,2,...,m中选 k k k个数,记作集合 T T T,对于每个 i ∈ [ 1 , m ] i\in [1,m] i[1,m],求每种情况下 ( ∑ j ∈ T b j ) i (\sum_{j\in T}b_j)^i (jTbj)i的和。最简单的想法是,每次加入一个数时,用二项式定理暴力展开。这可以用 O ( n 4 ) O(n^4) O(n4) d p dp dp预处理求出。

复杂度 O ( n 4 ) O(n^4) O(n4)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int mod=998244353; 
ll B,C,m;
ll fac[100],dp[100][100][100],inv[100],Pw[10000],res,a[105];
string s;
struct bignum{
	int len;
	ll a[10000];
	bignum(){memset(a,0,sizeof a);}
	friend bool operator <(bignum a,bignum b){
		if(a.len<b.len)return 1;
		if(a.len>b.len)return 0;
		for(int i=a.len;i>=0;i--){
			if(a.a[i]!=b.a[i])return a.a[i]<b.a[i];
		}return 0;
	}
	friend bignum operator -(bignum a,bignum b){
		assert(a.len>=b.len);
		bignum c;c.len=a.len;
		for(int i=0;i<=a.len;i++){
			if(a.a[i]<b.a[i])a.a[i]+=B,a.a[i+1]--;
			c.a[i]=a.a[i]-b.a[i];
		}while(c.len>=0&&!c.a[c.len])c.len--;
		return c;
	}
	friend bignum operator +(bignum a,bignum b){
		a.len=max(a.len,b.len);
		for(int i=0;i<=a.len;i++){
			a.a[i]+=b.a[i];
			if(a.a[i]>=B)a.a[i]-=B,a.a[i+1]++;
		}while(a.a[a.len+1])a.len++;
		return a;
	}
	friend bignum operator /(bignum a,ll b){
		ll c=0;
		for(int i=a.len;i>=0;i--){
			c=c*10+a.a[i];a.a[i]=c/b,c%=b;
		} while(a.len>=0&&!a.a[a.len])a.len--;
		return a;
	}
	friend ll operator %(bignum a,ll b){
		ll c=0;for(int i=a.len;i>=0;i--)c=(c*10+a.a[i])%b;
		return c;
	}
}n,n3,n2;
bignum change(ll x){
	bignum a;a.len=-1;
	while(x){
		a.a[++a.len]=x%B,x/=B;
	}return a;
}
bignum Change(bignum x){
	bignum a;a.len=-1;
	while(x.len>=0){
		a.a[++a.len]=x%B,x=x/B;
	}return a;
}
struct poly{
	ll a[100];
	poly(){memset(a,0,sizeof a);}
	friend poly operator *(poly a,poly b){
		poly c;
		for(int i=0;i<=m;i++){
			for(int j=0;j<=m;j++){
				c.a[i+j]=(c.a[i+j]+a.a[i]*b.a[j])%mod;
			}
		}return c;
	}
	friend poly operator +(poly a,poly b){
		for(int i=0;i<=m;i++)a.a[i]=(a.a[i]+b.a[i])%mod;
		return a;
	}
	friend poly operator *(poly a,ll b){
		for(int i=0;i<=m;i++)a.a[i]=a.a[i]*b%mod;
		return a;
	}
}f;
ll pw(ll x,ll y=mod-2){
	ll z(1);
	for(;y;y>>=1){
		if(y&1)z=z*x%mod;
		x=x*x%mod;
	}return z;
}
void init(int n){
	fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
	inv[n]=pw(fac[n]);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod; 
}
ll binom(int x,int y){
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
void dfs(int x,int y,ll z,int l){
	if(x==0){
		if(y==0){
			for(int i=0;i<=m;i++)a[i]=(a[i]+pw(z,i))%mod;
		}
		return;
	}
	if(!l){
		for(int i=0;i<=m;i++){
			for(int j=0;j<=i;j++){
				a[i]=(a[i]+binom(i,j)*dp[x][y][j]%mod*pw(z,i-j))%mod;
			}
		}
		return;
	}if(n3.a[x]>1)dfs(x,y,z,0);
	else if(n3.a[x]==0)dfs(x-1,y,z,1);
	else {
		dfs(x-1,y,z,0);
		if(y)dfs(x-1,y-1,(z+Pw[x])%mod,1);
	}
}
int main(){
	cin>>m>>B>>C,init(m);cin>>s;Pw[0]=1;for(int i=1;i<=m*m;i++)Pw[i]=Pw[i-1]*B%mod;
	reverse(s.begin(),s.end()),n.len=s.size()-1;
	for(int i=0;i<=n.len;i++)n.a[i]=s[i]-'0';ll nn=n%mod;
	n=Change(n);dp[0][0][0]=1;
	for(int i=1;i<=m;i++){
		for(int j=0;j<=m;j++){
			for(int k=0;k<=m;k++){
				dp[i][j][k]=dp[i-1][j][k];
				if(j){
					for(int l=0;l<=k;l++){
						dp[i][j][k]=(dp[i][j][k]+binom(k,l)*dp[i-1][j-1][l]%mod*Pw[i*(k-l)])%mod;
					}
				}	
			}
		}
	}
	for(int i=0;i<=m;i++){
		n2=change((C-1)*i);
		if(C>=1){
			n3=n+change(m-1)+n2;
		}
		else{
			n3=n+change(m-1),n2=change((1-C)*i);
			if(n3<n2)continue;
			n3=n3-n2;
		}
		memset(a,0,sizeof a),memset(f.a,0,sizeof f.a),f.a[0]=1,dfs(n3.len,i,0,1);
		for(int j=0;j<m;j++){
			poly g;g.a[1]=-1,g.a[0]=(nn+m+(C-1)*i-1-j)%mod;
			f=f*g;
		}for(int j=0;j<=m;j++)f.a[j]=f.a[j]*inv[m]%mod;
		for(int j=0;j<=m;j++){
			if(i&1)res=(res-f.a[j]*a[j])%mod;
			else res=(res+f.a[j]*a[j])%mod;
		}
	}
	cout<<(res+mod)%mod;
} 

爆搜

不会。只会 O ( 2 n poly ( n ) ) O(2^n\text{poly}(n)) O(2npoly(n))的做法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值