传送门:https://www.luogu.com.cn/problem/P7324
好久没写题解了,最近放假闲得无聊,写了写这两天WC的题,顺手来一发题解。
题目大意:给定图,每条边上正反方向分别标有某种括号的左括号和右括号,问有多少点对之间存在能组成合法括号序列的路径。
这道题的关键是:这个“存在合法括号序列路径”到底有什么性质?
如果我们在上建立关系:当且仅当点到点存在合法括号序列路径。方便起见这里姑且把的限制去掉。
那么我们惊奇地发现,居然是个等价关系:
(1)自反性:自己到自己是空串,显然是合法的;
(2)对称性:如果到存在合法路径,那么考虑其翻转之后的路径,其括号序列为的括号序列翻转后左右括号颠倒,容易证明也是合法的;
(3)传递性:如果到存在合法路径,到存在合法路径,则考虑两条路径拼接而成的路径,容易证明也是合法的。
发现了这一点,我们就可以尝试求出所有的等价类,然后所求答案就是对于所有等价类,的和。
怎么求出等价类呢?
我们先从最简单的非空合法序列“”看起,假设点到点有一条“”的边,点到点有一条“”的边。定睛一看,其实就是点向外有两条“”的边。
因此,我们只保留所有的右括号边,这样如果某个点向外有两条相同种类括号的边,就可以把它们的终点合并。实际操作中可以看作用一个新点替代了原先的两个点,并继承了原先两个点的所有出入边。
如果又有点到点有“”的边,点到点有“”的边,则在上述合并操作后原先到的合法路径“”就会变成“”,相当于消去了最内层括号。
如果某两点间存在一条合法路径,我们总能通过不断消去最内层括号的方式把整个括号序列消空,这个事实保证了上述算法的正确性。
现在需要做的,就是用数据结构维护这个合并过程。
每个点要维护若干个集合,分别存储不同类型的边。然后一旦发现某个集合大小,就把这个集合里所有边的终点合并。
这当然是老生常谈的启发式合并,考虑到边的种数很多,可以拿map存每一种边的索引,或者在外面套一个线段树合并。
唯一需要注意的代码细节是在合并的时候有可能将正在处理的集合又拿去和别的集合合并,或者已经被处理过的集合又因为合并上了新的点需要再次处理。
时间复杂度。
代码如下:
#include<bits/stdc++.h>
#define gc getchar()
#define pc putchar
#define li long long
#define pb push_back
#define mp make_pair
#define md int mid = l + r >> 1
#define ln ls[q],l,mid
#define rn rs[q],mid + 1,r
using namespace std;
inline li read(){
li x = 0;
int y = 0,c = gc;
while(c < '0' || c > '9') y = c,c = gc;
while(c >= '0' && c <= '9') x = x * 10 + c - '0',c = gc;
return y == '-' ? -x : x;
}
inline void prt(li x){
if(x >= 10) prt(x / 10);
pc(x % 10 + '0');
}
inline void print(li x){
if(x < 0) pc('-'),x = -x;
prt(x);
}
int n,m,k;
int vis[600010];
int f[300010],sz[300010];
int que[2000010],h,t,cnt,tot;
vector<int> e[600010];
int rt[300010],ls[13000010],rs[13000010],dy[13000010];
inline int getf(int x){
return f[x] == x ? x : f[x] = getf(f[x]);
}
inline void ins(int &q,int l,int r,int x,int y){
if(!q) q = ++cnt;
if(l == r){
if(!dy[q]) dy[q] = ++tot;
e[dy[q]].pb(y);
if(e[dy[q]].size() == 2) que[++t] = dy[q],vis[dy[q]] = 1;
return;
}
md;
if(mid >= x) ins(ln,x,y);
else ins(rn,x,y);
}
int merge(int p,int q,int l,int r){
if(!p || !q) return p + q;
if(l == r){
int u = dy[p],v = dy[q];
if(e[u].size() > e[v].size()) swap(u,v),swap(p,q);
vis[u] = 2;
if(!vis[v]) que[++t] = v,vis[v] = 1;
for(int i = 0;i < e[u].size();++i) e[v].pb(e[u][i]);
e[u].clear();
return q;
}
md;
ls[q] = merge(ls[p],ln);
rs[q] = merge(rs[p],rn);
return q;
}
inline int mg(int u,int v){
u = getf(u);v = getf(v);
if(u == v) return u;
if(sz[u] > sz[v]) swap(u,v);
f[u] = v;sz[v] += sz[u];
rt[v] = merge(rt[u],rt[v],1,k);
return v;
}
int main(){
int i,u,v,w;
n = read();m = read();k = read();
for(i = 1;i <= n;++i) f[i] = i,sz[i] = 1;
for(i = 1;i <= m;++i){
u = read();v = read();w = read();
ins(rt[v],1,k,w,u);
}
while(h < t){
w = que[++h];
if(vis[w] == 2) continue;
u = e[w][0];
for(i = 1;i < e[w].size();++i) u = mg(u,e[w][i]);
if(vis[w] == 2) continue;
e[w].clear();e[w].pb(u);vis[w] = 0;
}
li ans = 0;
for(i = 1;i <= n;++i) if(f[i] == i) ans += 1ll * sz[i] * (sz[i] - 1) / 2;
print(ans);
return 0;
}
尽管的数据范围很大,在内跑出来还是问题不大。