[BZOJ3394]雪後村莊

題目描述

 


輸入


輸出

輸出q行,每行一個字符串“yes”或“no”(不包括引號)。


樣例輸入
2 4
3 4
1 2 3
2 3 2
2 4 4
1 2 3
1 3 2
2 3 2
3 4 4
4
1 3 3
1 3 2
1 4 3

3 4 4


樣例輸出
no
yes
no

no


數據範圍


來源 by azui



題解 by azui:
考慮離線。受原題的思路影響,我們可能難以注意到,本題並不是要直接求兩點間邊權最小值,而是要求邊權最小值與給定值的大小關係。

n=1 時,我們可以同時將邊和詢問從大到小排序,對邊集做 Kruskal 時用一個指針掃描詢問數組,在適合回答詢問(也就是大於等於該詢問的要求值的邊已經全部添加)時,利用並查集檢查詢問的兩個點是否在同一個集合裏面即可。

如果 n>1,需要同時維護個並查集,只要這個並查集中詢問的兩點均相連即可。出題人覺得作爲一道 NOIP 題,想到這裏應該可以給 70~80 分,但由於技術上的原因無法和求 n 次 lca 的方法區分開,十分遺憾。

捨棄並查集,在每棵樹中賦予每個點一個標號表示它所在的集合,採用啓發式合併來維護標號(兩個塊合併時,暴力遍歷節點數較小的塊,將較小的塊中每個節點的標號改成另一個塊的標號,總複雜度O(nmlogm))。這樣一個點的連通信息可以用一個長度爲的數字串來表示,答案爲 yes 當且僅當詢問的兩個點的連通信息對應的數字串完全匹配,可以用字符串的哈希解決。修改一個點的標號時修改這個點的哈希值即可。


#include<cstdio>
#include<algorithm>
using namespace std;
const int Mod=998244353;
const int N=200005;
const int M=600005;
 
void Getin( int &shu ) {
    char c; int f=1; shu=0;
    for( c=getchar(); c<'0' || c>'9'; c=getchar() ) if( c=='-' ) f=-1;
    for( ; c>='0' && c<='9'; c=getchar() ) shu=shu*10+c-'0';
    shu*=f;
}
 
struct node{
    int s, e, w, id;
    void scan( int &x ) { id=x; Getin(s); Getin(e); Getin(w); }
    bool operator < ( const node &x ) const { return w>x.w; }
}e[N], q[N];
 
int fir[N], ecnt;
struct nodes{ int e, next; } edge[M];
void Link( int s, int e ) {
    edge[++ecnt].e=e; edge[ecnt].next=fir[s]; fir[s]=ecnt;
    edge[++ecnt].e=s; edge[ecnt].next=fir[e]; fir[e]=ecnt;
}
 
int n, m, k[N], esum, qsum;
int b[N], h[N];
void Pre() {
	Getin(n); Getin(m);
    int p=n+7; b[0]=1;
    for( int i=1; i<=n; i++ ) b[i]=1ll*b[i-1]*p%Mod;
}

int col[N];//賦予每戶人家一個標號表示其所在集合
int Pos( int i, int j ) { return ( i-1 )*m+j; }
void Hash( int id, int p, int c ) {
    int t=(p-1)%m+1;
    h[t]=( 1ll*h[t]-1ll*col[p]*b[id]%Mod+Mod )%Mod;
	col[p]=c;
    h[t]=( 1ll*h[t]+1ll*col[p]*b[id]%Mod )%Mod;
}
 
int que[N];
bool vis[N], ans[N];
void BFS( int id, int p, int nowc ) {//暴力遍歷再修改
    int l=1, r=0;
    que[++r]=p;
    while( l<=r ) {
        vis[ p=que[l++] ]=1;
        Hash( id, p, nowc );
        for( int i=fir[p]; i; i=edge[i].next )
            if( !vis[ edge[i].e ] ) que[++r]=edge[i].e;
    }
    for( int i=1; i<=r; i++ ) vis[ que[i] ]=0;
}

int siz[N];
void Comb( int id, int p1, int p2 ) {//合併集合
    int i1=Pos( id, p1 ), reai1=Pos( id, col[i1] );
    int i2=Pos( id, p2 ), reai2=Pos( id, col[i2] );
    if( siz[reai1]<siz[reai2] ) 
        swap( p1, p2 ), swap( i1, i2 ), swap( reai1, reai2 );
    siz[reai1]+=siz[reai2];
    BFS( id, i2, col[i1] );
    Link( i1, i2 );
}
 
void Solve() {
	e[esum+1].w=-1;
    for( int i=1; i<=n; i++ )
        for( int j=1; j<=m; j++ ) {//將每戶人家編號
            int p=Pos( i, j ); siz[p]=1;
            Hash( i, p, j );
        }
    int j=1;
    for( ; j<=qsum && q[j].w>e[1].w; j++ );//先去掉無法通過的
	
    for( int i=1; i<=esum; i++ ) {
        int c1=col[ Pos( e[i].id, e[i].s ) ];
        int c2=col[ Pos( e[i].id, e[i].e ) ];
        if( c1!=c2 ) Comb( e[i].id, e[i].s, e[i].e );//道路兩端點屬於不同集合, 合併
        for( ; j<=qsum && e[i].w>=q[j].w && q[j].w>e[i+1].w; j++ )
            if( h[ q[j].s ]==h[ q[j].e ] ) ans[ q[j].id ]=1;
    }
}
 
int main() {
    Pre();
    for( int i=1; i<=n; i++ ) Getin( k[i] );
    for( int i=esum=1; i<=n; i++ )//標記路在哪一個村莊
        for( int j=1; j<=k[i]; j++, esum++ ) e[esum].scan(i);
    sort( e+1, e+esum+1 );
	
    Getin(qsum);
    for( int i=1; i<=qsum; i++ ) q[i].scan(i);  
    sort( q+1, q+qsum+1 );
	
    Solve();
    for( int i=1; i<=qsum; i++ ) printf( ans[i] ? "yes\n" : "no\n" );
    return 0;
}


發佈了101 篇原創文章 · 獲贊 7 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章