P5360 [SDOI2019]世界地图 虚树+最小生成树
作者:互联网
题意:
分析:
- 暴力:
每一次拿出对应的边,跑最小生成树,复杂度 \(O(qnm\log)\)
- 正解:
我们有一个很容易得到的想法就是,维护一个前缀 \(mst\) 和一个后缀 \(mst\) 每次将他们两个合并起来,树的动态合并可以通过 \(lct\) 做到,但是我们可能需要一个可持久化 \(lct\) 来维护前后缀(也许真的存在这么一个东西吧)
我们进一步发现,每一次合并会影响到的边只会是那些和两侧的点相连的边(可能是同侧或者异侧),我们称这些可能被删掉的边为关键边,因为内部的边不会因为加入新的边连通性发生变化,所以我们只需要利用虚树的思想,只考虑那些关键边和关键边的点集,维护一下合并时会有哪些关键边被断掉了
具体来说就是合并两个连通块时,我们把最左/右的共 \(2n\) 个关键点标起来,然后我们把两个连通块各自的关键边和连通块之间的边拿出来,跑一遍 \(kruskal\) 建出最小生成树,然后任选一个关键点开始遍历整棵树,遍历的同时把那些有至少两个子树内有关键点 的点标起来,因为这些点一定在关键边的路径上,然后我们在将两个关键点所连路径上最大的边权作为这两个关键点之间的边权(等价于 \(kruskal\) 重构树的操作),然后这些被标记了的点就成了新的 \(MST\) 的关键点,关键点之间的边就成了 \(MST\) 上的树边,然后其他的边不变,直接把边权和继承过来,以上的操作可以通过构建 \(kruskal\) 树或者两遍 \(dfs\) 实现
所以我们需要一个结构体维护 \(MST\)
struct mst
{
int tot;
long long sum;
vector<edge> e;
}
其中 \(tot\) ( \(2n\le tot\) ) 表示关键点个数(其中前 \(n\) 个一定是最左边的 \(n\) ,后 \(n\) 个一定是最右边的 \(n\) 个)
\(sum\) 表示非关键边的边权和,\(e\) 表示关键边的边集
代码:
#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second
using namespace std;
namespace zzc
{
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int maxn = 1e4+5;
const int maxm = 1e6+5;
unsigned int SA, SB, SC;
int lim,n,m,cnt,qt;
long long ans;
int rig[maxn][105],dwn[maxn][105],fa[maxn],head[maxn],siz[maxn],tag[maxn];
struct edge
{
int frm,to,dis;
edge(){}
edge(int frm,int to,int dis):frm(frm),to(to),dis(dis){}
bool operator <(const edge &b)const
{
return dis<b.dis;
}
};
vector<edge> tmp,son[maxn];
struct mst
{
int tot;
long long sum;
vector<edge> e;
mst(){}
mst(int tot,long long sum,vector<edge> e):tot(tot),sum(sum),e(e){}
mst(int *v)
{
tot=n;sum=0;
for(int i=1;i<n;i++) e.pb(edge(i,i+1,v[i]));
}
long long query()
{
long long res=sum;
for(auto i:e) res+=i.dis;
return res;
}
}pre[maxn],suf[maxn];
int getweight()
{
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
unsigned int t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC % lim + 1;
}
void gen()
{
scanf("%d%d%u%u%u%d", &n, &m, &SA, &SB, &SC, &lim);
int i, j;
for(i = 1; i <= n; i++) for(j = 1; j <= m; j++) rig[j][i]=getweight();
for(i = 1; i < n; i++) for(j = 1; j <= m; j++) dwn[j][i]=getweight();
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
{
if(siz[fx]<siz[fy]) swap(fx,fy);
fa[fy]=fx;
siz[fx]+=siz[fy];
}
}
bool dfs1(int u,int ff)
{
int res=0;
for(auto i:son[u])
{
int v=i.to;
if(v==ff) continue;
res+=dfs1(v,u);
}
tag[u]|=(res>=2);
return res+tag[u];
}
void dfs2(int u,int ff,int lst,int val)
{
if(tag[u])
{
if(lst) tmp.pb(edge(tag[u],lst,val));
lst=tag[u];ans-=val;val=0;
}
for(auto i:son[u])
{
int v=i.to;
if(v==ff) continue;
dfs2(v,u,lst,max(val,i.dis));
}
}
mst merge(mst a,mst b,int *v)
{
int tot=a.tot+b.tot;
tmp.clear();
for(auto i:a.e) tmp.pb(i);
for(auto i:b.e) tmp.pb(edge(i.frm+a.tot,i.to+a.tot,i.dis));
for(int i=1;i<=n;i++) tmp.pb(edge(a.tot-n+i,a.tot+i,v[i]));
sort(tmp.begin(),tmp.end());
for(int i=1;i<=tot;i++) fa[i]=i,siz[i]=1,tag[i]=(i<=n||i>tot-n),son[i].clear();
ans=0;
for(auto i:tmp) if(find(i.frm)!=find(i.to))
{
merge(i.frm,i.to);
son[i.frm].pb(edge(i.frm,i.to,i.dis));
son[i.to].pb(edge(i.to,i.frm,i.dis));
ans+=i.dis;
}
dfs1(1,0);
cnt=0;
for(int i=1;i<=tot;i++) if(tag[i]) tag[i]=++cnt;
tmp.clear();
dfs2(1,0,0,0);
return mst(cnt,ans+a.sum+b.sum,tmp);
}
void work()
{
int l,r;
gen();
pre[1]=mst(dwn[1]);suf[m]=mst(dwn[m]);
for(int i=2;i<m;i++) pre[i]=merge(pre[i-1],mst(dwn[i]),rig[i-1]);
for(int i=m-1;i>1;i--) suf[i]=merge(mst(dwn[i]),suf[i+1],rig[i]);
qt=read();
while(qt--)
{
l=read();r=read();
printf("%lld\n",merge(suf[r+1],pre[l-1],rig[m]).query());
}
}
}
int main()
{
zzc::work();
return 0;
}
标签:int,sum,mst,tot,P5360,frm,SDOI2019,虚树,关键点 来源: https://www.cnblogs.com/youth518/p/14254627.html