「筆記」圖論雜題選做


1. NOIP 2014 聯合權值

題目大意:一棵 n 個點的樹,每一個點有一個點權。若兩個點距離為 2,那么它們就會產生它們的點權之積的聯合權值。求整棵樹的聯合權值之和。1≤n≤2×105

Solution:

若距離為 2,則一定存在一個中轉點。

枚舉這個中轉點,周圍點權之積可以直接算。

假設每個中轉點周圍有兩個點,權值分別為 a、b,則聯合權值為 2ab=(a+b)2-(a2+b2)。

若有三個點,權值分別為 a、b、c,則聯合權值為 2ab+2bc+2ac=(a+b+c)2-(a2+b2+c2)。

綜上,以某個節點為中轉點的聯合權值之和等於權值和的平方減去權值的平方和。

為了找到最大的聯合權值,只需找到周圍最大的兩個權值 mx1,mx2,相乘判斷即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,mod=10007;
int n,x,y,w[N],cnt,hd[N],to[N<<1],nxt[N<<1],k1,k2,mx1,mx2,ans1,ans2;
void add(int x,int y){
    to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
}
signed main(){
    scanf("%lld",&n);
    for(int i=1;i<n;i++){
        scanf("%lld%lld",&x,&y);
        add(x,y),add(y,x);
    } 
    for(int i=1;i<=n;i++)
        scanf("%lld",&w[i]);
    for(int x=1;x<=n;x++){
        mx1=mx2=k1=k2=0;
        for(int i=hd[x];i;i=nxt[i]){
            int y=to[i];
            if(w[y]>mx1) mx2=mx1,mx1=w[y];
            else if(w[y]>mx2) mx2=w[y];
            k1=(k1+w[y]%mod)%mod,k2=(k2+w[y]%mod*w[y]%mod)%mod;
        }
        k1=k1*k1%mod,ans2=(ans2+k1-k2+mod)%mod,ans1=max(ans1,mx1*mx2);
    }
    printf("%lld %lld\n",ans1,ans2);
    return 0;
}

2. NOI 2001 食物鏈

題目大意:

有三種生物 A,B,C,A 吃 B,B 吃 C,C 吃 A。現在有 n 個生物,還有 k 個條件,每一個條件形如下面一種:

  • X 和 Y 是同類。
  • X 吃 Y。

問你有多少句話和前面的話沖突。n≤5×105,k≤105

Solution:

設 X1, X2, X3 分別表示自己,食物和天敵,用 x, x+n, x+2*n 表示。

用並查集維護,如果在任何時刻 X1, X2, X3 在同一個集合中那就不合法。

時間復雜度 O(kα(n))。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5; 
int n,k,x,y,z,ans,f[N];
int get(int x){
    return f[x]==x?x:f[x]=get(f[x]);
}
signed main(){
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=3*n;i++) f[i]=i;
    for(int i=1;i<=k;i++){
        scanf("%lld%lld%lld",&z,&x,&y);
        if(x>n||y>n) ans++;
        else if(z==1){
            if(get(x)==get(y+n)||get(x)==get(y+2*n)){ans++;continue;}
            f[get(f[x])]=get(f[y]);
            f[get(f[x+n])]=get(f[y+n]);
            f[get(f[x+2*n])]=get(f[y+2*n]);
        }
        else if(z==2){
            if(x==y||get(x)==get(y)||get(x)==get(y+n)){ans++;continue;}
            f[get(f[x])]=get(f[y+2*n]);
            f[get(f[x+n])]=get(f[y]);
            f[get(f[x+2*n])]=get(f[y+n]);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

3. NOIP 2013 車站分級

題目大意:一條單向的鐵路線上,依次有編號為 1,2,...,n 的 n 個火車站。每個火車站都有一個級別,最低為 1 級。現有若干趟車次在這條線路上行駛,每一趟都滿足如下要求:
如果這趟車次停靠了火車站 x,則始發站、終點站之間所有級別大於等於火車站 x 的都必須停靠。(注意:起始站和終點站自然也算作事先已知需要停靠的站點)。現有 m 趟車次的運行情況(全部滿足要求),問這 n 個火車站至少分為幾個不同的級別。n,m≤1000。

Solution:

對於一列火車,停靠的所有車站級別一定大於未停靠的,相當於拓撲排序中的偏序關系。由於一定滿足條件,因此一定能得到一個有向無環圖。

拓撲排序的時候順帶進行 DP,設 f[i] 表示 i 的最小級別。

直接暴力建圖,時間復雜度 O(mn2)。

可以引入輔助點,時間復雜度降到 O(nm)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+5,M=1e6+5;
int n,m,x,a[N],cnt,hd[N],to[M],nxt[M],f[N],in[N],ans;
bool vis[N],mp[N][N];
queue<int>q;
void add(int x,int y){
    to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
}
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%lld",&x);
        memset(vis,0,sizeof(vis));
        for(int j=1;j<=x;j++)
            scanf("%lld",&a[j]),vis[a[j]]=1;
        for(int j=a[1];j<=a[x];j++) 
            if(!vis[j]) for(int k=1;k<=x;k++) 
                if(!mp[a[k]][j]) mp[a[k]][j]=1,in[j]++,add(a[k],j);    //把一個車次中停靠的車站與一個車次的所有不停靠的車站連邊,前提是沒有連過,再將不停靠的車站入讀加一 
    }
    for(int i=1;i<=n;i++)
        if(!in[i]) q.push(i);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=hd[x];i;i=nxt[i]){
            int y=to[i];
            if(--in[y]==0) f[y]=f[x]+1,q.push(y);
        }
    }
    for(int i=1;i<=n;i++)
        ans=max(ans,f[i]);
    printf("%lld",ans+1);
    return 0;
}

4.  Luogu P4643 阿狸和桃子的游戲

題目大意:有一張 n 個點 m 條邊的圖,點有點權,邊有邊權。先手后手輪流染黑白兩色,最后的得分是自己染的點權和 + 兩端均為自己的顏色的邊權和。雙方都希望自己的得分-對手的得分最大,求結果。1≤n≤104,0≤m≤105

Solution:

把一個點 u 的點權變為 2w(u)+∑w(u,v),邊權變為 0。

接下來貪心選取。

可以發現同色邊權不會發生變化,異色邊權兩方分數可以相互抵消。

時間復雜度 O(n log n)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5;
int n,m,a[N],x,y,z,ans;
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]),a[i]*=2;
    for(int i=1;i<=m;i++){
        scanf("%lld%lld%lld",&x,&y,&z);
        a[x]+=z,a[y]+=z;
    }
    sort(a+1,a+1+n);
    for(int i=n;i>=1;i-=2)
        ans+=a[i]-a[i-1];
    printf("%lld\n",ans/2);
    return 0;
}

5. NOIP 2015 運輸計划

題目大意:一棵 n 個點的樹,給你 m 條路徑。你需要選出一條邊把邊權改成 0,讓這些路徑長度的最大值最小。n,m≤3×105

Solution:

最大值最小,先二分。

二分之后,有一些路徑的長度已經滿足要求了,直接忽略。

剩下的路徑由於太長,所以每一條路徑都必須經過被刪除的邊。

假設有 k 條超過限制的路徑,對於每一條路徑,我們把這條路徑上所有邊的 cnt+1 ,然后看被所有 cnt=k 的邊,刪掉其中最長的邊,看是否能滿足條件。

對於 cnt+1 的操作,我們可以用樹上差分實現。每次 cnt[u]++,cnt[v]++,cnt[lca]-=2。

時間復雜度 O((n+m) log n),有點卡常。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int n,m,x,y,z,cnt,hd[N],to[N<<1],nxt[N<<1],val[N<<1],u[N],v[N],lca[N],dis[N],dep[N],d[N],f[N][30],sum,b[N],top,l,r,mid,ans,c[N];
int add(int x,int y,int z){
    to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt,val[cnt]=z;
} 
void dfs(int x,int fa){
    b[++top]=x,dep[x]=dep[fa]+1;    //b 數組的作用:找到葉節點向上累加
    for(int i=0;i<=19;i++)
        f[x][i+1]=f[f[x][i]][i];
    for(int i=hd[x];i;i=nxt[i]){
        int y=to[i];
        if(y==fa) continue;
        f[y][0]=x,d[y]=d[x]+val[i],dfs(y,x);
    }
}
int LCA(int x,int y){    //倍增求 LCA
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=20;i>=0;i--){
        if(dep[f[x][i]]>=dep[y]) x=f[x][i];
        if(x==y) return x; 
    }
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
bool solve(int mid){
    int k=0,ans=0;
    memset(c,0,sizeof(c));    //cnt 數組 
    for(int i=1;i<=m;i++)
        if(dis[i]>mid){
            c[u[i]]++,c[v[i]]++,c[lca[i]]-=2;    //樹上差分 
            ans=max(ans,dis[i]-mid),k++;
        }
    if(!k) return 1;
    for(int i=n;i>=1;i--)
        c[f[b[i]][0]]+=c[b[i]];
    for(int i=2;i<=n;i++)
        if(c[i]==k&&d[i]-d[f[i][0]]>=ans) return 1;    //找出被所有超過限制的路徑覆蓋的最長路徑
    return 0;
}
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<n;i++){
        scanf("%lld%lld%lld",&x,&y,&z);
        add(x,y,z),add(y,x,z),sum+=z;
    }
    dfs(1,0);
    for(int i=1;i<=m;i++){
        scanf("%lld%lld",&u[i],&v[i]);
        lca[i]=LCA(u[i],v[i]),dis[i]=d[u[i]]+d[v[i]]-2*d[lca[i]];    //點x與點y之間的距離=x的深度+y的深度-2*LCA(x,y)的深度 
    }
    l=0,r=sum;
    while(l<=r){
        mid=(l+r)/2;
        if(solve(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}

6. CF1349C Orac and Game of Life

題目大意:有一個 n×m 的網格,每一個格子初始是黑或白兩種顏色。

接下來的每一個時刻,對於一個格子 (i,j):

  • 若相鄰的格子中有至少一個格子和它的顏色相同,那么這個格子的顏色取反。
  • 否則,顏色不變。

q 次詢問,每次問你 (x,y) 在第 t 時刻的顏色。1≤n,m≤103,1≤q≤105,1≤t≤1018

Solution:

首先我們找規律。

注意到,一旦某個連通塊的大小超過 1,那么它就會無限 01 反轉下去。

而如果周圍有大小為 1 的連通塊,那么這兩個連通塊就會合並,然后繼續一起反轉。

對於每一個格子,我們預處理一個 ci,j,表示這個位置在這個時刻就會進入一個大小大於 1 的連通塊。

對於詢問,如果 t≤ci,j 那就不變,否則就黑白交錯出現。

由於邊權都是 1,所以可以直接 bfs。時間復雜度 O(nm+q)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+5;
int n,m,t,a[N][N],x,y,p,d[N][N],dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
bool vis[N][N],flag;
queue<pair<int,int> >q;
signed main(){
    scanf("%lld%lld%lld",&n,&m,&t);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%1lld",&a[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<4;k++){
                int xx=i+dx[k],yy=j+dy[k];
                if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&a[xx][yy]==a[i][j]) vis[i][j]=1;
            }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(vis[i][j]) q.push(make_pair(i,j));
    if(!q.size()) flag=1;
    while(q.size()){
        int xx=q.front().first,yy=q.front().second; q.pop();
        for(int k=0;k<4;k++){
            int nx=xx+dx[k],ny=yy+dy[k];
            if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&!vis[nx][ny]) vis[nx][ny]=1,d[nx][ny]=d[xx][yy]+1,q.push(make_pair(nx,ny));
        }
    }
    while(t--){
        scanf("%lld%lld%lld",&x,&y,&p);
        if(flag||p<d[x][y]) printf("%lld\n",a[x][y]);
        else printf("%lld\n",(a[x][y]+p-d[x][y])%2);
    }
    return 0;
}

7. CF209C Trails and Glades

題目大意:給一張圖,添加最少的邊使得整張圖存在一條歐拉回路。1≤n106,0≤m≤106

Solution:

連通圖中存在歐拉回路的充要條件是該圖所有頂點的度數都為偶數。一個顯然的想法就是:找出所有度數為奇數的點,然后將這些點兩兩相連,得到的圖的度數就全部是偶數了。

但是這樣並不對,因為存在歐拉回路需要先保證原圖是一個連通圖。所以,我們在連邊的過程中還需要保證新圖連通。

考慮每一個連通塊。

如果只有一個連通塊,那就直接把度數為奇數的點兩兩相連。答案為奇數點個數除以 2。

否則如果有 k 個連通塊,我們需要先把所有連通塊連接。

  • 若某個連通塊不存在奇數點(即所有節點的度數均為偶數),因為如果從這個連通塊向別的連通塊只連一條邊就會出現奇數點,所以應該從這個連通塊向別的連通塊連兩條邊。
  • 若某個連通塊存在奇數點,則保留兩個奇數點,從這兩個奇數點向外連兩條邊。並不影響答案。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,x,y,fx,fy,cnt,f[N],d[N],c[N];
int find(int x){
    return f[x]==x?x:f[x]=find(f[x]);
}
signed main(){
    scanf("%lld%lld",&n,&m),d[1]=2;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%lld%lld",&x,&y);
        d[x]++,d[y]++;
        x=find(x),y=find(y),f[x]=y;
    }
    for(int i=1;i<=n;i++)
        if(d[i]&1) cnt++,c[find(i)]=1;
    for(int i=1;i<=n;i++)
        if(f[i]==i&&d[i]&&!c[i]) cnt+=2;
    if(!c[f[1]]&&cnt==2) cnt=0;
    printf("%lld\n",cnt/2);
    return 0;
}

8. CF1311E Construct the Binary Tree

題目大意:給定 n 和 d,你需要構造一棵 n 個點的二叉樹,滿足所有點的深度之和恰好為 d。2≤n,d≤5000。 

Solution:

首先考慮深度最小的情況,也就是完全二叉樹。如果 d 小於這個數直接不可能。

否則,我們考慮對這棵樹進行修改,每次讓總深度 +1。

首先,隨便拎出來一條最深的鏈,為了方便我們直接把 1 到 n 拎出來。

然后我們開始倒序考慮不在這條鏈上的所有點,每次嘗試把這個點的深度 +1。

只需要讓這個點的父節點變為鏈上深度等於它的節點即可。

每次我們剛好把總深度 +1,所以一定能夠得到所有可行的結果。如果最后還沒達到 d 那就不可能。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e3+5;
int t,n,d,fa[N],dep[N],a[N],mx,ans;
bool vis[N];
signed main(){
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld",&n,&d);
        memset(vis,0,sizeof(vis)),mx=0,a[0]=1;
        for(int i=2;i<=n;i++)
            fa[i]=i/2,dep[i]=dep[fa[i]]+1,d-=dep[i],mx=max(mx,dep[i]);    //求出每個點的父親和深度。mx:最大深度。 
        if(d<0){puts("NO");continue;}     //考慮深度最小的情況,也就是完全二叉樹。如果 d 小於這個數直接不可能。 
        int x=n; 
        while(x) a[dep[x]]=x,vis[x]=1,x=fa[x];    //找出一條最深的鏈,標記在這條鏈上的點。 
        for(int i=n;i>=1;i--){    //倒序考慮不在這條鏈上的所有點。 
            if(vis[i]) continue;
            int pre=mx;
            while(dep[fa[i]]<pre&&d){
                fa[i]=a[dep[fa[i]]+1],dep[i]=dep[fa[i]]+1;    //每次嘗試把這個點的深度 +1。
                if(dep[i]>mx) mx++,a[mx]=i,vis[i]=1;     //dep[i]>mx 相當於節點 i 接到了最深的鏈的后面。修改相關信息。 
                d--;
            }
        }
        if(d){puts("NO");continue;} 
        puts("YES");
        for(int i=2;i<=n;i++)
            printf("%lld%c",fa[i],i==n?'\n':' ');
    }
    return 0;
}

9. NOIP 2013 華容道

題目大意:有一個 n × m 的棋盤,其中有若干個位置是障礙,還有一個空白位置,其它位置都是棋子。有 q 組詢問,每次告訴你一個指定的棋子 (SX,SY),空白位置 (EX,EY) 和目標位置 (TX,TY),你需要求出將指定的棋子移動到目標位置最少需要移動幾次。1≤n,m≤30,1≤q≤500。

Solution:

首先我們有一個暴力的想法。設 f(i,j,k,l) 表示指定棋子位於 (i,j),空格位於 (k,l) 的情況下,到達目標點的最短路徑。

建圖直接看下一步往哪走。時間復雜度 O(n2m2q),難以通過本題。

注意到,空格無論怎樣,一定要移動到指定棋子周圍才能起作用。

所以直接設 f(i,j,k) 表示指定棋子位於 (i,j),空格位於指定棋子的上下左右哪個方向。

首先需要預處理狀態之間的轉移。跑一遍 bfs 即可。

總時間復雜度 O(n2m2+nmq log(nm))。

10. CF875F Royal Questions

題目大意:有 n 個王子 m 個公主,每一個公主喜歡其中兩個王子,還有一個美麗度。你需要將王子和公主配對,使得每一個公主都和自己喜歡的王子配對,並且配對的公主美麗度之和最大。n,m≤2×105

Solution:

每一個公主可以抽象為一條邊。

一個王子和一個公主配對,我們會發現答案形成了若干個基環樹。(每一個連通塊點數等於邊數,剛好可以配對)

實際上就是要求整張圖的最大生成環套樹。

先將所有邊排序。接下來對於每一條邊 (u,v):

  • 若 u,v 不連通且分別位於兩棵樹內:加入這條邊,得到一棵新的樹。
  • 若 u,v 不連通且分別位於一棵樹和一棵基環樹內:加入這條邊,得到一個基環樹。
  • 若 u,v 不連通且分別位於兩棵基環樹內:無法加入。
  • 若 u,v 連通且位於一棵樹內:加入這條邊,樹變成基環樹。
  • 若 u,v 連通且位於一棵基環樹內:無法加入。

只需要在並查集的基礎上記錄一個 flag 數組表示是否是基環樹即可。

時間復雜度 O(m log m)。

11. BZOJ 2238 Mst

題目大意:一張圖,q 次詢問刪掉某條邊之后最小生成樹的大小。詢問相互獨立。n≤50000,m,q≤105。 

Solution:

首先隨便找一棵最小生成樹。

如果刪掉的邊不在生成樹上那么無影響。

刪掉樹邊等價於求跨過這條邊的非樹邊的最小權值。

如果用樹剖的話,時間復雜度是兩個 log 的。

我們可以把其它所有的邊按照邊權從小到大排序。

接下來相當於每次要把未被標記的邊打上標記。

用並查集維護某個點上方第一個未被標記的點,這樣一來每一條邊只會被訪問一次。

時間復雜度 O(mα(n))。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM