2021“MINIEYE杯”中國大學生算法設計超級聯賽(10)1003. / HDU7079 Pty loves lines(bitset優化DP)


Problem Description

You are required to put n straight lines on a plane, guaranteeing no three lines share a common point and no lines are coincident. They will form some intersections, please output all possible numbers of intersections.

Input

The first line, an integer T(1 ≤ T ≤ 5) - the number of test cases.

Following T lines, an positive integer n(1 ≤ n ≤ 700) - the number of lines.

Output

Several lines, each line several numbers separated by a blank space - all possible numbers of intersections.

Sample Input

2
3
5

Sample Output

0 2 3
0 4 6 7 8 9 10

這個題題意非常短,問的是給定n條直線,問在沒有三線共點等情況下能產生多少種不同的交點數。

這個題可以看做HDU1466的加強版。原題給的數據范圍是n <= 20,那么就可以用普通的dp求解。如果手玩幾個樣例,例如n = 5,會發現一開始如果五條線平行的話交點為0,四條線平行一條線與這四條平行線相交的話交點為4,三條線平行,另外兩條線互相平行或相交的話交點數為6或者7...可以看出來平行線的存在能夠將問題規模縮小。因為如果x條線相互平行,另外y條線任意(但不與這x條線平行)的話,總的交點數就是y條線自己產生的交點數 + x * y,原因就是y條線中的每條線都與x條平行線產生x個交點。

這樣就可以進行dp了,設\(dp[i, j]\)為用i條直線產生j個交點是否可行,k為i條線中相互平行的線的數量,則轉移方程為\(dp[i, j] \ |= \ dp[i - k, j - (i - k) \times k]\)。其中i - k就是任意的線的數量,(i - k) * k就是任意的線與平行線產生的交點數。這樣就可以輕松通過HDU1466這道題了。

#include <bits/stdc++.h>
using namespace std;
const int N = 25;
const int NN = N * N / 2;//交點數最多有這么多
bool dp[N + 5][NN + 5];//dp[i][j]表示用i條直線湊出來j個點是否可行
void init() {
    for(int i = 0; i < N; i++) dp[i][0] = 1;
    for(int i = 1; i <= N; i++) {
        for(int j = 1; j <= NN; j++) {
            for(int k = 0; k < i; k++) {//k條直線平行
                if(j - (i - k) * k >= 0) dp[i][j] = dp[i][j] | dp[i - k][j - (i - k) * k];
            }
        }
    }
}
void solve(int n) {
    vector<int> ans;
    for(int i = 0; i < NN; i++) {
        if(dp[n][i]) ans.push_back(i);
    }
    for(int i = 0; i < ans.size(); i++) {
        if(i != ans.size() - 1) cout << ans[i] << " ";
        else cout << ans[i] << endl;//注意行末空格
    }
}
int main () {
    cin.tie(0);
    ios::sync_with_stdio(false);
    init();
    int n;
    while(cin >> n) {
        solve(n);
    }
}

然而對於比賽的這道題,n的范圍陡然增加到了700,上述代碼的時間復雜度為\(O(n^4)\),顯然時間上無法通過,而且有爆內存的風險。那么就要考慮進行優化,一種可行的方案是使用bitset進行優化。觀察上面代碼,第二重循環實際上直接貢獻了\(n^2\)的復雜度,那么借助bitset,我們可以把它優化成\(O(\frac{n^2}{w})\)。具體來說,對於每一個i維護一個bitset,bitset的大小為\(\frac{n\times (n - 1)}{2}\),在循環里直接省略掉原先的第二重循環,同時把轉移方程的寫法改為dp[i] = dp[i] | dp[i - k] << ((i - k) * k);這里相當於整體進行或操作而不是單獨進行遍歷,借助bitset的玄學操作可以帶來不小的提升。因為左移會在低位補0,因此也不用像上面代碼一樣擔心越界的問題。

#include <bits/stdc++.h>
using namespace std;
const int N = 700;
const int NN = N * N / 2;
bitset<NN + 5> dp[N + 5];
void init() {
    for(int i = 0; i < N; i++) dp[i][0] = 1;
    for(int i = 1; i <= N; i++) {
        for(int k = 0; k < i; k++) {
            dp[i] = dp[i] | dp[i - k] << ((i - k) * k);
        }
    }
}
void solve(int n) {
    vector<int> ans;
    for(int i = 0; i < NN; i++) {
        if(dp[n][i]) ans.push_back(i);
    }
    for(int i = 0; i < ans.size(); i++) {
        if(i != ans.size() - 1) cout << ans[i] << " ";
        else cout << ans[i] << endl;
    }
}
int main () {
    cin.tie(0);
    ios::sync_with_stdio(false);
    init();
    int n;
    while(cin >> n) {
        solve(n);
    }
}

然而本地跑一下會發現init()函數執行的時間還是非常長(需要好幾秒才能進行查詢),瓶頸在於bitset開的實際上非常大。這時候就需要繼續進行優化。比賽的時候發現當輸出了若干個數以后,后面的數都是連續的,通過對拍程序測試一下n = 20的時候100以后基本都是連續的,猜想可能是大約5倍以上后面就都是連續的了(然而是錯的),題解給的界限是31500,這樣的話bitset實際上只需要開到這個上界就足矣,i大於上屆的話直接輸出即可,這樣就能通過本題了。

#include <bits/stdc++.h>
using namespace std;
const int N = 700;
const int NN = N * N / 2;
#define MX 35000
bitset<MX> dp[N + 5];//dp[i][j]表示用i條直線湊出來j個點是否可行
void init() {
    for(int i = 0; i <= N; i++) dp[i][0] = 1;
    for(int i = 1; i <= N; i++) {
        for(int k = 0; k <= i; k++) {
            dp[i] = dp[i] | (dp[i - k] << ((i - k) * k));
        }
    }
}
void solve(int n) {
    vector<int> ans;
    int lim = min(MX - 1, n * (n - 1) / 2);
    for(int i = 0; i <= lim; i++) {
        if(dp[n][i]) ans.push_back(i);
    }
    for(int i = lim + 1; i <= n * (n - 1) / 2; i++) ans.push_back(i);
    for(int i = 0; i < ans.size(); i++) {
        if(i != ans.size() - 1) cout << ans[i] << " ";
        else cout << ans[i] << endl;
    }
}
int main () {
    cin.tie(0);
    ios::sync_with_stdio(false);
    init();
    int t;
    cin >> t;
    while(t--) {
        int n;
        cin >> n;
        solve(n);
    }
}


免責聲明!

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



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