20200513 hz【子树异或和 / 优化多项式乘积(分治FFT) / 最大权路径(动态DP)】

WXHCoder Round 9!(取名风格真是女少口阿

T1:献给逝去公主的七重奏
题目大意:

原题:Codechef WEASELTX
树上每个点有权值 w i w_i wi,每次操作为将所有点的权值变为子树中所有点权值的异或和。
Q次询问(相互独立),问 T T T次操作后根节点的权值。

题目分析:

考虑一个点在 i i i次操作后对它的 j j j级祖先的贡献次数 f i , j f_{i,j} fi,j
有前缀和 f i , j = ∑ k = 0 j f i − 1 , k f_{i,j}=\sum_{k=0}^jf_{i-1,k} fi,j=k=0jfi1,k
这很像组合数路径数的形式,转化一下发现: f i , j = f i − 1 , j + f i , j − 1 f_{i,j}=f_{i-1,j}+f_{i,j-1} fi,j=fi1,j+fi,j1
同时有 f 1 , j = 1 f_{1,j}=1 f1,j=1,所以 f i , j = ( i − 1 + j j ) f_{i,j}=\binom {i-1+j} j fi,j=(ji1+j)
那么对于同一深度的点我们就知道了它的贡献次数。
( n m ) ≡ 1   (   m o d     2 ) \binom nm\equiv1~(\bmod ~2) (mn)1 (mod 2) 当且仅当 n & m = m n\&m=m n&m=m m m m为1的二进制位 n n n都必须为1)
进一步的, ( n + m m ) ≡ 1   (   m o d     2 ) \binom {n+m}m\equiv1~(\bmod ~2) (mn+m)1 (mod 2)当且仅当 n & m = 0 n\&m=0 n&m=0

对于 ( T − 1 + d d ) \binom{T-1+d}d (dT1+d),设 d d d的最高二进制位为 k k k,当 T > 2 k T>2^k T>2k时, T − 1 & d = 0 T-1\&d=0 T1&d=0等价于 ( ( T − 1 ) % 2 k ) & d = 0 ((T-1)\%2^k)\&d=0 ((T1)%2k)&d=0
所以只需要对 T < = 2 k T<=2^k T<=2k预处理答案就可以了。
暴力子集卷积是 O ( 3 k ) O(3^k) O(3k)的,可以用 F W T FWT FWT o r or or卷积,就是 O ( n log ⁡ n ) O(n\log n) O(nlogn)了。

Code:

#include<bits/stdc++.h>
#define maxn 270005
using namespace std;
int n,Q,dep[maxn],mxd,s[maxn],ans[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
void dfs(int u,int ff){
	mxd=max(mxd,dep[u]);
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff) dep[v]=dep[u]+1,dfs(v,u);
}
void FMT(int *a,int len){
	for(int i=2,l=1;i<=len;l=i,i<<=1)
		for(int j=0;j<len;j+=i) for(int k=j;k<j+l;k++)
			a[k+l]^=a[k];
}
int main()
{
	int x,y;
	scanf("%d%d",&n,&Q);
	for(int i=1;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
	dfs(1,0);
	for(int i=1;i<=n;i++) scanf("%d",&x),s[dep[i]]^=x;
	int len=1; while(len<=mxd) len<<=1; int all=len-1;
	ans[0]=s[0],FMT(s,len);
	for(int S=1;S<1<<len;S++) ans[S]=s[all-(S-1)];
	while(Q--) scanf("%d",&x),printf("%d\n",ans[x&all]);
}
T2:幽雅的绽放吧,墨染的樱花
题目大意:

经过prufer序列的转化后大意为:给出序列 w i w_i wi,第 k k k个多项式是 ∑ i = 0 n − 2 i + 1 i ! w k i + 1 x i \sum_{i=0}^{n-2}{i+1\over i!}w_k^{i+1}x^i i=0n2i!i+1wki+1xi n n n个多项式乘起来后的 x n − 2 x^{n-2} xn2的系数。
n ≤ 100000 ,   m o d   = 998244353 n\le100000,\bmod=998244353 n100000,mod=998244353

题目分析:

暴力 O ( n 3 ) O(n^3) O(n3) N T T NTT NTT优化 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)
在这里插入图片描述
在这里插入图片描述
另外一种做法:
把多项式看做无穷项,最终的多项式可以表示为: ∏ i w i ( w i x + 1 ) e w i x = ∏ i w i ∏ i ( w i x + 1 ) e x ∑ w \prod_iw_i(w_ix+1)e^{w_ix}\\=\prod_i w_i\prod_i(w_ix+1)e^{x\sum w} iwi(wix+1)ewix=iwii(wix+1)exw
后面的 e x ∑ w e^{x\sum w} exw可以直接展开,中间的式子分治NTT即可。

Code:

#include<bits/stdc++.h>
#define maxn 135005
using namespace std;
const int mod = 998244353;
typedef vector<int> Poly;
int w[maxn],WL,r[maxn],lg[maxn];
int Pow(int a,int b){
	int s=1; for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) s=1ll*s*a%mod;
	return s;
}
void init(int n){
	for(WL=1;WL<=n;WL<<=1); w[0]=1,w[1]=Pow(3,(mod-1)/WL);
	for(int i=2;i<=WL;i++) w[i]=1ll*w[i-1]*w[1]%mod,lg[i]=lg[i>>1]+1;
}
inline void upd(int &x){x+=x>>31&mod;}
void NTT(int *a,int len,int flg){
	for(int i=0;i<len;i++) if(i<r[i]) swap(a[i],a[r[i]]);
	for(int i=2,l=1;i<=len;l=i,i<<=1){
		for(int j=0,v,t=WL/i;j<len;j+=i) for(int k=j,o=0;k<j+l;k++,o+=t)
			v=1ll*w[flg^1?WL-o:o]*a[k+l]%mod,upd(a[k+l]=a[k]-v),upd(a[k]+=v-mod);
	}
	if(flg^1) for(int i=0,Inv=Pow(len,mod-2);i<len;i++) a[i]=1ll*a[i]*Inv%mod;
}
int n,m,a[maxn],sum,Pw=1,fac[maxn],inv[maxn];
Poly Mul(const Poly &a,const Poly &b){
	static int A[maxn],B[maxn]; int n=a.size(),m=b.size(),len=1<<(lg[n+m-1]+1);
	for(int i=0;i<len;i++) A[i]=i<n?a[i]:0, B[i]=i<m?b[i]:0, r[i]=r[i>>1]>>1|(i&1?len>>1:0);
	NTT(A,len,1),NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=1ll*A[i]*B[i]%mod;
	NTT(A,len,-1);
	return Poly(A,A+n+m-1);
}
Poly solve(int i,int l,int r){
	if(l==r) {Poly f(2); f[0]=1,f[1]=a[l]; return f;}
	int mid=(l+r)>>1;
	Poly L=solve(i<<1,l,mid),R=solve(i<<1|1,mid+1,r);
	return Mul(L,R);
}
int main()
{
	scanf("%d",&n); if(n==1) return puts("0"),0;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum=(sum+a[i])%mod,Pw=1ll*Pw*a[i]%mod;
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=n;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
	init(n); Poly F=solve(1,1,n);
	int ans=0;
	for(int i=0,pw=1;i<n-1;i++,pw=1ll*pw*sum%mod) ans=(ans+1ll*pw*inv[i]%mod*F[n-2-i])%mod;
	printf("%d\n",1ll*ans*Pw%mod*fac[n-2]%mod);
}
T3:竹取飞翔
题目描述:

原题:UOJ#268. 【清华集训2016】数据交互
在这里插入图片描述
路径交是指点相交。
n , m ≤ 100000 n,m\le100000 n,m100000

题目分析:

两条相交的链,其中一条链的 L C A LCA LCA必然在另一条链上。
在每个点 u u u上存两个值 a u , b u a_u,b_u au,bu a u a_u au表示链LCA为 u u u的链的权值和, b u b_u bu表示链经过点 u u u但LCA不为 u u u的权值和。
接下来的操作这篇博客里面讲的超好。
做一点补充说明(定义都在上面那篇博客中):
动态DP的常见操作重链剖分,然后维护轻儿子,然后线段树维护重链的答案。
全局的答案再用一个(可删)堆维护,每次修改先删掉原来的,再加入新的。
区间修改 b b b g g g无关,比较好写。单点修改 a a a时需要往上跳重链修改 g g g g g g的维护同样需要一个堆,而轻儿子对它的贡献就是它线段树的 l v lv lv max ⁡ ( g v + ∑ a ∈ [ l , v ] ) \max(g_v+\sum a\in[l,v]) max(gv+a[l,v])),同样要先删再修改再添加。重链上单点的 m x mx mx需要用 g u + s e c o n d   g u + a u + b u g_u+second~g_u+a_u+b_u gu+second gu+au+bu进行更新, b u b_u bu就是这个点的 t a g tag tag
因为线段树的查询都是对整条重链的查询,所以对每条重链分别开线段树就不需要query了,实现也很简单,就动态开点记一下左右儿子和根,范围就是dfs序的范围。

Code:

#include<bits/stdc++.h>
#define maxn 100005
#define LL long long
using namespace std;
int n,m,dep[maxn],fa[maxn],siz[maxn],son[maxn],top[maxn],dfn[maxn],bot[maxn],ln[maxn],tim;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
struct Heap{
	priority_queue<LL>A,B;
	void erase(LL x){B.push(x);}
	void push(LL x){A.push(x);}
	LL top(){while(!B.empty()&&A.top()==B.top()) A.pop(),B.pop(); return A.empty()?0:A.top();}
	LL sec(){
		LL x=top(); if(A.empty()) return 0; A.pop();
		LL y=top(); return A.push(x),y;
	}
}ans,g[maxn];
struct data{
	LL lx,rx,mx,sa,tag;//lx: gv+[l,v]  rx: gu+bu+[u,r]  mx: maxsubsequence  sa:sum of a  tag: add of b
	void add(LL v){rx+=v,mx+=v,tag+=v;}
	data operator + (const data &B){
		return (data){max(lx,sa+B.lx), max(B.rx,rx+B.sa), max(mx,max(B.mx,rx+B.lx)), sa+B.sa, 0};
	}
}t[maxn<<2];
int rt[maxn],lc[maxn<<2],rc[maxn<<2],sz;
void build(int &i,int l,int r){if(i=++sz,l!=r) build(lc[i],l,l+r>>1),build(rc[i],(l+r>>1)+1,r);}
void dfs1(int u,int ff){
	siz[u]=1,dep[u]=dep[fa[u]=ff]+1;
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff){
		dfs1(v,u),siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}
void dfs2(int u,int tp){
	top[u]=tp,ln[dfn[u]=++tim]=u;
	if(!son[u]) ans.push(0),bot[tp]=tim,build(rt[tp],dfn[tp],tim);
	else dfs2(son[u],tp);
	for(int i=fir[u],v;i;i=nxt[i]) if(!top[v=to[i]]) g[u].push(0),dfs2(v,v);
}
void pushdown(int i){if(t[i].tag) t[lc[i]].add(t[i].tag),t[rc[i]].add(t[i].tag),t[i].tag=0;}
void mdf(int i,int l,int r,int x,int y,int v){//b in [x,y] += v
	if(x<=l&&r<=y) return t[i].add(v);
	int mid=l+r>>1; pushdown(i);
	if(x<=mid) mdf(lc[i],l,mid,x,y,v);
	if(y>mid) mdf(rc[i],mid+1,r,x,y,v);
	t[i]=t[lc[i]]+t[rc[i]];
}
void mdfa(int i,int l,int r,int x,int v){//a at x += v
	if(l==r) {t[i].lx+=v,t[i].rx+=v,t[i].mx+=v,t[i].sa+=v; return;}
	int mid=l+r>>1; pushdown(i);
	x<=mid?mdfa(lc[i],l,mid,x,v):mdfa(rc[i],mid+1,r,x,v);
	t[i]=t[lc[i]]+t[rc[i]];
}
void ins(int i,int l,int r,int x){//g changes, recalc.
	if(l==r){
		t[i].lx=g[ln[x]].top()+t[i].sa;
		t[i].rx=t[i].lx+t[i].tag;
		t[i].mx=t[i].rx+g[ln[x]].sec();
		return;
	}
	int mid=l+r>>1; pushdown(i);
	x<=mid?ins(lc[i],l,mid,x):ins(rc[i],mid+1,r,x);
	t[i]=t[lc[i]]+t[rc[i]];
}
#define ERASE ans.erase(t[rt[tp]].mx)
#define PUSH ans.push(t[rt[tp]].mx)
void modify(int u,int v,int w){
	int tp;
	for(;top[u]!=top[v];u=fa[top[u]]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		tp=top[u];
		ERASE; mdf(rt[tp],dfn[tp],bot[tp],dfn[tp],dfn[u],w); PUSH;
	}
	if(dep[u]<dep[v]) swap(u,v);
	if(u!=v){
		tp=top[u];
		ERASE; mdf(rt[tp],dfn[tp],bot[tp],dfn[v]+1,dfn[u],w); PUSH;
		u=v;
	}//now u is LCA.
	if(fa[tp=top[u]]) g[fa[tp]].erase(t[rt[tp]].lx);
	ERASE; mdfa(rt[tp],dfn[tp],bot[tp],dfn[u],w); PUSH;
	if(fa[tp]) g[fa[tp]].push(t[rt[tp]].lx);
	for(u=fa[tp];u;u=fa[tp]){
		if(fa[tp=top[u]]) g[fa[tp]].erase(t[rt[tp]].lx);
		ERASE; ins(rt[tp],dfn[tp],bot[tp],dfn[u]); PUSH;
		if(fa[tp]) g[fa[tp]].push(t[rt[tp]].lx);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
	dfs1(1,0),dfs2(1,1);
	static int x[maxn],y[maxn],z[maxn]; char op[3];
	for(int i=1,t;i<=m;i++){
		if(scanf("%s",op),op[0]=='+') scanf("%d%d%d",&x[i],&y[i],&z[i]),modify(x[i],y[i],z[i]);
		else scanf("%d",&t),modify(x[t],y[t],-z[t]);
		printf("%lld\n",ans.top());
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值