20200513 hz【子樹異或和 / 優化多項式乘積(分治FFT) / 最大權路徑(動態DP)】

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

T1:獻給逝去公主的七重奏

題目大意:

原題:Codechef WEASELTX
樹上每個點有權值wiw_i,每次操作爲將所有點的權值變爲子樹中所有點權值的異或和。
Q次詢問(相互獨立),問TT次操作後根節點的權值。

題目分析:

考慮一個點在ii次操作後對它的jj級祖先的貢獻次數fi,jf_{i,j}
有前綴和 fi,j=k=0jfi1,kf_{i,j}=\sum_{k=0}^jf_{i-1,k}
這很像組合數路徑數的形式,轉化一下發現:fi,j=fi1,j+fi,j1f_{i,j}=f_{i-1,j}+f_{i,j-1}
同時有f1,j=1f_{1,j}=1,所以fi,j=(i1+jj)f_{i,j}=\binom {i-1+j} j
那麼對於同一深度的點我們就知道了它的貢獻次數。
(nm)1 (mod 2)\binom nm\equiv1~(\bmod ~2) 當且僅當 n&m=mn\&m=mmm爲1的二進制位nn都必須爲1)
進一步的,(n+mm)1 (mod 2)\binom {n+m}m\equiv1~(\bmod ~2)當且僅當 n&m=0n\&m=0

對於(T1+dd)\binom{T-1+d}d,設dd的最高二進制位爲kk,當T>2kT>2^k時,T1&d=0T-1\&d=0等價於((T1)%2k)&d=0((T-1)\%2^k)\&d=0
所以只需要對T<=2kT<=2^k預處理答案就可以了。
暴力子集卷積是O(3k)O(3^k)的,可以用FWTFWToror卷積,就是O(nlogn)O(n\log n)了。

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序列的轉化後大意爲:給出序列wiw_i,第kk個多項式是i=0n2i+1i!wki+1xi\sum_{i=0}^{n-2}{i+1\over i!}w_k^{i+1}x^inn個多項式乘起來後的xn2x^{n-2}的係數。
n100000,mod=998244353n\le100000,\bmod=998244353

題目分析:

暴力O(n3)O(n^3)NTTNTT優化O(n2logn)O(n^2\log n)
在這裏插入圖片描述
在這裏插入圖片描述
另外一種做法:
把多項式看做無窮項,最終的多項式可以表示爲:iwi(wix+1)ewix=iwii(wix+1)exw\prod_iw_i(w_ix+1)e^{w_ix}\\=\prod_i w_i\prod_i(w_ix+1)e^{x\sum w}
後面的exwe^{x\sum w}可以直接展開,中間的式子分治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,m100000n,m\le100000

題目分析:

兩條相交的鏈,其中一條鏈的LCALCA必然在另一條鏈上。
在每個點uu上存兩個值au,bua_u,b_uaua_u表示鏈LCA爲uu的鏈的權值和,bub_u表示鏈經過點uu但LCA不爲uu的權值和。
接下來的操作這篇博客裏面講的超好。
做一點補充說明(定義都在上面那篇博客中):
動態DP的常見操作重鏈剖分,然後維護輕兒子,然後線段樹維護重鏈的答案。
全局的答案再用一個(可刪)堆維護,每次修改先刪掉原來的,再加入新的。
區間修改bbgg無關,比較好寫。單點修改aa時需要往上跳重鏈修改gggg的維護同樣需要一個堆,而輕兒子對它的貢獻就是它線段樹的 lvlvmax(gv+a[l,v])\max(g_v+\sum a\in[l,v])),同樣要先刪再修改再添加。重鏈上單點的mxmx需要用gu+second gu+au+bug_u+second~g_u+a_u+b_u進行更新,bub_u就是這個點的tagtag
因爲線段樹的查詢都是對整條重鏈的查詢,所以對每條重鏈分別開線段樹就不需要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());
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章