【学习笔记】NOIP暴零赛2

细思极恐,我的能力已经退步到这个地步了吗?

数据结构

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

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

完了我只会树形dp

完了完了

结论:设 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

似乎机房大部分人都想到了这一点,只有我是joker???

道理很简单。显然这个条件是必要的。其次, 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)一道将数位 d p dp dp,容斥,多项式,高精度强行拼凑的辣鸡的毒瘤签到题

我为什么要写这个代码

#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))的做法。

似乎大家t2都挂分了,但是只有我t2是不会做,我好菜啊???


__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530034.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(7)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示