洛谷题目传送门
神仙题
给定一个网格图,每次询问
i
<
l
∣
∣
i
>
r
i<l||i>r
i<l∣∣i>r的点
(
i
,
j
)
(i,j)
(i,j)构成的子图的最小生成树
也就是一个前缀和一个后缀的最小生成树
设
p
r
e
i
pre_i
prei表示前
i
i
i列构成的最小生成树,
s
u
f
i
suf_i
sufi表示
i
i
i~
m
m
m构成的最小生成树
我们考虑一个类似于
l
c
t
lct
lct维护MST的方法合并两个最小生成树
也就是把在连接两个最小生成树的边逐渐加入生成树中,判断是否形成环,如果形成环就去点环上最大的边
但是这样需要记录最小生成树中的边,这个达到了
n
m
nm
nm量级,时间空间都不允许
思考如何把
p
r
e
i
−
1
pre_{i-1}
prei−1和第
i
i
i列合并
如图,假设蓝色边是
M
S
T
MST
MST上的边,红色边是要加入的边
我们把红色边按照边权排序,这样已经被加入的红色边不会再被删除
加入的第一条边可以直接加入
对于可能被删掉的边,我们用绿色表示,比如加入了两条红色边
加入第三条边
事实上,若把第
i
−
1
i-1
i−1列和第
i
i
i列的点称为关键点,那么可能被删除的一定是在关键点之间路径上的边(因为我们把加入的红色边排过序了,所以不会被删)
并且对于一条连接关键点的路径,这条路径上最多只会删除一条边(很显然吧)
考虑在MST上保留关键点构成的虚树,因为一条路径只会被删除一次
所以我们把虚树的边权设为MST上这两点间路径的最大值就行了
合并的时候只需要把两颗虚树合并就行了
因为关键点只有
n
n
n个,所以复杂度就可以保证了
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 110,M = 1e4+7,A=N*M;
#define uint unsigned int
uint SA,SB,SC,lim;
inline int get()
{
SA^=SA<<16;SA^=SA>>5;
SA^=SA<<1;int t=SA;
SA=SB;SB=SC;
SC^=t^SA;
return SC%lim+1;
}
int n,m;
int Id(int x,int y)
{
return (x-1)*m+y;
}
int getx(int id)
{
return (id-1)/m+1;
}
int gety(int id)
{
return (id-1)%m+1;
}
inline int read()
{
int X=0;bool flag=0;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
X=(X<<1)+(X<<3)+ch-'0';
ch=getchar();
}
if(flag) return ~(X-1);
return X;
}
struct edge
{
int x,y,v;
};
edge Rig[N][M],Dwn[N][M];
inline void gen()
{
n=read();m=read();
scanf("%u%u%u%u",&SA,&SB,&SC,&lim);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(j<m) Rig[i][j]=(edge){Id(i,j),Id(i,j+1),get()};
else Rig[i][j]=(edge){Id(i,j),Id(i,1),get()};
for(int i=1;i<n;i++)
for(int j=1;j<=m;j++)
Dwn[i][j]=(edge){Id(i,j),Id(i+1,j),get()};
}
struct tree
{
vector<int> px;
vector<edge> s;
LL sum=0;
inline void addin(int x)
{
for(int i=1;i<=n;i++)
{
px.push_back(Id(i,x));
if(i==n) continue;
s.push_back(Dwn[i][x]);
}
}
inline void Merge(tree &x)
{
sum+=x.sum;
for(edge e:x.s) s.push_back(e);
for(int p:x.px) px.push_back(p);
}
inline LL query()
{
LL res=sum;
for(edge e:s)
res+=e.v;
return res;
}
}pre[M],suf[M];
inline tree merge(int r,tree &x,tree &y)
{
tree res=x;
res.Merge(y);
for(int i=1;i<=n;i++)
res.s.push_back(Rig[i][r]);
return res;
}
vector<edge> G[A];
int fa[A];
void Make(tree &x)
{
for(int p:x.px)
G[p].resize(0),fa[p]=p;
}
bool cmp(edge a,edge b)
{
return a.v<b.v;
}
int Find(int x)
{
if(x==fa[x]) return x;
return fa[x]=Find(fa[x]);
}
inline LL Kruskal(tree &x)
{
LL sum=0;
sort(x.s.begin(),x.s.end(),cmp);
vector<edge> res;
for(edge i:x.s)
{
int u=i.x,v=i.y,w=i.v;
if(Find(u)==Find(v)) continue;
sum+=w;
fa[Find(u)]=Find(v);
res.push_back(i);
G[u].push_back((edge){u,v,w});
G[v].push_back((edge){v,u,w});
}
x.s=res;
return sum;
}
bool ins[A];
int Aux(tree &x)
{
int Mn=1e9,Mx=0,res;
for(int u:x.px)
{
Mn=min(Mn,gety(u));
Mx=max(Mx,gety(u));
}
for(int u:x.px)
if(gety(u)==Mn||gety(u)==Mx) ins[u]=1,res=u;
else ins[u]=0;
return res;
}
int dfs(int x,int pre)
{
int cnt=0;
for(edge i:G[x])
{
int y=i.y;
if(y==pre) continue;
cnt+=dfs(y,x);
}
if(cnt>1) ins[x]=1;
return (int)(ins[x]||cnt);
}
void Build(int x,int pre,int lst,int Mx,tree &T)
{
if(ins[x])
{
if(lst) T.s.push_back((edge){x,lst,Mx}),T.sum-=Mx;
lst=x;
Mx=0;
T.px.push_back(x);
}
for(edge i:G[x])
{
int y=i.y;
if(y==pre) continue;
Build(y,x,lst,max(Mx,i.v),T);
}
}
void change(tree &x,bool opt)
{
Make(x);
LL val=Kruskal(x);
if(!opt) return;
x.sum+=val;
int root=Aux(x);
x.px.resize(0);
x.s.resize(0);
dfs(root,0);
Build(root,0,0,0,x);
}
void init()
{
pre[1].addin(1);
for(int i=2;i<=m;i++)
{
pre[i].addin(i);
pre[i]=merge(i-1,pre[i-1],pre[i]);
change(pre[i],1);
}
suf[m].addin(m);
for(int i=m-1;i>=1;i--)
{
suf[i].addin(i);
suf[i]=merge(i,suf[i],suf[i+1]);
change(suf[i],1);
}
}
LL solve(int l,int r)
{
tree res=merge(m,pre[l-1],suf[r+1]);
change(res,0);
return res.query();
}
int main()
{
gen();
init();
int q=read();
while(q--)
{
int l=read(),r=read();
printf("%lld\n",solve(l,r));
}
return 0;
}