长跑
内存限制:128 MiB
时间限制:1000 ms
【题目描述】
某校开展了同学们喜闻乐见的阳光长跑活动。为了能“为祖国健康工作五十 年”,同学们纷纷离开寝室,离开教室,离开实验室,到操场参加 3000 米长跑运 动。一时间操场上熙熙攘攘,摩肩接踵,盛况空前。 为了让同学们更好地监督自己,学校推行了刷卡机制。 学校中有 n 个地点,用 1 到 n 的整数表示,每个地点设有若干个刷卡机。 有以下三类事件: 1、修建了一条连接 A 地点和 B 地点的跑道。 2、A 点的刷卡机台数变为了 B。 3、进行了一次长跑。问一个同学从 A 出发,最后到达 B 最多可以刷卡多少 次。具体的要求如下: 当同学到达一个地点时,他可以在这里的每一台刷卡机上都刷卡。但每台刷 卡机只能刷卡一次,即使多次到达同一地点也不能多次刷卡。 为了安全起见,每条跑道都需要设定一个方向,这条跑道只能按照这个方向 单向通行。最多的刷卡次数即为在任意设定跑道方向,按照任意路径从 A 地点 到 B 地点能刷卡的最多次数。
【输入描述】
输入的第一行包含两个正整数 n,m,表示地点的个数和操作的个数。 第二行包含 n 个非负整数,其中第 i 个数为第个地点最开始刷卡机的台数。 接下来有 m 行,每行包含三个非负整数 P,A,B,P 为事件类型,A,B 为事件 的两个参数。 最初所有地点之间都没有跑道。 每行相邻的两个数之间均用一个空格隔开。表示地点编号的数均在 1 到 n 之间,每个地点的刷卡机台数始终不超过 10000,P=1,2,3。
【输出描述】
输出的行数等于第 3 类事件的个数,每行表示一个第 3 类事件。如果该情况 下存在一种设定跑道方向的方案和路径的方案,可以到达,则输出最多可以刷卡 的次数。如果 A 不能到达 B,则输出-1。
输入样例:
9 31
10 20 30 40 50 60 70 80 90
3 1 2
1 1 3
1 1 2
1 8 9
1 2 4
1 2 5
1 4 6
1 4 7
3 1 8
3 8 8
1 8 9
3 8 8
3 7 5
3 7 3
1 4 1
3 7 5
3 7 3
1 5 7
3 6 5
3 3 6
1 2 4
1 5 5
3 3 6
2 8 180
3 8 8
2 9 190
3 9 9
2 5 150
3 3 6
2 1 210
3 3 6
输出样例:
-1
-1
80
170
180
170
190
170
250
280
280
270
370
380
580
题解
感觉好LCT啊。。。
先考虑一个简单的问题:
如何在一个支持加边的图中维护两点的连通性?
这不无脑并查集吗。。。
再考虑一个‘简单’的问题:
如何在一个支持加边的图中维护两点是否在同一个边双连通分量?
似乎不会。。。
发现形成的第一个边双连通分量一定是一个环
想了一下,发现可以用LCT+两个并查集
先用一个并查集check来维护连通性
再用一个并查集real来维护边双连通分量的集合
当我们加上一条边(u,v)时:
如果u,v不连通,就把它们在check上合并
如果u,v连通但不在一个边双连通分量,就把它们在LCT上的路径spilt出来,把路径上的点一个一个在real上合并
感觉会T,于是我们缩一下点,把已经合并了real的点从LCT中删掉,只保留它们的祖宗,祖宗要统计它们的所有信息
如果u,v在同一个边双连通分量里,就不管了
让我们回到这个题,我们任务的就是求两点路径的点权和+两点路径上的边双连通分量总点权和
我们就可以用上面的思路来维护边双连通分量的点权和
于是我们维护的就是一个由边双连通分量祖先构成的LCT
如图:
如果在已删除节点之间连边,就会使两个边双祖先合并,就会删掉其中之一
所以不同祖先的已删除节点都无连边。
代码实现还有许多细节:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
int n,m,val[N],a[N];//a是实际点权,val是祖先点权
struct node{
int f[N];
void init(){for(int i=1;i<=n;++i)f[i]=i;}
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void uni(int x,int y){f[find(x)]=find(y);}
}real,check;
int fa[N],ch[N][2],sum[N];
bool rev[N];
inline bool pdc(int x){return ch[fa[x]][1]==x;}
inline bool pdr(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushdown(int x)
{
if(rev[x]){
swap(ch[x][0],ch[x][1]);
if(ch[x][0])rev[ch[x][0]]^=1;
if(ch[x][1])rev[ch[x][1]]^=1;
rev[x]=0;
}
}
void update(int x){sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];}
inline void rot(int x)
{
int y=fa[x],z=fa[y];
bool flg=pdc(x);
if(!pdr(y)) ch[z][pdc(y)]=x;
if(ch[y][flg]=ch[x][flg^1])
fa[ch[y][flg]]=y;
ch[x][flg^1]=y;
fa[y]=x;fa[x]=z;
update(y);update(x);
}
void pdpath(int x){if(!pdr(x)) pdpath(fa[x]);pushdown(x);}
inline void splay(int x)
{
for(pdpath(x);!pdr(x);rot(x))
if(!pdr(fa[x]))
rot(pdc(fa[x])==pdc(x)?fa[x]:x);
}
inline void Access(int x)
{
for(int last=0;x;last=x,x=fa[x]=real.find(fa[x])){//边Access边路径压缩找祖先,因为有可能遇到已删除的节点
splay(x);
ch[x][1]=last;
update(x);
}
}
inline void beroot(int x){Access(x);splay(x);rev[x]^=1;}
inline void link(int x,int y){beroot(x);fa[x]=y;}
void dfs(int &u,int d)//dfs只找LCT上的节点(即未删除节点)
{
if(!u) return;
real.uni(u,d);
val[d]+=val[u];//把所有点权都统计加边双祖先上
dfs(ch[u][0],d);
dfs(ch[u][1],d);
u=0;//删掉节点
}
int main()
{
int i,op,u,v,d;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++)
val[i]=a[i];
real.init();check.init();
for(i=1;i<=m;i++){
scanf("%d%d%d",&op,&u,&v);
if(op==1){
u=real.find(u);v=real.find(v);
if(u==v) continue;
if(check.find(u)!=check.find(v)){link(u,v);check.uni(u,v);}
else{
beroot(u);Access(v);splay(v);//把路径spilt出来
dfs(ch[v][0],v);update(v);
}
}
else if(op==2){
d=real.find(u);splay(d);
val[d]+=v-a[u];a[u]=v;
update(d);
}
else{
if(check.find(u)!=check.find(v)){
printf("-1\n");
continue;
}
u=real.find(u);v=real.find(v);
beroot(u);Access(v);splay(v);
printf("%d\n",sum[ch[v][0]]+val[v]);
}
}
}
好久都没写LCT了。。。