跳转至

[JRKSJ R8] +1-1

题目描述

给你 \(n\) 个点 \(m\) 条边的无向图,每个结点上有一个字符 ( 或者 )

\(q\) 次查询,每次查询给出 \(x,y\),你需要判断是否存在一条从 \(x\)\(y\) 的路径(不需要保证是简单路径)满足将路径上的点上的字符顺次写下来得到的字符串是合法括号串。

输入格式

第一行三个整数 \(n,m,q\)

第二行一个只包含 () 的长度为 \(n\) 的字符串表示结点 \(1\dots n\) 上的字符。

下面 \(m\) 行,每行两个整数 \(u,v\) 表示图上的一条边。

下面 \(q\) 行,每行两个整数 \(x,y\)

输出格式

一个长度为 \(q\) 的 01 串,第 \(i\) 个字符表示第 \(i\) 次询问的答案,1 表示存在这样的路径,0 表示不存在这样的路径。

  • \(A\) 是合法括号串,则 \((A)\) 是合法括号串

  • 除此之外的其他字符串均不是合法括号串

()(()()) 是合法括号串,(()())( 不是合法括号串。

样例解释

为了方便观察,输入的边和询问之间有一个换行。但数据中并不存在这个换行。

https://cdn.luogu.com.cn/upload/image_hosting/x2lp3c7m.png

其中 \(1,2,3\) 号点的字符是 (\(4,5\) 号点的字符是 )

\(1\to 2\):显然,合法括号串不可能以 ( 结尾。 \(3\to 4\):路径 \(3\to 4\) 表示的字符串是 ()\(1\to 4\):路径 \(1\to 3\to 2\to 4\to 5\to 4\) 表示的字符串是 ((()))\(1\to 5\):路径 \(1\to 2\to 4\to 5\) 表示的字符串是 (())\(2\to 5\):路径 \(2\to 3\to 4\to 5\) 表示的字符串是 (())

数据规模与约定

本题采用捆绑测试。

  • Subtask 1(20 pts):\(n,q\leq 500\)\(m \leq 800\)

  • Subtask 2(30 pts):图是森林;

  • Subtask 3(20 pts):\(q\le 10\)

  • Subtask 4(30 pts):无特殊限制。

对于所有数据,满足 \(1\le n,q\le 5\times 10^5\)\(0\le m\le \min(\frac{n\times(n-1)}{2},5\times 10^5)\)\(1\le u,v,x,y\le n\),保证给出的图无重边、无自环。


发现只要哟一条边两端的括号相同,那么就可以无限循环。我们称这种边为括号边,分为(括号边和)括号边。

先不考虑存在括号边的情况,那么自然就是()()..的序列了。假设答案路径不存在括号边,那么怎么做呢?

我们只需要对每一条()或者)(的两端点并入同一个集合D,最后查询询问的a,b是否在同一个集合内即可。这里的一个集合是除去了括号边后的图的联通块。

那么存在括号边呢?发现只要我们可以从a安全地到一条(括号边A,从这条边可以到一条)括号边B,并且从这一条)括号边可以安全地到b即可。安全指的是不经过其它括号边。我们发现只要a所属的集合D中有(括号边的一个节点,那么就可以满足第一个条件。第三个类似。第二个呢?

要判定A,B联通,只要判定a,b是否联通即可。注意这里的联通是在原图上的联通。

还有一个强条件,就是路径长度(边)为奇数。将一个点拆为奇数点和偶数点,看起点(奇数)是否可达终点(偶数)即可。

// Problem: P10572 [JRKSJ R8] +1-1
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P10572
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Challenger: Erica N
// ----
#include<bits/stdc++.h>

using namespace std;
#define rd read()
#define ull unsigned long long
// #define int long long 
#define itn int
#define ps second 
#define pf first


#define rd read()
int read()
{
  int xx = 0, ff = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9')
  {
    if (ch == '-')
      ff = -1;
    ch = getchar();
  }
  while (ch >= '0' && ch <= '9')
    xx = xx * 10 + (ch - '0'), ch = getchar();
  return xx * ff;
}
#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() {
    cerr << endl;
}
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) {
    for (auto v: a) cerr << v << ' ';
    err(x...);
}
template<typename T, typename... A>
void err(T a, A... x) {
    cerr << a << ' ';
    err(x...);
}
#else
#define dbg(...)
#endif
const int N=1e6+5;
const ull P=137;
const int INF=1e9+7;
/*

策略


*/
int fa[N],pa[N];

int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}

int pind(int x){
    if(pa[x]==x)return x;
    return pa[x]=pind(pa[x]);
}


vector<int> e[N];
bool spe[N],c[N],d[N];
string s;

void add(int a,itn b){
    e[a].push_back(b);
    if(s[a]==s[b])spe[a]=spe[b]=1;
    else fa[find(a)]=find(b); //()()..联通块

    pa[pind(a)]=pind(b); //边联通块
}

signed main(){
    int n=rd,m=rd,q=rd;

    cin>>s;
    s=" "+s+s;
    for(int i=1;i<=n*2;i++){
        pa[i]=fa[i]=i;
    }


    for(int i=1;i<=m;i++){
        int a=rd,b=rd;
        add(a,b+n); //奇偶点。因为要求路径长度为奇数
        add(a+n,b);
        add(b+n,a);
        add(b,a+n);
    }


    for(int i=1;i<=n*2;i++){
        int faa=find(i);
        for(auto v:e[i]){
            c[faa]|=(s[v]=='('&&spe[v]);
            d[faa]|=(s[v]==')'&&spe[v]);
        }
    }



    while(q--){
        int a=rd,b=rd;
        if(s[a]==')'||s[b]=='('){
            putchar('0');
        }else if(pind(a)!=pind(b+n)){
            putchar('0');
        }else if(c[find(a)]&&d[find(b+n)]){
            putchar('1');
        }else if(find(a)==find(b+n)){
            putchar('1');
        }else putchar('0');
    }
}

tree and path

给定一个带权的 n 个节点的树和 q 次询问,每次询问一个整数 d,求树上有多少路径满足所有边都被 d 整除。

对于 100% 的数据 1≤n,q≤105,1≤w,d≤105​


发现一条边的边权最多\sqrt w个因数。因此考虑维护集合g,g_i中的边满足其边权被i整除。

对于询问d,我们按g_i中的边作为合并并查集的纽带,最后从并查集中得到答案。并查集维护的是一个联通块,满足边权都可以被d整除。一个大小为sz的联通块,可以产生sz\times (sz-1)\div 2的合法路径。