1. 程式人生 > 實用技巧 >訓練指南第第三章-資料結構 刷題記錄

訓練指南第第三章-資料結構 刷題記錄

演算法進階指南第三章 資料結構 刷題記錄

注:前面有一些題目直接跳過了

3.1 基礎資料結構

並查集

UVA1160 X-Plosives

explosive adj. 爆炸性的 volatile adj. 爆炸性的;不穩定的 property n. 性質 occur v. 發生,出現,存在

compound n. 化合物 sequential adj. 連續的,有順序的 cargo n. 貨物 stock n. 庫存 refuse-refusal n.

思路:把每種元素作為點,每次放入的時候如果在同一個集合裡說明形成了環,就 refuse 掉.

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

void merge( int x,int y )
{
    int fx=find(x),fy=find(y);
    if ( fx==fy ) { cnt++; return; }
    fa[fx]=fy;
}

int main()
{
    while ( scanf( "%d",&x )==1 )
    {
        cnt=0;
        for ( int i=0; i<=N-10; i++ )
            fa[i]=i;
        while ( 1 )
        {
            scanf( "%d",&y ); merge( x,y );
            scanf( "%d",&x ); if ( x==-1 ) break;
        }
        printf( "%d\n",cnt );
    }

    return 0;
}

UVA1329 Corporative Network

corporation n. 公司,法人 enterprise n. 企業,事業 telecommunication n. 電訊 amelioration n. 改良,改進

cluster n. 群,叢

思路:並查集維護父親,路徑壓縮的時候維護到父親的距離即可.注意更新距離.

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

void merge( int u,int v )
{
    fa[u]=v;  dis[u]=abs(u-v)%1000;
}

3.2 區間資訊維護與查詢

3.2.1 樹狀陣列

UVA1428 Ping pong

line segment n. 線段 referee n. 裁判員 contest-contestant n. 競爭者

思路:題目的意思就是兩個人的比賽裁判員能力在兩人中間,且房子一定要在他們中間.設 \(a_1\sim a_{i-1}\) 中有 \(c_i\) 個比 \(a_i\) 小,\(a_{i+1}\sim a_n\) 中有 \(d_i\) 個比 \(a_i\) 大,那麼對於一個人 \(i\) ,以他為裁判的比賽數就是 \(c_i\times d_i+(i-1-c_i)\times (n-i-d_i)\)

,那麼問題就在於如何求 \(c_i,d_i\) .

考慮樹狀陣列求逆序對的思路.同理,這裡的 \(a_i\leq 1e5\) ,那麼可以從前到後掃一遍,每次 \(tr[a_j]++\) ,統計 \(a_i\) 的時候就統計 \(1\sim a_i\) 的字首和即可.倒序再做一遍.

void add( int x,int val ) { for ( ; x<=N-10; x+=lowbit(x) ) tr[x]+=val; }		
//注意這裡的值域不是n...調了半天
ll query( int x )
{
    ll res=0;
    for ( ; x; x-=lowbit(x) ) res+=tr[x];
    return res;
}
        for ( int i=1; i<=n; i++ )
            c[i]=query(a[i]),add( a[i],1 );
        memset( tr,0,sizeof(tr) );
        for ( int i=n; i>=1; i-- )
            d[i]=query(a[i]),add(a[i],1);
        ll ans=0;
        for ( int i=1; i<=n; i++ )
            ans=ans+c[i]*(n-i-d[i])+(i-1-c[i])*d[i];

3.2.2 RMQ問題

用倍增的思想計算區間最小值.

\(d[i][j]\) 表示從 \(i\) 開始,長度為 \(2^j\) 的一段元素中的最值,和 LCA 一樣的思想,有 \(d[i][j]=min(d[i][j-1],d[i+2^{j-1}][j-1])\) ,\([L,R]\) 區間的答案即為 \(min(d[L][k],d[R-(1<<k+1)][k])\)

\(2^k\leq R-L+1<2^{k+1}\)

POJ3368 Frequent values

frequent adj. 頻繁的

思路: 第一反應是排序,但是題目中說了 in non-decreasing order 所以根本沒這個必要.那麼首先把所有重複數字轉化成二元組的形式,即(數字,出現次數).然後就是對出現次數序列進行 RMQ ,然後輸出對應的數字即可.

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e5+10;
int n,q,a[N],L[N],R[N],cnt[N],st[N][30];

void ST_init()
{
    for ( int i=1; i<=n; i++ )
        st[i][0]=cnt[i];
    for ( int j=1; (1<<j)<=n; j++ )
     for ( int i=1; (i+(1<<j)-1)<=n; i++ )
        st[i][j]=max( st[i][j-1],st[i+(1<<j-1)][j-1] );
    return;
}

int query( int l,int r )
{
    if ( a[l]==a[r] ) return r-l+1;
    int res=-1000010,k=0;
    res=max( res,max(R[l]-l+1,r-L[r]+1 ) );
    l=R[l]+1; r=L[r]-1;
    if ( l>r ) return res;
    while ( (1<<(k+1))<=r-l+1 ) k++;
    return max( res,max( st[l][k],st[r-(1<<k)+1][k] ) ); 
}

void init()
{
    memset( a,0,sizeof(a) ); memset( L,0,sizeof(L) );
    memset( R,0,sizeof(R) ); memset( cnt,0,sizeof(cnt) );
    memset( st,0,sizeof(st) );
}

int main()
{
    while ( scanf( "%d",&n ) && n )
    {
        init();
        scanf( "%d",&q );
        for ( int i=1; i<=n; i++ )
            scanf( "%d",&a[i] );
        
        int cntk=0,rk=n,rnk=0,k=0;
        for ( int i=1; i<=n+1; i++ )
        {
            if ( a[i]!=a[i-1] )
            {
                int j=i-1;
                while ( j && !cnt[j] ) cnt[j--]=cntk;
                cntk=0;
            }
            cntk++;
        }

        ST_init();
        for ( int i=1; i<=n; i++ )
        {
            if ( a[i]!=a[i-1] ) k=i;
            L[i]=k; int tmp=n-i+1;
            if ( a[tmp]!=a[tmp+1] ) rk=tmp;
            R[tmp]=rk;
        }

        int l,r;
        while ( q-- )
        {
            scanf( "%d%d",&l,&r );
            if ( a[l]==a[r] ) printf( "%d\n",r-l+1 );
            else printf( "%d\n",query( l,r ) );
        }
    }

    return 0;
}

3.2.3 線段樹:點修改

UVA1400 "Ray, Pass me the dishes!"

favor v. 較喜歡 n. 幫助,提拔 represente v. 表現,代表 absolute adj. 絕對的 n. 絕對

consecutive adj. 連貫的,連續不斷的

思路:要求一個動態區間的最大連續和,那麼考慮這個和是由哪些部分組成的,構造一棵線段樹,每個節點記錄,字首最大值,字尾最大值,和整個區間裡的最大連續子段和.

Attention:不能在有返回值的函式裡面不 return ,可能會產生 RE 等奇怪錯誤

struct Segment
{
    int l,r; ll val;
    Segment ( int _l=0,int _r=0,ll _val=0 ) { reset(_l,_r,_val); }
    void reset( int _l,int _r,ll _val ) { l=_l,r=_r,val=_val; }
    Segment operator + ( const Segment &tmp )
    {
        return Segment( min(l,tmp.l),max( r,tmp.r),val+tmp.val );
    }
    bool operator < ( const Segment &tmp ) const 
    {
        if ( val^tmp.val ) return val<tmp.val;
        if ( l^tmp.l ) return l>tmp.l;
        return r>tmp.r;
    }
};
struct SegTree
{
    int l,r; Segment sub,pre,suf;
}tr[N<<2];
int n,m;
ll a[N],s[N];

SegTree pushup ( SegTree t1,SegTree t2 )
{
    SegTree res;
    ll suml=s[t1.r]-s[t1.l-1],sumr=s[t2.r]-s[t2.l-1];
    res.l=t1.l;  res.r=t2.r;
    res.sub=max( t1.suf+t2.pre,max( t1.sub,t2.sub ) );
    res.pre=max( t1.pre,Segment(t1.l,t1.r,suml)+t2.pre );
    res.suf=max( t2.suf,t1.suf+Segment(t2.l,t2.r,sumr) );
    return res;
}

void build( int rt,int l,int r )
{
    if ( l==r )
    {
        tr[rt].l=tr[rt].r=l;
        tr[rt].sub.reset( l,r,a[l] );
        //tr[rt].sub.l=l; tr[rt].sub.r=r; tr[rt].sub.val=a[l];
        tr[rt].pre.reset( l,r,a[l] ); tr[rt].suf.reset( l,r,a[l] );
        return;
    }
    int mid=(l+r)>>1;
    build( lson(rt),l,mid ); build ( rson(rt),mid+1,r );
    tr[rt]=pushup( tr[lson(rt)],tr[rson(rt)] );
}

SegTree query( int rt,int l,int r )
{
    if ( l<=tr[rt].l && r>=tr[rt].r ) return tr[rt];
    int mid=(tr[rt].l+tr[rt].r)>>1;
    if ( l<=mid && r>mid ) return pushup( query(lson(rt),l,r),query(rson(rt),l,r) );
    else if ( r<=mid ) return query( lson(rt),l,r );
    else return query( rson(rt),l,r );
}

3.2.4 線段樹:區間修改

區間加的話打個 tag 然後累加就好了。如果是區間修改那麼還要標記下傳。

UVA11992 Fast Matrix Operations

Initially adv. 最初 Increment n. 增加

思路:題意是在矩陣中支援三種操作:子矩陣區間加,區間修改,區間求和。相當於是一個線段樹的二維區間維護問題。那麼給每一行建一棵線段樹,然後照常維護即可。

但是聽上去很麻煩……有沒有更簡單的方法? 當然是把陣列展開成一行了

線段樹雖然碼量大但確實很常用……寫多了還能增加碼力 (bushi)

tagadd 清零成 -1 的我宛如一個zz……

struct value
{
    int sum,max,min;
    value ( int _sum=0,int _max=0,int _min=0 )
    {
        reset( _sum,_max,_min );
    }
    void reset( int _sum,int _max,int _min )
    {
        sum=_sum,max=_max,min=_min;
    }
};
struct SegTree
{
    int l,r,tagset,tagadd; value val;
    void reset( int _l,int _r,int _tagset,int _tagadd )
    {
        l=_l; r=_r; tagset=_tagset; tagadd=_tagadd;
    }
}tr[N<<2];
int R,C,Q;

value get_value( value a,value b )
{
    return value( a.sum+b.sum,max(a.max,b.max),min(a.min,b.min) );
}

void add_tree( int u,int val )
{
    tr[u].tagadd+=val; tr[u].val.sum+=(tr[u].r-tr[u].l+1)*val;
    tr[u].val.max+=val; tr[u].val.min+=val;
}

void set_tree( int u,int val )
{
    tr[u].tagadd=0; tr[u].tagset=val;
    tr[u].val.sum=(tr[u].r-tr[u].l+1)*val;
    tr[u].val.min=val; tr[u].val.max=val;
}

void pushup( int u )
{
    tr[u].reset( tr[lson(u)].l,tr[rson(u)].r,-1,0 );
    tr[u].val=get_value( tr[lson(u)].val,tr[rson(u)].val );
}

void pushdown( int u )
{
    if ( tr[u].tagset>=0 )
    {
        set_tree( lson(u),tr[u].tagset ); set_tree( rson(u),tr[u].tagset );
        tr[u].tagset=-1;
    }
    if ( tr[u].tagadd )
    {
        add_tree( lson(u),tr[u].tagadd ); add_tree( rson(u),tr[u].tagadd );
        tr[u].tagadd=0;
    }
}

void build( int u,int l,int r )
{
    if ( l==r )
    {
        tr[u].reset( l,r,-1,0 ); tr[u].val.reset( 0,0,0 );  return;
    }
    int mid=(l+r)>>1;
    build( lson(u),l,mid ); build( rson(u),mid+1,r );
    pushup( u );
}

void add_Seg( int u,int l,int r,int val )
{
    if ( l<=tr[u].l && tr[u].r <=r ) { add_tree(u,val); return; }
    pushdown( u );
    int mid=(tr[u].l+tr[u].r)>>1;
    if ( l<=mid ) add_Seg( lson(u),l,r,val );
    if ( r>mid ) add_Seg( rson(u),l,r,val );
    pushup(u);
}

void set_Seg( int u,int l,int r,int val )
{
    if ( l<=tr[u].l && tr[u].r<=r ) { set_tree(u,val); return; }
    pushdown( u );
    int mid=(tr[u].l+tr[u].r)>>1;
    if ( l<=mid ) set_Seg( lson(u),l,r,val );
    if ( r>mid ) set_Seg( rson(u),l,r,val );
    pushup( u );
}

value query( int u,int l,int r )
{
    if ( l<=tr[u].l && tr[u].r<=r ) return tr[u].val;
    pushdown( u );
    value res; res.reset( 0,-inf,inf );
    int mid=(tr[u].l+tr[u].r)>>1;
    if ( l<=mid ) res=get_value( res,query(lson(u),l,r) );
    if ( r>mid ) res=get_value( res,query( rson(u),l,r ) );
    return res;
}

3.5 排序二叉樹

3.5.1 基本概念

性質:滿足所有左子樹的節點都比根節點小,右子樹反之。

最常見的操作是旋轉維護平衡。比如:

陳峰/劉汝佳:本著不要重新發明輪子的作風,如果 set,map 已經可以滿足要求,建議不要實現自己的平衡 BST 這就是工程黨的作風嗎,愛不起來

UVA11020 Efficient Solutions

marriage n. 婚姻生活,結婚 ceremonies n. 儀式 solemn adj. 莊嚴的

sober adj. 冷靜的 reflection n. 映像,沉思,反射 regret n. 後悔,遺憾

mutual recrimination 相互指責 deconstruction n. 解構

eligible adj. 合格的 bachelorette n. 未婚女子 groom n. 新郎

royal n. 王室 lad n. 少年,小夥 lineage n. 血統 charm n. 魅力,魔力

observe v. 觀察 efficient adj. 有能力的 Pareto-optimal 最優的

dominate 優於,高於

思路:顯然,所有被保留的點均在上一個點的下方且在右方(不然易證會有一個點被 pop 掉) ,用 multiset 表示這個點集。如果這個點不會被放入,那麼比較 lower_bound(P)P.y ;如果要用這個點去刪點,那麼從 upper_bound(P) 開始刪除即可。

struct node
{
    int x,y;
    bool operator < ( const node &tmp ) const 
    {
        return x<tmp.x || ( x==tmp.x && y<tmp.y );
    }
};
multiset<node> S;
multiset<node> :: iterator it;

        while ( n-- )
        {
            int x,y; scanf( "%d%d",&x,&y );
            node p=(node){x,y}; it=S.lower_bound(p);
            if ( it==S.begin() ||  (--it)->y>y )
            {
                S.insert(p); it=S.upper_bound(p);
                while ( it!=S.end() && it->y>=y ) S.erase( it++ );
            }
            printf( "%lu\n",S.size() );
        }

3.5.2 用 Treap 實現名次樹

其實就是平衡樹的 kthget_rank .

UVA1479 Graph and Queries

undirected graph 無向圖 vertexes 頂點 encounter v. 遭遇,碰到 indicating 表明

guaranteed v. 保證 adj. 保證的 illegal adj. 非法的 indicate v. 表明,指出

思路:支援刪邊,查詢第 \(k\) 大的點權,修改點權,而且可以離線。那麼就可以把操作倒序,那麼就可以把刪邊換成加邊。然後點權修改就改成插入和刪除。加邊的時候維護一個並查集,如果兩個在同一個裡面就沒有影響,否則合併兩棵平衡樹。稍微優化的話就是把 size 小的樹合併到大的裡面即可。

struct Treap
{
    int val[N],rnd[N],siz[N],son[2][N],num[N];
    void pushup( int u )
    {
        siz[u]=siz[son[0][u]]+siz[son[1][u]]+1;
    }
    int new_node( int v )
    {
        int u=++cnt; val[u]=v,rnd[u]=rand();
        son[0][u]=son[1][u]=0; siz[u]=1; return u;
    }
    int merge( int x,int y )
    {
        if ( !x || !y ) return x+y;
        if ( rnd[x]<rnd[y] )
        {
            son[1][x]=merge( son[1][x],y ); pushup(x); return x;
        }
        else 
        {
            son[0][y]=merge( x,son[0][y] ); pushup( y ); return y;
        }
    }
    void split( int u,int k,int &x,int &y )
    {
        if ( !u ) { x=y=0; return; }
        if ( val[u]<=k ) x=u,split( son[1][u],k,son[1][u],y );
        else y=u,split( son[0][u],k,x,son[0][u] );
        pushup( u );
    }
    int kth( int u,int k )
    {
        if ( !u ) return 0;
        if ( k<=siz[son[0][u]] ) return kth( son[0][u],k );
        else if ( k==siz[son[0][u]]+1 ) return u;
        else return kth( son[1][u],k-siz[son[0][u]]-1 );
    }
    void insert( int &u,int v )
    {
        int now=new_node( v ),tx,ty;
        split( u,v,tx,ty ); u=merge( merge(tx,now),ty );
    }
    void Delete( int &u,int v )
    {
        int tx,ty,tz; split( u,v,tx,tz ); split( tx,v-1,tx,ty );
        ty=merge( son[0][ty],son[1][ty] );
        u=merge( merge(tx,ty),tz );
    }
}tr;

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

void bing( int u,int v )
{
    if ( tr.siz[rt[u]]<tr.siz[rt[v]] ) swap( u,v );
    while ( tr.siz[rt[v]] )
    {
        int now=tr.kth( rt[v],1 );
        tr.insert( rt[u],tr.val[now] ); tr.Delete( rt[v],tr.val[now] );
    }
    fa[v]=u; rt[v]=0;
}

3.5.3 用伸展樹實現可分裂與合併的序列

剛用 FHQ 寫過去的我看著 “實現可分裂合併”宛如zz

UVA11922 Permutation Transformer

思路:???文藝平衡樹模板題???害怕。哦,不太一樣,翻轉之後還要新增到尾部呢qwq 這有區別嗎

struct FHQTreap
{
    int l,r,val,rnd,siz,mark;
}tr[N];
int n,cnt=0,rt=0,m;

void update( int x )
{
    tr[x].siz=tr[tr[x].l].siz+tr[tr[x].r].siz+1;
}

int new_node( int val )
{
    cnt++; tr[cnt].siz=1;  tr[cnt].rnd=rand(); tr[cnt].val=val; tr[cnt].mark=0;
    return cnt;
}

void pushdown( int x )
{
    if ( !x || !tr[x].mark ) return;
    tr[x].mark=0; swap( tr[x].l,tr[x].r );
    if ( tr[x].l ) tr[tr[x].l].mark^=1;
    if ( tr[x].r ) tr[tr[x].r].mark^=1;
    update( x );
}

void split( int now,int k,int &x,int &y )
{
    if ( !now ) { x=y=0; return; }
    pushdown( now );
    if ( k<=tr[tr[now].l].siz ) y=now,split( tr[now].l,k,x,tr[now].l );
    else x=now,split( tr[now].r,k-tr[tr[now].l].siz-1,tr[now].r,y );
    update( now );
}

int merge( int x,int y )
{
    if ( !x || !y ) return x+y;
    pushdown( x ); pushdown( y );
    if ( tr[x].rnd<tr[y].rnd )
    {
        tr[x].r=merge( tr[x].r,y ); update( x ); return x;
    }
    else
    {
        tr[y].l=merge( x,tr[y].l ); update( y ); return y;
    }
}

void dfs( int x )
{
    if ( !x ) return; pushdown( x );
    dfs( tr[x].l );
    printf( "%d\n",tr[x].val );
    dfs( tr[x].r );
}

void reverse( int l,int r )
{
    int t1,t2,t3,t4;
    split( rt,r,t1,t2 ); split( t1,l-1,t3,t4 );
    tr[t4].mark^=1; //rt=merge( merge(t3,t4),t2 );
    rt=merge( merge( t3,t2 ),t4 ); 
}

UVA11996 Jewel Magic

emerald n. 綠寶石,祖母綠 pearl n. 珍珠 consecutive adj. 連貫的 instantly adv. 立即,馬上

denote v. 表示,指示

思路:前三個操作,根據之前的鋪墊,就是顯然的文藝平衡樹+插入刪除而已。但是最後一個 LCP 比較難搞,顯然不能再在上面套一個字尾陣列了……不現實啊。所以只能暴力出奇跡,直接記錄 Hash ,並在找 LCP 的時候暴力二分猜長度即可。(陳峰:效率和SA也就差了兩三倍,所以沒問題!)不過考慮到這裡還要翻轉區間……所以還得多存一個翻轉後區間的 Hash 值,不然翻轉的效率會被拉垮,反正預處理多一遍效率也是一樣的。

int new_node( int x )
{
    int u=++cnt; l(u)=r(u)=mark[u]=0; siz[u]=1;
    val[u]=has[u]=rehash[u]=x; return u;
}

void pushup( int x )
{
    siz[x]=siz[l(x)]+siz[r(x)]+1;
    has[x]=has[l(x)]*powe[siz[r(x)]+1]+val[x]*powe[siz[r(x)]]+has[r(x)];
    rehash[x]=rehash[r(x)]*powe[siz[l(x)]+1]+val[x]*powe[siz[l(x)]]+rehash[l(x)];
}

void reverse( int x )
{
    swap( l(x),r(x) ); swap( has[x],rehash[x] ); mark[x]^=1;
}

void pushdown( int x )
{
    if ( mark[x] ) mark[x]=0,reverse( l(x) ),reverse( r(x) );
}

void split( int x,int k,int &u,int &v )
{
    if ( !x ) { u=v=0; return; }
    pushdown( x );
    if ( k>=siz[l(x)]+1 ) u=x,split( r(x),k-siz[l(x)]-1,r(u),v ),pushup( u );
    else v=x,split( l(x),k,u,l(v) ),pushup( v );
}

void merge( int &x,int u,int v )
{
    if ( !u || !v ) { x=u|v; return; }
    if ( rand()%(siz[u]+siz[v])<siz[u] ) pushdown( u ),x=u,merge( r(x),r(u),v );
    else pushdown( v ),x=v,merge( l(x),u,l(v) );
    pushup( x );
}

void insert( int &x,int pos,int val )
{
    int L,R; split( x,pos,L,R );
    merge( L,L,new_node(val) ); merge( x,L,R );
}

void Delete( int &x,int pos )
{
    int t1,t2,t3; split( x,pos,t1,t2 ); split( t1,pos-1,t1,t3 );
    merge( x,t1,t2 );
}

void reverse( int &x,int l,int r )
{
    int t1,t2,t3; split( x,r,t1,t2 ); split( t1,l-1,t1,t3 );
    reverse( t3 ); merge( t1,t1,t3 ); merge( x,t1,t2 );
}

ull get_hash( int &x,int l,int r )
{
    int t1,t2,t3; split( x,r,t1,t2 ); split( t1,l-1,t1,t3 );
    ull res=has[t3]; merge( t1,t1,t3 ); merge( x,t1,t2 ); return res;
}

int get_LCP( int &x,int l,int r )
{
    int l1=0,r1=n-r+1,res,mid;
    while ( l1<=r1 )
    {
        mid=(l1+r1)>>1;
        if ( get_hash(x,l,l+mid-1)==get_hash(x,r,r+mid-1) ) res=mid,l1=mid+1;
        else r1=mid-1;
    }
    return res;
}

void build( int &x,int l=1,int r=n )
{
    if ( l>r ) return;
    int mid=(l+r)>>1;
    x=new_node( s[mid-1]-'0'+1 );
    build( l(x),l,mid-1 ); build( r(x),mid+1,r ); pushup( x );
}

3.6 小結與習題

基礎資料結構

Broken Keyboard (a.k.a. Beiju Text)

monitor n. 監控,顯示屏 automatically adv. 自動的

思路:我居然不知道 HomeEnd 是幹嘛的……慚愧。\(1e5\) 的資料範圍只要模擬就好了吧,可以用連結串列,不過感覺雙端佇列會好寫一點。哦,不對,雙端佇列的話頭那裡會反過來,得再套個棧。

update:因為沒考慮到兩次 [ 的情況 Wrong Answer 了一發……我好屑/kk

        for ( int i=0; i<len; i++ )
        {
            if ( s[i]=='[' ) 
            { 
                if ( !sta.empty() ) 
                {
                    while ( !sta.empty() ) q.push_front(sta.top()),sta.pop();
                } 
                mark=0; continue; 
            }
            if ( s[i]==']' ) 
            {
                if ( !sta.empty() ) 
                {
                    while ( !sta.empty() ) q.push_front(sta.top()),sta.pop();
                } 
                mark=1; continue; 
            }
            if ( mark ) q.push_back( s[i] );
            else sta.push( s[i] );
        }
        if ( !sta.empty() ) 
        {
            while ( !sta.empty() ) q.push_front( sta.top() ),sta.pop();
        }
        while ( !q.empty() ) printf( "%c",q.front() ),q.pop_front();
        printf( "\n" );

UVA11136 Hoax or what

promotion n. 推銷,促銷,提升,振興 scheme n. & v. 計劃,策劃 client n. 客戶,顧客,委託人

aka=also known as sucker n.笨蛋,易受騙的人, sucker for... 難以抗拒……的人 purchase n.&v. 購買

urn n. 缸 monetary adj. 貨幣的,財政的

思路:大根堆和小根堆……?不過倒是很方便,不需要再插入了。雖然不能同時刪除,但是顯然可以打刪除標記。那麼就做完了。但是好像用 set 會更好寫。

		for ( int i=0; i<n; i++ )
        {
            scanf( "%d",&m );
            for ( int j=0,x; j<m; j++ )
                scanf( "%d",&x ),ms.insert( x );
            
            int l=*ms.begin(),r=*(--ms.end());
            res+=r-l; ms.erase( --ms.end() ); ms.erase( ms.begin() );
        }

UVA12232 Exclusive-OR

思路:很顯然的並查集。設 \(f[x]\) 表示父節點, \(d[x]\) 表示 \(x\) 與父節點的異或值,對於給出權值的節點,將父節點設為0. (這樣在合併的時候需要注意。)查詢時,如果存在聯通集合的個數為奇數(不能被異或抵消),且根節點不是0,那麼就不能確定;否則取所有節點和父節點的異或和即可。

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

void query()
{
    int k,res=0,num[M],vis[M];
    memset( vis,0,sizeof(vis) );
    scanf( "%d",&k );
    for ( int i=0; i<k; i++ )
        scanf( "%d",&num[i] ),num[i]++;
    if ( fl ) return;
    for ( int i=0; i<k; i++ )
    {
        int rt=find( num[i] ),cnt=1;
        if ( rt==0 || vis[i] ) continue;
        for ( int j=i+1; j<k; j++ )
            if ( find(num[j])==rt ) cnt++,vis[j]=1;
        if ( cnt&1 ) { printf( "I don't know.\n" ); return; }
    }
    for ( int i=0; i<k; i++ )
        res^=dis[num[i]];
    printf( "%d\n",res );
}

bool connect()
{
    string s; getline( cin,s );
    int u,v,w,opt=sscanf( s.c_str(),"%d%d%d",&u,&v,&w );
    u++;
    if ( opt==2 ) { w=v,v=0; }
    else v++;
    int fu=find( u ),fv=find( v );
    if ( fu==0 ) swap( fu,fv );
    if ( fu!=fv ) fa[fu]=fv,dis[fu]=dis[u]^dis[v]^w;
    else return (dis[u]^dis[v])!=w;
    return 0;
}

UVA11987 Almost Union-Find

思路:合併集合,基操;集合元素個數,基操;移動元素……??這是啥。首先忽略一個 find 就能判掉。但是刪點……真的很難搞,因為下面可能還掛著一大串,基本就 TLE 了。

但是考慮一件事情:這些點到了另一個集合之後,顯然是不會成為 “根” 的。那麼如果我們要移動節點,不妨在最開始對每個節點建立一個虛點作為 “根” ,這樣每個實點都不會是代表節點,就能移動了。

話說訓練指南好惡心啊……輸出格式都是錯的/kel

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

void init( int n )
{
    for ( int i=1; i<=n; i++ )
    {
        fa[i]=i+n; fa[i+n]=i+n; ans[i+n]=1; res[i+n]=i;
    }
}
//-------------------------
            if ( opt==1 )
            {
                int u,v; scanf( "%d%d",&u,&v );
                u=find(u); v=find(v);
                if ( u==v ) continue;
                ans[v]+=ans[u]; res[v]+=res[u]; fa[u]=v;
            }
            else if ( opt==2 )
            {
                int u,v; scanf( "%d%d",&u,&v );
                int fu=find( u ),fv=find( v );
                ans[fu]--; ans[fv]++;
                res[fu]-=u; res[fv]+=u;
                fa[u]=fv;
            }
            else
            {
                int u; scanf( "%d",&u );
                u=find( u );
                printf( "%d %lld\n",ans[u],res[u] );
            }