[HNOI2019] 校園旅行(搜索+二分圖性質)

文章目錄

題目

注意數據範圍是N5000N\leq 5000!!

分析

考慮最plain的DP,dp[u][v]=1/0dp[u][v]=1/0表示uuvv是否有一條迴文路徑,如果dp[u][v]=1dp[u][v]=1,那麼dp[x][y]=1dp[x][y]=1xxuu的鄰結點,yyvv的鄰結點,且Numx=NumyNum_x=Num_y)。用Bfs的順序完成這個DP即可,大約是這樣:

queue<Edge> Q;
int Ans[MAXN+5][MAXN+5];
inline void Push(Edge e){
    Q.push(e);
    Ans[e.u][e.v]=Ans[e.v][e.u]=1;
}
for(int i=1;i<=M;i++)
    Push(E[i]);
while(!Q.empty()){
    int u=Q.front().u,v=Q.front().v;Q.pop();
    for(int i=0;i<int(G[u].size());i++){
        int x=G[u][i];
        for(int j=0;j<int(G[v].size());j++){
            int y=G[v][j];
            if(!Ans[x][y]&&Num[x]==Num[y])
                Push((Edge){x,y});
        }
    }
}

於是我們喜聞樂見地發現,對於每條邊最壞情況下會往外擴展另外的所有邊,於是時間複雜度就成了O(M2)O(M^2),TLE,據說有30分。

然後,既然邊太多了,我們就嘗試砍掉一些邊,使新圖和原圖對題意等價。
(由於刪掉一些邊後,原本爲00dp[u][v]dp[u][v]現在肯定也爲00,所以我們只需要讓原本爲11dp[u][v]dp[u][v]不會變成00即可)

對於一條路徑,它一定是由若干段相同權值的點構成的(下圖中點上的數字代表權值):
一條路徑
如果這條路徑是迴文的,則A=E|A|=|E|B=D|B|=|D|

注意題面里加粗的字,一條邊是可以反覆使用的,也就是說,如果A=E2|A|=|E|-2,我們完全可以把一條邊(u,v)A(u,v)\in A多走一遍,那AA就由“uv\cdots\to u\to v\to\cdots”變成了“uvuv\cdots\to u\to v\to u\to v \to\cdots”,這樣一來A|A|就等於E|E|了。

於是得到一個結論:只要A|A|E|E|C|C|D|D|……奇偶性相同,這條路徑就可以視爲迴文的。

好了,圖中的奇偶性問題,果斷考慮二分圖

對於一個只由相同權值的點(例如,00)組成的連通塊,分類討論:

  • 若它是二分圖,那原來在這個聯通塊裏面就一定奇數或偶數條邊的路徑之一
    二分圖
    例如原來某條迴文路徑如圖所示,左進右出,那這條路徑在該二分圖裏面,不管怎麼走,一定都是奇數長度。也就是說,只要這個二分圖是聯通的,原路徑2就可以轉化成奇偶性相同的另一條路徑。那麼我們對該二分圖做一個生成樹,是不會影響dpdp中的值的,邊數大大減少。
  • 如果不是二分圖,原來在這個聯通塊裏面就一定是奇數、偶數條邊的路徑都可以,所以做完生成樹後隨便找一個點,加個自環,就能保證奇數偶數長度的路徑一定都有了,邊數同樣大大減少。

現在其實還有一類邊:第一張圖中的橘色邊,這樣的邊怎麼連是沒有影響的(我們考慮的是點,考慮按同點權的點縮點後,這樣的邊一定是單個出現的,所以沒有影響),所以直接生成樹。

代碼

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;

int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=x*10+(c^48),c=getchar();
    return x;
}

#define MAXN 5000
#define MAXM 500000
#define MAXT 100000
int N,M,T;
int Num[MAXN+5];
char str[MAXN+5];

int Fa[MAXN+5];
int Find(int u){
    return Fa[u]==u?u:Fa[u]=Find(Fa[u]);
}

struct Edge{
    int u,v;
}E[MAXM+5];
vector<int> G[MAXN+5],H[MAXN+5];

queue<Edge> Q;
int Ans[MAXN+5][MAXN+5];
inline void Push(Edge e){
    Q.push(e);
    Ans[e.u][e.v]=Ans[e.v][e.u]=1;
}

int Col[MAXN+5];
bool Check(int u,int c){
    Col[u]=c;
    bool ret=1;
    for(int i=0;i<int(H[u].size());i++){
        int v=H[u][i];
        if(Num[v]==Num[u]){
            if(!Col[v]){
                G[u].push_back(v);
                G[v].push_back(u);
                Push((Edge){u,v});
                ret&=Check(v,c^1);
            }
            else if(Col[v]==c)
                ret=0;
        }
    }
    return ret;
}

int main(){
    N=read(),M=read(),T=read();
    scanf("%s",str+1);
    for(int i=1;i<=N;i++)
        Num[i]=str[i]-'0';
    for(int i=1;i<=M;i++){
        int u=read(),v=read();
        E[i].u=u,E[i].v=v;
        H[u].push_back(v);
        H[v].push_back(u);
    }
    for(int i=1;i<=N;i++)
        Fa[i]=i;
    for(int i=1;i<=N;i++)
        if(!Col[i]){
            if(!Check(i,2))
                G[i].push_back(i);
        }
    for(int i=1;i<=M;i++){
        int u=E[i].u,v=E[i].v;
        if(Num[u]!=Num[v]&&Find(u)!=Find(v)){
            Fa[Find(u)]=Find(v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
    }
    for(int i=1;i<=N;i++)
        Push((Edge){i,i});
    while(!Q.empty()){
        int u=Q.front().u,v=Q.front().v;Q.pop();
        for(int i=0;i<int(G[u].size());i++){
            int x=G[u][i];
            for(int j=0;j<int(G[v].size());j++){
                int y=G[v][j];
                if(!Ans[x][y]&&Num[x]==Num[y])
                    Push((Edge){x,y});
            }
        }
    }
    while(T--)
        puts(Ans[read()][read()]?"YES":"NO");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章