1. 程式人生 > 其它 >AcWing 1165 單詞環

AcWing 1165 單詞環

本題同樣是考察\(01\)分數規劃,只不過難度較上題有所提升。首先需要考慮的是如何建圖,如果常規的用每個字串作為圖中的節點,最多會有\(10w\)個節點,邊數也可能高達\(100\)億級別,顯然難以承受,而且以字串作為節點,還需要挨個比較每個字串的末尾兩個字元與其它字串的開頭兩個字元是否相等,這也將耗費大量的時間。

我們知道,有小寫字母構成的兩個字元最多有\(26 * 26 = 676\)種可能,而這十萬字串中間的內容並不重要,我們只關心每個字串的開始兩個字元末尾兩個字元以及字串的長度。這樣我們讀入一個字串時,將其開頭長度為\(2\)的字串作為一個節點,末尾長度為\(2\)的字串作為另一個節點,兩個節點間有向邊的長度就是該字串的長度。這種巧妙的建圖方式不僅使得節點數驟減,邊數不超過限制\(10w\)

,更方便的是每條邊都象徵著存在一個字串開頭字串是\(a\),末尾字串是\(b\),字串長度是\(c\)。如果\(b\)指向另一個點\(d\)\(abd\)就自然的連線起來了,並且連線後字串的長度就是邊權之和,非常方便。既然節點最多有\(26*26\)種可能性,那麼就可以將長度為\(2\)的字串進行雜湊對映到\(0\)\(675\)的區間中,比如\(aa\)就對映到編號為\(0\)的節點,這樣本題的圖就建好了。

第二步就是按照\(01\)分數規劃的解題思路推公式,環串的平均長度最大,等價於\(∑s_i / ∑1\)最大,其中\(s_i\)表示環中各邊的長度,\(∑1\)就是環上的邊數,要判斷對於某個\(mid\)

是否有\(∑s_i / ∑1 >= mid\),只需要\(∑s_i >= ∑1*mid\),即\(∑(mid - s_i) <= 0\)即可,即邊權為\(mid - s_i\)的圖中存在負權迴路,問題就進一步轉化為了\(spfa\)求負權迴路問題了。

最後本題時間卡得很緊,如果要等待出現一條路徑涉及邊的條數達到\(676\)再確定存在負環,就會超時,這裡就要用到玄學優化了,一般經驗值取節點數的兩倍,這裡邊比較多就取\(100000\),即被鬆弛的總次數達到\(100000\)時就按照經驗判定為存在負環,結束\(spfa\)演算法。也可以將\(spfa\)中的佇列換成棧,在實現上相當於每次都從隊尾取元素,這樣一來,一旦存在環,就會很快的去鬆弛環上的下一個節點,更快的結束演算法。另外,對於不存在負權的情況,只需要判斷下\(mid = 0\)

的時候是否合法即可,\(∑s_i / ∑1 >= mid = 0\),等價於\(∑s >= 0\),也就是存在一個單詞環滿足題目要求即可,無解的情況就是無法構造出環。總的程式碼如下:

#include <bits/stdc++.h>
using namespace std;

// n=26 26*26=676 這裡N設為700
const int N = 700, M = 100010;
int m; //邊的數量
int cnt[N], q[N];
double dist[N];
bool st[N];
//鄰接表
int idx, h[N], e[M], w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool check(double mid) {
    memset(dist, 0x3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);
    memset(st, false, sizeof st);
    queue<int> q;
    for (int i = 0; i < 676; i++) {
        q.push(i);
        st[i] = true;
    }
    //整體入佇列次數,676*10表示所有點都入隊10次了,還沒有找到解
    int count = 0;
    while (q.size()) {
        int u = q.front();
        q.pop();
        st[u] = false;
        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[j] < dist[u] + w[i] - mid) {
                dist[j] = dist[u] + w[i] - mid;
                cnt[j] = cnt[u] + 1;
                if (++count > 10000 || cnt[j] >= 676) return true;
                if (!st[j]) {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;
}
int main() {
    string s;
    while (cin >> m, m) {
        //多組測試資料,清空鄰接表
        memset(h, -1, sizeof h);
        idx = 0;
        //讀入每條邊
        for (int i = 0; i < m; i++) {
            cin >> s;
            //不夠長,沒用
            if (s.size() < 2) continue;
            //模擬節點號
            int a = (s[0] - 'a') * 26 + s[1] - 'a';
            int b = (s[s.size() - 2] - 'a') * 26 + s[s.size() - 1] - 'a';
            //建圖,有向圖
            add(a, b, s.size());
        }
        if (!check(0))
            puts("No solution");
        else {
            double l = 0, r = 1000;
            while (r - l > 1e-4) {
                double mid = (l + r) / 2;
                if (check(mid))
                    l = mid;
                else
                    r = mid;
            }
            printf("%.2lf\n", l);
        }
    }
    return 0;
}