1. 程式人生 > >[LightOJ 1018]Brush (IV)[狀壓DP]

[LightOJ 1018]Brush (IV)[狀壓DP]

邊界 lightoj 計算 const 擁有 for ostream 當前 blank

題目鏈接:http://lightoj.com/volume_showproblem.php?

problem=1018
題意分析:平面上有不超過N個點,如今能夠隨意方向劃直線將它們劃去,問:最少要劃幾次能夠把全部的點劃去?
解題思路:我們能夠使用集合S表示:有哪些點還沒有被劃掉,然後轉移 dp[s] = min(dp[s &(~line[i][j])]) + 1;這裏涉及到line[i][j]的處理,它代表的是在i點和j點構成的直線上一共同擁有幾個點,須要預先處理。

邊界條件就是S中集合元素 >0 && <= 2時,肯定是1。元素空時就是0了。


個人感受:一開始想到的是用s代表當前點沒被劃過的集合。然後卡在了怎麽轉移上,想著選兩個點遍歷著推斷這兩個點所在直線有幾個點。感覺炒雞麻煩。然後就是翻了別人的題解,看到了預處理這字樣。豁然開朗啊~
詳細代碼例如以下:

#include<iostream>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 20;
int x[MAXN], y[MAXN], line[MAXN][MAXN], n, dp[1 << 16];

void init()
{
    memset(line, 0, sizeof line);
    memset(dp, 0x3f, sizeof dp);
    dp[0] = 0;
    for (int i = 0; i < n; ++i)
        for (int j = i + 1; j < n; ++j)
        {
            line[i][j] = (1 << i) | (1 << j);
            int dx = x[j] - x[i], dy = y[j] - y[i];
            for (int k = j + 1; k < n; ++k)
            {
                int dx2 = x[k] - x[i], dy2 = y[k] - y[i]; //這裏我用了向量平行的條件
                if (dx2 * dy == dy2 * dx)
                    line[i][j] |= (1 << k);
            }
            line[j][i] = line[i][j];
        }
}

int dfs(int s)
{
    int& ret = dp[s];
    if (dp[s] < INF) return ret;
    int num = __builtin_popcount(s); //計算s中有幾個1.非常好用的函數
    if (num <= 2) return ret = 1;
    int i = 0;
    while (!(s & (1 << i))) ++i; //找出第一個沒被刪除的點
    for (int j = i + 1; j < n; ++j) //和其他點進行匹配。

這裏匹配次序是不會影響終於結果的 { if (s & (1 << j)) { ret = min(ret, dfs(s&(~line[i][j])) + 1); } } return ret; } int main() { int t; for (int kase = scanf("%d", &t); kase <= t; ++kase) { scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d%d", x+i, y+i); init(); printf("Case %d: %d\n", kase, dfs((1 << n) - 1)); } return 0; }


[LightOJ 1018]Brush (IV)[狀壓DP]