REFORM spoj – VOI 2015 – Kế hoạch cải tổ

Nguồn đề bài: http://vn.spoj.com/problems/REFORM/

1. Đề bài REFORM spoj

Mạng giao thông của thành phố NВ có n nút giao thông và m đoạn đường phố hai chiều nối các nút giao thông. Các nút giao thông được đánh số từ 1 đến n. Các đoạn đường phố được đánh số từ 1 đến m. Mạng giao thông của thành phố có tính chất sau đây:

  • Giữa hai nút giao thông bất kỳ có không quá một đoạn đường phố nối chúng;
  • Không có đoạn đường phố nào nối một nút giao thông với chính nó.

Vấn đề giao thông là thách thức với chính quyền thành phố từ nhiều năm. Với mong muốn đảm bảo việc đi lại thuận lợi hơn cho người dân, chính quyền thành phố quyết định tiến hành cải tổ mạng giao thông, trước hết nhằm đảm bảo có thể đi từ một nút giao thông bất kỳ đến tất cả các nút còn lại. (Lưu ý là mạng giao thông trước khi cải tổ có thể không đảm bảo yêu cầu này.) Tuy nhiên, do hạn hẹp về nguồn kinh phí, trước mắt kế hoạch cải tổ chỉ có thể bao gồm 2 công việc:

  • Loại bỏ một đoạn đường phố hiện có khỏi mạng giao thông;
  • Xây dựng một đoạn đường chưa từng có trước đó nối hai nút giao thông khác nhau.

Đồng thời, sau khi thực hiện cải tổ, mạng giao thông phải đảm bảo có thể đi từ một nút giao thông bất kỳ đến tất cả các nút còn lại.

Yêu cầu

Giúp chính quyền thành phố xác định xem có bao nhiêu cách khác nhau để thực hiện kế hoạch cải tổ thỏa mãn các yêu cầu đặt ra. (Hai kế hoạch cải tổ là khác nhau nếu có ít nhất một trong hai đoạn đường được lựa chọn loại bỏ hay xây dựng mới là khác nhau.)

Dữ liệu vào

  • Dòng đầu tiên chứa hai số nguyên n m;
  • Dòng thứ i trong số m dòng tiếp theo chứa hai số nguyên ui vi (1 ≤ ui, vin, uivi) là chỉ số của hai nút giao thông được nối bởi đoạn đường i (i = 1, 2, …, m).

Hai số liên tiếp trên cùng dòng được ghi cách nhau bởi dấu cách.

Dữ liệu ra

  • Ghi ra một số nguyên là số cách thực hiện kế hoạch cải tổ mạng giao thông thỏa mãn các yêu cầu đặt ra.

Ví dụ

Test 1:

input
4 4
1 2
2 3
2 4 
3 4 
output
8

Hình vẽ minh họa:

Test 2:

inout
7 5
1 2
3 4
5 6
5 7
6 7
output
0

Hình vẽ minh họa:

Ràng buộc

30% số test có 1≤n≤20.

70% số test còn lại có 1≤n≤100000, 0≤m≤200000.

2. Lời giải REFORM spoj

-Bài toán được phát biểu trên mô hình đồ thị:

Cho đồ thị vô hướng n đỉnh và m cạnh. Đếm số cách khác nhau đề thực hiên 2 công việc:

  • Loại bỏ 1 cạnh trong m cạnh của đồ thị.
  • Thêm cạnh mới vào đồ thị (cạnh này phải khác m cạnh lúc đầu) sao cho đồ thị mới đảm bảo tính liên thông.

-Nhận xét: ta chỉ có thể thêm 1 cạnh vào đồ thị nên số thành phần liên thông tối đa chỉ là 2. Ta chia bài toán thành 2 trường hợp:

    1. Đồ thị gồm 2 thành phần liên thông
      -Giả sử 2 thành phần liên thông của đồ thị có số đỉnh lần lượt là C1 và C2 (C1+C2=n). Do đồ thị có 2 thành phần liên thông, ta cần 1 cạnh nối 2 thành phần liên thông này với nhau và số cách nối là C1*C2. Đối với cạnh cần bỏ đi, ta có một nhận xét là cạnh này không được là cầu (bởi nếu bỏ đi cầu, số thành phần liên thông sẽ tăng lên). Do đó, số cách tính được trong trường hợp này là (m-c)*C1*C2 (với c là số cầu của đồ thị).
    2. Đồ thị chỉ có 1 thành phần liên thông
      -Xét cạnh bị bỏ đi, ta có 2 trường hợp:
      a. Cạnh bỏ đi không là cầu: đồ thị vẫn liên thông sau khi bỏ cạnh. Do đó, ta có thể thêm bất kì cạnh nào khác m cạnh lúc đầu. Ta có n*(n-1)/2-m cách thêm cạnh mới.
      b. Cạnh bỏ đi là cầu: đồ thị sẽ bị chia thành 2 thành phần liên thông. Do đó, cạnh thêm vào phải nối 2 thành phần liên thông đó lại. Gọi C1 và C2 lần lượt là số đỉnh của 2 thành phần liên thông đó. Ta có C1*C2-1 cách thêm (không tính cạnh vừa bỏ đi).

-Cuối củng chỉ việc tính tổng số cách trong từng trường hợp.

⇒ Độ phức tạp O(m+n).

3. Code REFORM spoj

a. Code c++

#include <bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=a; i<=b; i++)
#define REP(i,a,b) for(int i=a; i>=b; i--)
#define ll long long
#define pb push_back
#define getbit(x, pos) ((x >> (pos - 1)) & 1)
#define sz(x) (int)x.size()

const int Nmax = 100100;
vector<int> ad[Nmax];
int n, m, c1 = 0, cnt = 0, num[Nmax], low[Nmax], child[Nmax], root,
Count, bridge;
bool Free[Nmax];
// DFS để đếm thành phần liên thông cũng như tìm các cầu.

void visit(int p, int i) {
    if (Count == 1) c1++;
    num[i] = ++cnt;
    low[i] = num[i];
    child[i] = 1;
    Free[i] = 1;
    FOR(j, 0, sz(ad[i]) - 1) if (ad[i][j] != p) {
        if (Free[ad[i][j]] == 0) {
        	visit(i, ad[i][j]);
            child[i] += child[ad[i][j]];
            low[i] = min(low[i], low[ad[i][j]]);
        } else low[i] = min(low[i], num[ad[i][j]]);
    }
    if (root != i && low[i] == num[i]) bridge++;
}
int main() {
    cin >> n >> m;
    FOR(i, 1, m) {
        int x, y;
        cin >> x >> y;
        ad[x].pb(y);
        ad[y].pb(x);
    }
    Count = 0;
    memset(Free, 0, sizeof Free);
    FOR(i, 1, n) if (Free[i] == 0) {
        root = i;
        Count++;
        visit(0, i);
    }
    // trường hợp đồ thị có >= 3 thành phần liên thông, kết quả là 0
    if (Count >= 3) {
        cout << 0;
        return 0;
    }
    ll res;
    if (Count == 2) {
    // trường hợp đầu tiên.
        res = (ll)(m - bridge) * c1 * (n - c1);
    } else {
        res = (ll)(m - bridge) * ((ll)n * (n - 1)/2 - m);
    // cố định cầu bằng cách cố đỉnh các đỉnh từ 2 tới n và cha của các đỉnh tương ứng.
    // low[i] == num[i] nghĩa là i - cha[i] là cầu
        FOR(i, 2, n) if (low[i] == num[i])
            res += (ll)child[i] * (n - child[i]) - 1;
        }
    cout << res;
     return 0;
}

b. code pascal

const   fi='';
        nmax=1000000;
        mmax=400000;

type    data=longint;
var
        f:text;
        adj,x,y:array[0..mmax+1] of data;
        head:array[0..nmax+1] of data;
        sl:array[0..nmax+1] of int64;
        low,num,tr:array[0..nmax+1] of data;
        n,m,dinh,lab,sl1,res,khop,cau:int64;

procedure docfile;
var     i,j:data;
begin
        assign(f,fi); reset(f);
        read(f,n,m);
        for i:=0 to n+1 do
                head[i]:=0;
        for i:=1 to m do
                begin
                        read(f,x[i],y[i]);
                        inc(head[x[i]]);
                        inc(head[y[i]]);
                end;
        for i:=1 to n+1 do
                head[i]:=head[i-1]+head[i];
        for i:=1 to m do
                begin
                        adj[head[x[i]]]:=y[i];
                        dec(head[x[i]]);
                        adj[head[y[i]]]:=x[i];
                        dec(head[y[i]]);
                end;
        close(f);


end;

function min(a,b:data):data;
begin
        if a<b then exit(a); exit(b);
end;

procedure dfs(u:data);
var     v,k,i:data;
        s:boolean;
begin
        inc(lab);
        low[u]:=lab;
        num[u]:=lab;
        k:=0;
        sl[u]:=1;
        s:=false;
        for v:=head[u]+1 to head[u+1] do
                if tr[u]<>adj[v] then
                        begin
                                if num[adj[v]]=0 then
                                        begin
                                                tr[adj[v]]:=u;
                                                inc(k);
                                                dfs(adj[v]);
                                                if low[adj[v]]>=num[u] then
                                                        s:=true;
                                                sl[u]:=sl[adj[v]]+sl[u];
                                                low[u]:=min(low[u],low[adj[v]]);
                                        end
                                else
                                        low[u]:=min(low[u],num[adj[v]]);
                        end;
        if ((s and (tr[u]<>0)) or ((tr[u]=0) and (k>1))) then inc(khop);
        if (Low[u]=num[u]) and (tr[u]<>0) then inc(cau);
end;

procedure ddfs(u:data);
var
        v:data;
begin
        inc(dinh);
        num[u]:=1;
        for v:=head[u]+1 to head[u+1] do
                if num[adj[v]]=0 then
                        ddfs(adj[v]);
end;

procedure xuli;
var     i,j:data;
        stplt:int64;
begin
        for i:=1 to n do
                num[i]:=0;
        stplt:=0;
        sl1:=0;
        for i:=1 to n do
                if num[i]=0 then
                        begin
                                dinh:=0;
                                ddfs(i);
                                if sl1=0 then
                                        sl1:=dinh;
                                inc(stplt);
                        end;
        if stplt>2 then
                begin
                        writeln('0');
                        exit;
                end;
        for i:=1 to n do
                num[i]:=0;
        tr:=num;

        cau:=0;
        khop:=0;
        lab:=0;
        for i:=1 to n do
                if num[i]=0 then
                        dfs(i);
        if stplt=1 then
                begin
                        res:=(m-cau)*((n*(n-1)) div 2 - m);
                        for i:=2 to n do
                                if low[i]=num[i] then
                                        res:=res+int64(sl[i])*(int64(n-sl[i]))-1;
                        writeln(res);
                        exit;
                end;
        if stplt=2 then
                begin
                        res:=(m-cau)*sl1*(n-sl1);
                        writeln(res);
                end;
end;

begin
        docfile;
        xuli;
end.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *