通道
Luogu
LOJ
UOJ
先在第一棵树上跑边分治。
(边分治大概就是先多叉树转二叉树然后跟点分治差不多的过程。为了标记反向边需要用前向星存图,比较麻烦。)
假设当前选的边是((u,v)),那么((a,b))(默认(a,b)分别和(u,v)在同一个连通块)的贡献就是(w(u,v)+dis(a,u)+dis(b,v)+dep2_a+dep2_b+dep3_a+dep3_b-2(dep2_{lca2(a,b)}+dep3_{lca3(a,b)}))。
(w(u,v))是常数,(dis(a,u),dis(b,v))可以每层分治dfs一遍,(dep2_a+dep2_b+dep3_a+dep3_b)可以最开始预处理。
那么贡献可以表示成(val_a+val_b+k-2(dep2_{lca2(a,b)}+dep3_{lca3(a,b)}))的形式。
我们把(u,v)子树中的点(分别称为白点和黑点)拖到第二棵树上建虚树,然后枚举(a,b)在第二棵树上的lca,假设是(p),那么我们要求的就是(val_a+val_b-2dep3_{lca3(a,b)})的最大值。
假如说(val_u)就是(dep3_u)的话那么这个东西就是第三棵树上的路径,根据两个两个不交点集的直径的端点一定取在这两个点集的两条直径的四个端点中的这个性质,我们可以设计一个简单的虚树上dp。
但问题是这东西并不简单地是第三棵树上的路径,所以看上去我们不能直接用这个结论。
然而你仔细想想这个东西还是满足这个结论的。你可以想象成每个节点下面再挂一个虚点,边权就是(dep2_u+dis(a/b,u))这个东西,两个点之间的距离对应的就是它们的虚点之间的距离。这样这个结论就又可以用了。
那么沿用上面的做法,设计一个简单虚树上dp,(f_{u,0/1})表示(u)子树中白/黑点组成的点集在第三棵树上的直径的两个端点,转移随便搞搞就好了。
点分治有(log n)层,每层建虚树需要(O(nlog n))的复杂度。
其中一个(O(nlog n))是排序,可以用基数排序做到(O(n))但完全没必要了,sort常数很小的。
另一个(O(nlog n))是求lca的,利用st表可以做到(O(n))。
这样我们就可以做到总体的小常数(O(nlog^2n))了。
顺便一提这个代码是真的难写。
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<utility>
#include<vector>
#define pb emplace_back
#define cm(u,v) (dep[u]<dep[v]? u:v)
using std::pair;
using std::vector;
using std::sort;
using std::max;
using std::swap;
using ll=long long;
using pi=pair<int,ll>;
const int N=100007;
ll read(){ll x=0;int c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();return x;}
int lg[N<<1],n,ty[N],q1[N],q2[N],h1,h2;ll ans,val[N];
namespace T3
{
vector<pi>E[N];ll dis[N<<2];int id[N],dep[N],st[20][N<<1],t;
void add(int u,int v,ll w){E[u].pb(v,w),E[v].pb(u,w);}
void dfs(int u,int fa)
{
st[0][id[u]=++t]=u,val[u]+=dis[u];
for(auto[v,w]:E[u]) if(v^fa) dis[v]=dis[u]+w,dep[v]=dep[u]+1,dfs(v,u),st[0][++t]=u;
}
int lca(int u,int v)
{
u=id[u],v=id[v];if(u>v) swap(u,v);int d=lg[v-u+1];
return cm(st[d][u],st[d][v-(1<<d)+1]);
}
ll dist(int u,int v){return val[u]+val[v]-2*dis[lca(u,v)];}
void build(){dfs(1,0);for(int d=1,j=1;d<t;d<<=1,++j)for(int i=1;i+d<=t;++i)st[j][i]=cm(st[j-1][i],st[j-1][i+d]);}
}
namespace T2
{
#define d(u,v) (T3::dist(u,v))
vector<pi>E[N];int t,T,dfi[N],dfo[N],id[N],st[20][N<<1],dep[N],q[N<<1],stk[N],vis[N],hd,tp;ll dis[N];
struct node
{
int u,v;ll w;node(){u=v=w=0;}
node(const int&a,const int&b){u=a,v=b,w=d(u,v);}
node(const int&a,const int&b,const int &c){u=a,v=b,w=c;}
}f[N][2];
int operator<(const node&a,const node&b){return a.w<b.w;}
node operator+(const node&a,const node&b){return !a.u? b:(!b.u? a:max(max(max(a,b),max(node{a.u,b.v},node{a.v,b.u})),max(node{a.u,b.u},node{a.v,b.v})));}
ll cal(const node&a,const node&b){return !a.u||!b.u? 0:max(max(d(a.u,b.u),d(a.v,b.v)),max(d(a.u,b.v),d(a.v,b.u)));}
void add(int u,int v,ll w){E[u].pb(v,w),E[v].pb(u,w);}
void dfs(int u,int fa)
{
st[0][id[u]=++t]=u,dfi[u]=++T,val[u]+=dis[u];
for(auto[v,w]:E[u]) if(v^fa) dis[v]=dis[u]+w,dep[v]=dep[u]+1,dfs(v,u),st[0][++t]=u;
dfo[u]=++T;
}
int cmp(int u,int v){return(u<0?dfo[-u]:dfi[u])<(v<0?dfo[-v]:dfi[v]);}
int lca(int u,int v)
{
u=id[u],v=id[v];if(u>v) swap(u,v);int d=lg[v-u+1];
return cm(st[d][u],st[d][v-(1<<d)+1]);
}
void build(){dfs(1,0);for(int d=1,j=1;d<t;d<<=1,++j)for(int i=1;i+d<=t;++i)st[j][i]=cm(st[j-1][i],st[j-1][i+d]);}
void solve(ll k)
{
hd=0;
for(int i=1;i<=h1;++i) q[++hd]=q1[i];
for(int i=1;i<=h2;++i) q[++hd]=q2[i];
sort(q+1,q+hd+1,cmp);
for(int i=1;i<=hd;++i) vis[q[i]]=1;
for(int i=1,u,t;i<=hd;++i) u=q[i],t=ty[u],f[u][t]=node(u,u,0),f[u][t^1]=node();
for(int i=1,l,m=hd;i<m;++i) if(!vis[l=lca(q[i],q[i+1])]) vis[l]=1,q[++hd]=l,f[l][0]=f[l][1]=node();
for(int i=1;i<=hd;++i) vis[q[i]]=0;
for(int i=1,m=hd;i<=m;++i) q[++hd]=-q[i];
sort(q+1,q+hd+1,cmp);
for(int i=1,u,v;i<=hd;++i)
if(q[i]>0) stk[++tp]=q[i];
else
{
--tp;if(!tp) continue;
u=stk[tp+1],v=stk[tp],ans=max(ans,max(cal(f[u][0],f[v][1]),cal(f[u][1],f[v][0]))+k-dis[v]*2),f[v][0]=f[v][0]+f[u][0],f[v][1]=f[v][1]+f[u][1];
}
}
}
namespace T1
{
int hd[N<<1],ver[N<<2],nx[N<<2],tot=1,sz[N<<1],vis[N<<2],id,mn;ll ev[N<<2],dis[N<<1];
void add(int u,int v,ll w){ver[++tot]=v,nx[tot]=hd[u],ev[tot]=w,hd[u]=tot,ver[++tot]=u,nx[tot]=hd[v],ev[tot]=w,hd[v]=tot;}
namespace OT
{
int id[N],cnt;vector<pi>E[N];
void add(int u,int v,ll w){E[u].pb(v,w),E[v].pb(u,w);}
void ins(int u,int v,ll w){++cnt,T1::add(v,cnt,w),T1::add(id[u],cnt,0),id[u]=cnt;}
void dfs(int u,int fa){for(auto[v,w]:E[u])if(v^fa)ins(u,v,w),dfs(v,u);}
void build(){for(int i=1;i<=n;++i)id[i]=i;cnt=n,dfs(1,0);}
}
int dfs(int u,int fa){sz[u]=1;for(int i=hd[u],v;i;i=nx[i])if((v=ver[i])^fa&&!vis[i])sz[u]+=dfs(v,u);return sz[u];}
void find(int u,int fa,int s){for(int i=hd[u],v;i;i=nx[i])if((v=ver[i])^fa&&!vis[i]){int x=max(sz[v],s-sz[v]);if(x<mn)mn=x,id=i;find(v,u,s);}}
void findl(int u,int fa){if(u<=n)ty[u]=0,q1[++h1]=u;for(int i=hd[u],v;i;i=nx[i])if((v=ver[i])^fa&&!vis[i])dis[v]=dis[u]+ev[i],findl(v,u);}
void findr(int u,int fa){if(u<=n)ty[u]=1,q2[++h2]=u;for(int i=hd[u],v;i;i=nx[i])if((v=ver[i])^fa&&!vis[i])dis[v]=dis[u]+ev[i],findr(v,u);}
void del(int u,int fa){for(int i=hd[u],v;i;i=nx[i])if((v=ver[i])^fa&&!vis[i])del(v,u),vis[i]=vis[i^1]=1;}
void solve(int x)
{
dfs(x,0);if(sz[x]==1)return;
id=0,mn=2e9,find(x,0,sz[x]),vis[id]=vis[id^1]=1;
int u=ver[id],v=ver[id^1];
h1=h2=dis[u]=dis[v]=0,findl(u,0),findr(v,0);
if(!h1) return del(u,0),solve(v);
if(!h2) return del(v,0),solve(u);
for(int i=1;i<=h1;++i) val[q1[i]]+=dis[q1[i]];
for(int i=1;i<=h2;++i) val[q2[i]]+=dis[q2[i]];
T2::solve(ev[id]);
for(int i=1;i<=h1;++i) val[q1[i]]-=dis[q1[i]];
for(int i=1;i<=h2;++i) val[q2[i]]-=dis[q2[i]];
solve(u),solve(v);
}
}
int main()
{
n=read();ll w;
for(int i=2;i<=n<<1;++i) lg[i]=lg[i>>1]+1;
for(int i=1,u,v;i<n;++i) u=read(),v=read(),w=read(),T1::OT::add(u,v,w);
for(int i=1,u,v;i<n;++i) u=read(),v=read(),w=read(),T2::add(u,v,w);
for(int i=1,u,v;i<n;++i) u=read(),v=read(),w=read(),T3::add(u,v,w);
T3::build(),T2::build(),T1::OT::build(),T1::solve(1),printf("%lld",ans);
}
州区划分
Luogu
LOJ
UOJ
一开始以为是神仙题结果看到数据范围发现是锶铂题。
首先我们暴力处理出每个集合是否存在一条Euler回路,同时处理出它的点权和(sum),并设(g(S)=[ ext{There isn't a Eluer circuit in S}]sum_S)。
这里暴力做的复杂度是(O(2^nn^2)),根据超大的时限判断可以过。
然后设(f(S))表示(S)集合的所有划分方案的满意度之和。
可以发现(f(S)=frac1{sum_S}sumlimits_{Tsubseteq S}f(Ssetminus T)g(T))。
这是一个子集卷积的形式,直接FST就好了,复杂度(O(2^nn^2))。
最后总的复杂度为(O(2^nn^2))。
#include<cstdio>
const int N=1<<21,P=998244353;
int n,m,p,U,f[22][N+5],g[22][N+5],e[21],fa[21],deg[21],w[21],cnt[N],sum[N],inv[N];
int read(){int x;scanf("%d",&x);return x;}
int find(int x){return x==fa[x]? x:fa[x]=find(fa[x]);}
int mod(int a){return a+(a>>31&P);}
int power(int a,int k=P-2){int r=1;for(;k;k>>=1,a=1ll*a*a%P)if(k&1)r=1ll*a*r%P;return r;}
int cal(int x){return !p? 1:(p==1? x:x*x);}
int check(int S)
{
int k=cnt[S];
for(int i=0;i<n;++i) if(S>>i&1) sum[S]+=w[i],fa[i]=i,deg[i]=0;
sum[S]=cal(sum[S]);
if(k==1) return 0;
for(int i=0;i<n;++i)
{
if(!(S>>i&1)) continue;
for(int j=i+1;j<n;++j)
{
if(!(S>>j&1)||!(e[i]>>j&1)) continue;
++deg[i],++deg[j];
if(find(i)^find(j)) fa[fa[i]]=j,--k;
}
}
if(k^1) return sum[S];
for(int i=0;i<n;++i) if(S>>i&1&°[i]&1) return sum[S];
return 0;
}
void FWHT(int*a){for(int i=1;i<U;i<<=1)for(int j=0;j<U;j+=i*2)for(int k=0;k<i;++k)a[i|j|k]=mod(a[i|j|k]+a[j|k]-P);}
void iFWHT(int*a){for(int i=1;i<U;i<<=1) for(int j=0;j<U;j+=i*2) for(int k=0;k<i;++k) a[i|j|k]=mod(a[i|j|k]-a[j|k]);}
int main()
{
n=read(),m=read(),p=read(),U=(1<<n)-1;
for(int i=1,u,v;i<=m;++i) u=read()-1,v=read()-1,e[u]|=1<<v,e[v]|=1<<u;
for(int i=0;i<n;++i) w[i]=read();
for(int i=1;i<=U;++i) cnt[i]=cnt[i>>1]+(i&1),g[cnt[i]][i]=check(i),inv[i]=power(sum[i],P-2);
for(int i=2;i<=n;++i) FWHT(g[i]);
f[0][0]=1,FWHT(f[0]);
for(int i=1;i<=n;++i)
{
for(int j=0;j<i;++j) for(int k=0;k<=U;++k) f[i][k]=mod(f[i][k]+1ll*f[j][k]*g[i-j][k]%P-P);
iFWHT(f[i]);
for(int k=0;k<=U;++k) f[i][k]=cnt[k]==i? 1ll*f[i][k]*inv[k]%P:0;
if(i^n) FWHT(f[i]);
}
printf("%d",f[n][U]);
}
即时战略
LOJ
UOJ
先看链的吧。
随便rand一个不在当前已知链上的点,然后看看它在哪边,连过去就好了。
只有在确定它在哪边的时候有可能会有无效操作,这个期望次数是(O(log n))的。
然后看树上的。
还是先rand一个扩展的顺序。
我们用LCT维护每一条已知链,同时记录该点在链上的父亲和儿子。
每次要扩展到一个点(y),先从(1)所在的Splay的根(x)开始,设(z=explore(x,y)),我们进行分类讨论:
(z)是(x)所在链上的父亲:在Splay上跳左儿子。
(z)是(x)所在链上的儿子:在Splay上跳右儿子。
(z)在其它的已知的链上:跳到(z)所在Splay的根。
(z)未知:让(z)当(x)的虚儿子。
这样子做下来大概是(O(nlog n))的。
#include<cstdio>
#include<algorithm>
#include"rts.h"
#define lc ch[x][0]
#define rc ch[x][1]
const int N=300007;
int id[N],ch[N][2],fa[N],L[N],R[N],vis[N];
int nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
int root(int x){while(nroot(x))x=fa[x];return x;}
void pushup(int x){L[x]=R[x]=x;if(lc)L[x]=L[lc];if(rc)R[x]=R[rc];}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=ch[y][1]==x;
if(nroot(y)) ch[z][ch[z][1]==y]=x;
ch[y][k]=ch[x][!k],ch[x][!k]=y,fa[ch[y][k]]=y,fa[y]=x,fa[x]=z,pushup(y);
}
void splay(int x)
{
for(int y;nroot(x);rotate(x)) if(nroot(y=fa[x])) rotate((ch[fa[y]][0]==y)^(ch[y][0]==x)? x:y);
pushup(x);
}
void access(int x){for(int y=0;x;x=fa[y=x])splay(x),rc=y,pushup(x);}
void find(int x)
{
int y=root(1);
while(!vis[x])
{
int z=explore(y,x);
if(z==R[ch[y][0]]) y=ch[y][0];
else if(z==L[ch[y][1]]) y=ch[y][1];
else if(vis[z]) y=root(z);
else vis[z]=1,fa[z]=y,y=z;
}
access(x);
}
void play(int n,int T,int type)
{
for(int i=2;i<=n;++i) id[i]=i;
std::random_shuffle(id+2,id+n+1);
if(type==3)
{
vis[1]=1;
int l=explore(1,id[2]),r=1;
vis[l]=1;
for(int i=2,x;i<=n;++i)
{
if(vis[id[i]])continue;
if(vis[x=explore(l,id[i])]) while(r^id[i]) vis[r=explore(r,id[i])]=1;
else for(vis[l=x]=1;l^id[i];) vis[l=explore(l,id[i])]=1;
}
return;
}
for(int i=1;i<=n;i++) L[i]=R[i]=i;
for(int i=2;i<=n;i++) if(!vis[id[i]]) find(id[i]);
}