[考试反思]0513省选模拟95:见识
(三道题题目名$OJ$上抄错了两个 )
大约是比较正常的一场。
基本没几档暴力分,没有乱搞,没有构造,三道题都差不多有人讲过($T1$联赛模拟的老套路$T2LNC$的数学专题选讲$T3cqbz$大神的$dp$选讲)
遇到原题/讲过的题就崩这是铁律
但是得亏没去写$T3$不然估计得当场暴毙。。。
三道题打得都是大暴力。$T2$会$50$但是懒得为了$20$分写个多项式再调半天了。
垃圾考试,垃圾的我。
T1:献给逝去公主的七重奏
大意:树,每次操作会使所有节点的权值都变为子树权值的异或和,多次询问$t$次操作后根节点的权值。$n,q \le 2 \times 10^5,t \le 10^9$
考虑每个点对根的贡献,与深度有关,是个组合数。所以同一深度的所有点之间可以合并起来
然后我们要求的就是这个组合数是奇数的点的权值和。
用一下联赛模拟42的结论,我们要求的是所有与$t-1$按位与之后为$0$的$x$的权值和。$FWT$即可。$O(nlogn)$
1 #include<cstdio> 2 #define S 400005 3 int fir[S],l[S],to[S],ec,n,q,maxd,tot[S],w[S]; 4 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 5 void dfs(int p,int d,int fa=0){ 6 tot[d]^=w[p]; if(d>maxd)maxd=d; 7 for(int i=fir[p];i;i=l[i])if(to[i]!=fa)dfs(to[i],d+1,p); 8 } 9 int main(){ 10 scanf("%d%d",&n,&q); 11 for(int i=1,a,b;i<n;++i)scanf("%d%d",&a,&b),link(a,b),link(b,a); 12 for(int i=1;i<=n;++i)scanf("%d",&w[i]); 13 dfs(1,0); int l=1; while(l<=maxd)l<<=1; 14 for(int i=1;i<l;i<<=1)for(int j=0;j<l;j+=i<<1)for(int k=j;k<j+i;++k)tot[k+i]^=tot[k]; 15 for(int i=1,t;i<=q;++i)scanf("%d",&t),printf("%d\n",t?tot[l-1^(t-1&l-1)]:w[1]); 16 }
T2:幽雅地绽放吧,墨染的樱花
大意:每个点有点权$w_i$。满足$i,j$之间有$w_i \times w_j$条边,一棵树的权值定义为所有节点的度数之积。求所有生成树权值和。$n \le 10^5$
度数。应该先想到$prufer$。
每种$prufer$都对应一棵树,每个数字在$prufer$中出现次数就是度数$-1$
同一个形态的树的出现次数是边权的积,也就是$\prod w_i^{deg_i}$。
枚举每个点在$prufer$中的出现次数$a_i$。也就是$deg_i -1$
总的贡献就是$(\prod\limits_{i=1}^{n} w_i ) (n-2)! \times \sum\limits_{a} [\sum\limits_{i=1}^{n} a_i =n-2] \prod\limits_{i=1}^{n} \frac{(a_i+1)w_i^{a_i}}{a_i!}$
上面那个$\prod (a_i+1)$形式很好,也就是说枚举所有$[1,n]$的子集,可选可不选,也就是子集积的和。设$b$为其子集元素。
$(\prod\limits_{i=1}^{n} w_i ) (n-2)! \times \sum\limits_{a} [\sum\limits_{i=1}^{n} a_i =n-2] \prod\limits_{i=1}^{n} \frac{w_i^{a_i}}{a_i!} \sum\limits_{b \subseteq [1,n]} \prod\limits_{i=1}^{n} a_{b_i}$
改变枚举顺序。同时我们把所有在$b$中的元素$x$的$a_x$值减少$1$。
考虑产生的影响,首先$w_i^{a_i}$那里会少算一个$w_i$,提出来一起算,然后分母上的$a_i!$少乘一项也就是整个式子乘了$a_i$,发现后面也有个$\prod a_{b_i}$恰好抵消了,就不用管了
$(\prod\limits_{i=1}^{n} w_i ) (n-2)! \times \sum\limits_{b \subseteq [1,n]} \prod\limits_{i=1}^{n} w_{b_i} \sum\limits_{a} [\sum\limits_{i=1}^{n} a_i =n-2-|b|] \prod\limits_{i=1}^{n} \frac{w_i^{a_i}}{a_i!} $
考虑最后$a$那个部分。如果把式子乘上一个$(n-2-|b|)!$,会结合分母上的阶乘形成一个多重集排列。
考虑实际含义,就是把各种$w_i$填在一个长为$n-2-|b|$的序列上,一个序列的权值是所有元素积,求所有序列的权值和。
所以再把$(n-2-|b|)!$除回去的话我们就能得到我们要求的式子是:
$(\prod\limits_{i=1}^{n} w_i ) (n-2)! \times \sum\limits_{b \subseteq [1,n]} \prod\limits_{i=1}^{n} w_{b_i} \frac{(\sum w_i)^{n-2-|b|} }{ (n-2-|b|)!} $
我们发现现在后面那一坨只与$|b|$有关而与其中具体有哪些元素无关。我们只在意大小。式子的中部是说同一种大小的子集的积的和。
对于每个$i$构造生成函数$F_i(x)=1+w_ix$。把$n$项都乘起来就知道大小为某特定值的权值和。分治$NTT$即可。
1 #include<cstdio> 2 const int S=1<<22,mod=998244353; 3 char IO[S],*p=IO; 4 void rin(int&x){x=0;while(*p<48||*p>57)p++; while(*p>47&&*p<58)x=x*10+*p++-48;} 5 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 6 int mo(int x){return x>=mod?x-mod:x;} 7 int fac[S],inv[S],w[S],n,ans,pw=1,sw,pool[S],L[S],R[S],P,len,rev[S]; 8 #define lc p<<1 9 #define rc lc|1 10 #define md (l+r>>1) 11 void NTT(int*a,int op=1){ 12 for(int i=0,x;i<len;++i)if(rev[i]>i)x=a[i],a[i]=a[rev[i]],a[rev[i]]=x; 13 for(int i=1;i<len;i<<=1)for(int j=0,t=qp(3,(mod-1)/i/2*op+mod-1);j<len;j+=i<<1) 14 for(int k=j,w=1,x,y;k<j+i;++k,w=1ll*w*t%mod) 15 x=a[k],y=1ll*w*a[k+i]%mod,a[k]=mo(x+y),a[k+i]=mo(mod+x-y); 16 if(op==-1)for(int i=0,iv=qp(len,mod-2);i<len;++i)a[i]=1ll*a[i]*iv%mod; 17 } 18 void sat(int x){ 19 len=1;while(len<x)len<<=1; 20 for(int i=0;i<len;++i)rev[i]=rev[i>>1]>>1|(i&1?len>>1:0); 21 } 22 void build(int p,int l,int r){ 23 L[p]=P; R[p]=P+=r-l+1; P++; 24 if(l==r){pool[L[p]]=1;pool[R[p]]=w[l];return;} 25 build(lc,l,md);build(rc,md+1,r); 26 static int A[S],B[S]; sat(r-l+2); 27 for(int i=L[lc];i<=R[lc];++i)A[i-L[lc]]=pool[i]; 28 for(int i=L[rc];i<=R[rc];++i)B[i-L[rc]]=pool[i]; 29 NTT(A);NTT(B);for(int i=0;i<len;++i)A[i]=1ll*A[i]*B[i]%mod;NTT(A,-1); 30 for(int i=L[p];i<=R[p];++i)pool[i]=A[i-L[p]]; 31 for(int i=0;i<len;++i)A[i]=B[i]=0; 32 } 33 int main(){ 34 fread(IO,1,S,stdin); rin(n); 35 for(int i=1;i<=n;++i)rin(w[i]),pw=1ll*pw*w[i]%mod,sw=mo(sw+w[i]); 36 for(int i=fac[0]=1;i<=n;++i)fac[i]=fac[i-1]*1ll*i%mod; 37 inv[n]=qp(fac[n],mod-2); 38 for(int i=n-1;~i;--i)inv[i]=inv[i+1]*(i+1ll)%mod; 39 build(1,1,n); 40 for(int k=n-2,x=1;~k;--k)ans=(ans+1ll*inv[n-2-k]*pool[k]%mod*x)%mod,x=1ll*x*sw%mod; 41 printf("%lld\n",1ll*fac[n-2]*pw%mod*ans%mod); 42 }
T3:竹取飞翔
大意:树,支持插入或删除带权路径,每次操作后询问 求一条路径使得与这条路径相交的路径的权值和尽量大。$n,m \le 10^5$
专题分享的原题。
相交的路径满足其一的$LCA$在另一条路径上。($LCA$相同的情况归类到其中一种)
我们可以维护$A$表示经过该点的路径且$LCA$不是该点的路径权值和,维护$B$表示以该点为$LCA$的路径的权值和。
我们要查询的是每个点 $A$值加上从这个点开始的两条不同下行链的最大$B$值 的最大值。
我们的操作就是要支持 链加$A$,单点加$B$,维护每个点的最大/次大下行链$B$和,维护每个点作为$LCA$时产生的答案。
后两个东西可以直接用一个可删堆解决。
进行树链剖分,那么答案怎么表示?
只考虑路径$LCA$所在的重链,设之为$x$,然后最优路径在某个点$y$处脱离了重链。
这样对答案的贡献就是:$A[x]+totB]x][y]+downB[x]+downB[y]$。其中$downB$特指沿轻链下行最大权值。
所以如果我们维护的好$downB,A$
注意特殊处理$x=y$的情况,其中一个$downB$是次大的。
考虑每次修改的时候我们都干了点啥:
修改$x-LCA,y-LCA$的$A$值,直接区间加。树剖线段树没啥问题。
注意这里的区间加不是单纯的每个点都加,而是点加边减,这样才能保证每条路径被计数一遍。
维护$tag$表示区间加懒标记,$ex$表示左右儿子之间的边权和。区间查询如果跨越中点的话需要减去$ex$
如果能维护出这个东西,我们就能发现其实我们需要的就是一个最大区间和了。
然后跳祖先更新$B$。每次是重链上的前缀加和单点修改($x=y$的情况)
实现细节的话,可以选择对于每条重链开一棵树,常数小,不需要区间查询(直接访问跟节点维护的最优值)
跳重链更新$B$的时候可以剪枝,就是如果和原值一样就不改,查询的方式也可以直接访问跟节点所维护的前缀最优值(维护最大子段和的副产品)减去这个点的$A$值
时间复杂度$O(nlogn)$。代码复杂度螺旋升天。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 400005 4 #define ll long long 5 struct Heap{ 6 priority_queue<ll>p,r; 7 void push(ll x){p.push(x);} 8 void pop(ll x){r.push(x);} 9 ll top(){while(r.size()&&p.top()==r.top())p.pop(),r.pop();return p.top();} 10 ll sec(){ll x=top(),y;pop(x);y=top();push(x);return y;} 11 }ans,s[S>>2]; 12 int n,m,a[S],b[S],w[S],l[S],fir[S],to[S],ec,len[S]; char o[5]; 13 int top[S],dfn[S],tim,f[S],sz[S],son[S],dep[S]; 14 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 15 void dfs(int p,int fa){ 16 sz[p]=1; dep[p]=dep[fa]+1; f[p]=fa; 17 for(int i=fir[p];i;i=l[i])if(to[i]!=fa){ 18 dfs(to[i],p); sz[p]+=sz[to[i]]; 19 if(sz[to[i]]>sz[son[p]])son[p]=to[i]; 20 } 21 } 22 void DFS(int p,int tp){ 23 dfn[p]=++tim; top[p]=tp; 24 if(son[p])DFS(son[p],tp); else ans.push(0),len[tp]=dfn[p]-dfn[tp]; 25 for(int i=fir[p];i;i=l[i])if(!dfn[to[i]])DFS(to[i],to[i]),s[p].push(0); 26 } 27 ll tot[S],mx[S],lmx[S],rmx[S],lz[S],ex[S],exA[S];int rt[S],pc,Lc[S],Rc[S]; 28 #define lc Lc[p] 29 #define rc Rc[p] 30 #define md (L+R>>1) 31 void up(int p){ 32 lmx[p]=max(lmx[lc],tot[lc]+lmx[rc]-ex[p])+lz[p]; 33 rmx[p]=max(rmx[rc],tot[rc]+rmx[lc]-ex[p])+lz[p]; 34 mx[p]=max(max(mx[lc],mx[rc]),lmx[rc]+rmx[lc]-ex[p])+lz[p]; 35 tot[p]=tot[lc]+tot[rc]-ex[p]+lz[p]; 36 } 37 void add(int l,int r,int w,int&p,int L,int R){ 38 if(!p)p=++pc; 39 if(l<=L&&R<=r){lmx[p]+=w;rmx[p]+=w;mx[p]+=w;tot[p]+=w;lz[p]+=w;return;} 40 if(r<=md)add(l,r,w,lc,L,md); else if(l>md)add(l,r,w,rc,md+1,R); 41 else ex[p]+=w,add(l,r,w,lc,L,md),add(l,r,w,rc,md+1,R); up(p); 42 } 43 void chg(int P,ll v1,ll v2,int&p,int L,int R){ 44 if(!p)p=++pc; 45 if(L==R){lmx[p]=rmx[p]=mx[p]=v1+lz[p];mx[p]+=v2;return;} 46 if(P>md)chg(P,v1,v2,rc,md+1,R); else chg(P,v1,v2,lc,L,md); up(p); 47 } 48 void upd(int p){static ll B[S];if(B[p]!=mx[rt[p]])ans.pop(B[p]),ans.push(B[p]=mx[rt[p]]);} 49 bool updB(int p){ 50 static ll B[S]; int F=f[p],T=top[F]; 51 if(lmx[rt[p]]-exA[p]==B[p])return 0; 52 s[F].pop(B[p]); s[F].push(B[p]=lmx[rt[p]]-exA[p]); 53 return chg(dfn[F],s[F].top(),s[F].sec(),rt[T],dfn[T],dfn[T]+len[T]),1; 54 } 55 void upd(int x,int y,int w){int T; 56 while(top[x]!=top[y]){ 57 if(dep[top[x]]<dep[top[y]])swap(x,y); T=top[x]; 58 add(dfn[T],dfn[x],w,rt[T],dfn[T],dfn[T]+len[T]); 59 x=T; exA[x]+=w; updB(x); upd(x); x=f[x]; 60 } 61 if(dfn[x]>dfn[y])swap(x,y); T=top[x]; add(dfn[x],dfn[y],w,rt[T],dfn[T],dfn[T]+len[T]); 62 for(upd(x=T);f[x]&&updB(x);upd(x=top[f[x]])); 63 } 64 int main(){ 65 scanf("%d%d",&n,&m); 66 for(int i=1,x,y;i<n;++i)scanf("%d%d",&x,&y),link(x,y),link(y,x); 67 dfs(1,0); DFS(1,1); 68 for(int i=1,x;i<=m;++i){ 69 scanf("%s",o); 70 if(o[0]=='+')scanf("%d%d%d",&a[i],&b[i],&w[i]),upd(a[i],b[i],w[i]); 71 else scanf("%d",&x),upd(a[x],b[x],-w[x]); 72 printf("%lld\n",ans.top()); 73 } 74 }