uva 11008 Antimatter Ray Clearcutting

原题:
It’s year 2465, and you are the Chief Engineer for Glorified Lumberjacks Inc. on planet Trie. There is a number of trees that you need to cut down, and the only weapon you have is a high-powered antimatter ray that will cut through trees like butter. Fuel cells for the antimatter ray are very expensive, so your strategy is: stand somewhere in the forest and shoot the ray in some chosen direction. This will cut down all the trees that lie on the line in that direction. Given the locations of several trees and the
number of trees that you are required to cut, what is the minimum number of shots that you need to fire?
Input
The first line of input gives the number of cases, N (at most 20). N test cases follow. Each one starts with 2 lines containing the integers n (the number of trees in the forest, at most 16) and m (the number of trees you need to cut, at most n). The next n lines will each give the (x,y) coordinates of a tree (integers in the range [−1000, 1000]).
Output
For each test case, output the line ‘Case #x:’, where x is the number of the test case. On the next line, print the number of antimatter ray shots required to cut down at least m trees. Print an empty line between test cases.
Notes:
In the first test case, you can cut down 4 trees by standing at (0, -1) and firing north (cutting 2 trees) and then standing at (1, -1) and again firing north (cutting 2 more trees).
In the second test case, you should stand at (3,-1) and fire north (cutting 4 trees) and then stand at (-1, -1) and fire north-east (cutting 3 more trees).
Sample Input
2
4 4
0 0
0 1
1 0
1 1
9 7
0 0
1 1
0 2
2 0
2 2
3 0
3 1
3 2
3 4

Sample Output
Case #1:
2

Case #2:
2

中文:
给你一堆树,不超过16棵。然后给你一把打直线的激光枪,在激光枪弹道上面的树全能被消灭。给你n和m其中n为树的总数,m为需要你消灭的树的数量。现在问你最少打多少枪能消灭m颗树。

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

const int maxn = (1 << 20);
const double eps = 1e-5;
const int inf = 99999;
int n, m;
int dp[maxn];
struct Point
{
    int x, y;
    Point(int x = 0, int y = 0) :x(x), y(y){}
    bool operator == (const  Point &p)
    {
        return this->x==p.x&&this->y==p.y;
    }
};
typedef Point Vector;

Vector operator +(Vector A, Vector B){ return Vector(A.x + B.x, A.y + B.y); }//坐标点相加
Vector operator -(Vector A, Vector B){ return Vector(A.x - B.x, A.y - B.y); }//相减


int dcmp(double x)
{
    if (fabs(x)<eps)
        return 0;
    else
        return x<0 ? -1 : 1;
}
double Cross(Vector A, Vector B)
{
    return A.x*B.y - A.y*B.x;
}
bool OnLine(Point p, Point a1, Point a2)//p是否在a1和a2形成的直线上
{
    if(a1==a2)//考虑到是否用重复点的情况
    {
        if(p==a1)
            return true;
        return false;
    }
    return Cross(a1 - p, a2 - p) == 0;
}
Point ps[17];

int Count(int S)
{
    int cnt = 0;
    for (int i = 0; i <= n; i++)
    {
        int tmp = (1 << i);
        if (S&tmp)
            cnt++;
    }
    return cnt;
}
int main()
{
    ios::sync_with_stdio(false);
//    ifstream in("in.txt");
//    ofstream out("output.txt");
    int t, ind = 1;
    cin >> t;
    while(t--)
    {
        cin >> n >> m;
        memset(dp,0,sizeof(dp));
        for (int i = 0; i <= (1 << n); i++)
            dp[i] = inf;
        dp[0] = 0;
        for (int i = 0; i <= n; i++)
            dp[(1 << i)] = 1;
        for (int i = 1; i <= n; i++)
            cin >> ps[i].x >> ps[i].y;
        for (int i = 1; i <= n; i++)
        {
            for (int j = i + 1; j <= n; j++)
            {//枚举任意两点
                int S = 0;
                S |= (1<<(i-1));
                S |= (1<<(j-1));//将枚举的两点加入到集合当中
                dp[S] = 1;//此时需要一条直线
                for (int k = 1; k <= n; k++)
                {
                    if (k != i && k != j && OnLine(ps[k], ps[i], ps[j]) )//每次判断余下点是否在直线上
                    {
                        S |= (1<<(k-1));//在直线上就加入集合
                    }
                }
                for (int s = 0; s<(1 << n); s++)
                {
                    if ((s&S)==S)//S集合中的元素全在s当中
                    {
                        dp[s] = min(dp[s], dp[s&~S] + 1);//状态转移
                    }
                }
            }
        }
        int ans = INT_MAX;
        for (int i = 0; i<(1 << n); i++)
        {
            if (Count(i) >= m)//检查集合中树的数量为i的状态,一定要大于等于才能过
            {
                ans = min(ans, dp[i]);
            }
        }
        cout<<"Case #"<<ind++<<":"<<endl;
        cout << ans <<endl;
        if(t)
            cout<<endl;
    }
//  in.close();
//  out.close();
    return 0;
}

解答:

很容易看出是动态规划问题。
首先考虑如何保存状态。
考虑两种方法,一种是枚举所有两点组成的直线,另外一种是枚举所有的点。

第一种方法需要n×(n-1)/2条直线,第二种需要保存16个点。

考虑状态转移,第一种方法,当前状态一定是之前已经选定了几条直线,然后考虑下一条直线的最优解,那么需要将以前的直线方程全部保存下来,考虑到数据量问题,不可能实现。

第二种方法,枚举任意两点形成的直线,上一次的状态保存的是已经被消灭的点,如今加入新枚举的两点所形成的直线,最多能消灭多少点。

设置dp[s]为点的集合状态为s时,需要最少的直线数。
line(x,y)为x和y连接的直线上面有多少点。
状态转移方程为

dp[S]=min(dp[S],dp[S-{line(x,y)}]+1)

最后枚举结果的方式为
枚举所有状态,判断该状态中消灭的点数是否大于等于m,如果满足,则更新结果。找到最小值即可。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值