Brush (IV) LightOJ - 1018

题意:平面上有一些点,每刷一次可以把同一条直线上的点都刷光,问最少几次把所有点刷光。

方法:

显然是一个状态压缩dp。ans[S]表示把S集合中点刷掉的最少次数。最开始想到的方法是如果S中只有一个或两个点,那么ans[S]=1。否则枚举S中任意两点i,j作为直线上的点,并算出经过i,j的直线还过S中其他多少个点,那么ans[S]=min(ans[S],ans[S去掉那条直线经过的所有点]+1)。自然而然的就想到应该预处理出过i,j两点的直线过的其他点的集合,只需要枚举i,j和另外一个点再判是否共线即可。

这里需要用到判三点共线:https://www.zybang.com/question/ca7778a2e315afb588629121177b6772.html

A(x1,y1),B(x2,y2),C(x3,y3),则(x2-x1)×(y3-y2)=(x3-x2)×(y2-y1)

但是,这样时间复杂度是$O(T*n^3*2^n)$,还是太慢了。

观察一下可以发现:由于一个集合中所有点早晚都要刷掉,只需要指定任意一个点作为直线上的点,再枚举另一个点就行了。

那么,复杂度降低到$O(T*n^2*2^n)$。对于单组数据复杂度已经可以接受,但是由于T比较大,还是不能通过。

接下来开始卡常:

1.把循环的dp改成记忆化搜索能快许多,因为最终状态不一定需要其他所有状态的答案。

2.在开始时预处理出每个集合S的所有元素,而不是每次枚举编号判断是否在S内

3.常规(meiyong):快读,预处理左移

错误记录:

1.复杂度错误,$O(T*n^3*2^n)$

2.被卡常,用http://blog.csdn.net/kijamesqi/article/details/50533691的方法卡过去

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 int x[30],y[30];
 6 int T,TT,n,fff;
 7 int ans[140000];
 8 int tmp[30][30];
 9 int left[30];
10 int G[70000][30];
11 void read(int&x)
12 {
13     x=0;
14     char ch=getchar();fff=1;
15     while(ch<'0'||ch>'9')    
16     {
17         if(ch=='-')    fff=-1;
18         ch=getchar();
19     }
20     while(ch>='0'&&ch<='9')    x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
21     x*=fff;
22 }
23 int main()
24 {
25     int i,j,k,t1,t2;
26     for(i=0;i<30;i++)    left[i]=(1<<i);
27     for(i=1;i<70000;i++)
28         for(k=0;k<17;k++)
29             if(i&left[k])
30                 G[i][++G[i][0]]=k;
31     read(T);
32     for(TT=1;TT<=T;TT++)
33     {
34         read(n);
35         for(i=0;i<n;i++)
36             read(x[i]),read(y[i]);
37         //memset(tmp,0,sizeof(tmp));
38         for(i=0;i<n;i++)
39             for(j=i+1;j<n;j++)
40             {
41                 tmp[i][j]=0;
42                 t1=x[j]-x[i];
43                 t2=y[j]-y[i];
44                 for(k=0;k<n;k++)
45                     if(t1*(y[k]-y[j])==(x[k]-x[j])*t2)
46                         tmp[i][j]|=left[k];
47                 tmp[j][i]=tmp[i][j];
48             }
49         //memset(ans,0x3f,sizeof(ans));
50         ans[0]=0;
51         for(i=1;i<left[n];i++)
52         {
53             if(__builtin_popcount(i)<=2)
54                 ans[i]=1;
55             else
56             {
57                 ans[i]=0x3f3f3f3f;
58                 //每个点都迟早要被刷,因此枚举任意一个点作为直线上点即可,不用枚举两个
59                 j=G[i][1];
60                 for(k=2;k<=G[i][0];k++)
61                     ans[i]=min(ans[i],ans[i^(tmp[j][G[i][k]]&i)]+1);
62             }
63         }
64         printf("Case %d: %d\n",TT,ans[left[n]-1]);
65     }
66     return 0;
67 }
posted @ 2017-10-28 12:30  hehe_54321  阅读(255)  评论(0编辑  收藏  举报
AmazingCounters.com