2016.7.15.2014noip模拟赛D1(和昨天那个不一样,但同样网上搜不到

1.      合理种植

(plant.pas/.c/.cpp)

【问题描述】

   大COS在氯铯石料场干了半年,受尽了劳苦,终于决定辞职。他来到表弟小cos的寒树中学,找到方克顺校长,希望寻个活干。

      于是他如愿以偿接到了一个任务……

美丽寒树中学种有许多寒树。方克顺希望校园无论从什么角度看都是满眼寒树,因此他不希望有三棵甚至更多寒树种在一条直线上。现在他把校园里n棵寒树的坐标都给了大COS,让他数出存在多少多树共线情况。(若一条直线上有三棵或以上的树,则算出现一个多树共线情况。)

【输入】

      输入文件名为plant.in。

      第1行一个正整数n,表示寒树棵数。

      接下来n行,每行两个非负整数x、y,表示一颗寒树的坐标。没有两颗寒树在同一位置。

【输出】

      输出文件名为plant.out。

      输出一个整数,表示存在多少多树共线情况。

 

【输入输出样例】

plant.in

plant.out

6

0 0

1 1

2 2

3 3

0 1

1 0

1

 

【数据范围】

      对于30%的数据,有n≤10;

      对于50%的数据,有n≤100;

      对于100%的数据,有n≤1,000,0≤x,y≤10,000。

解析:

槐花槐花,我会说我第一遍读题居然直接没有管“无论什么角度”这几个字吗?宛如一个智障,结果只算了0,45,90,135度的情况,得了十分......咳咳,然后,下午想了一会,用了另一个o(N3)的方法,险过,再加一句绝对TLE,因为有人借用我的方法改了一些地方就超时了。

我的方法:o(N3

先用N2,把i点与j点所在直线的斜率计算出来,存入long long  f[i][j]中,为什么不用double?原因很简单,我不太清楚double存数据的方式,后来在网上查了一下,发现double也不行,因为double存的是23位,如果10000除以两个小质数,其斜率可能存储相等,因为10000/9999和9999/9998所得数是在小数点后第8位才有区别的。所以我用的longlong,当然我还*108,解决。然后再来一个N3的循环,枚举三个点,再用一个bool a[i][j]表示i和j所在直线三点共线的情况算了没有。此处,如果你在后面的循环内再计算斜率->TLE。当然你也可以不采用k=y/x的公式,用高中学的(x1-x3)(y2-y3)=(x2-x3)(y1-y3)这个式子也可以,但是同样的不可以放在后面的循环中,放了,最多7个点,剩下500以上就TLE。

程序:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int n,x,y,ans;
 6 struct stu 
 7 {
 8     int x,y;
 9 }tree[1005];
10 long long X,Y,k;
11 long long dis[1005][1005];
12 bool a[1005][1005];
13 int main()
14 {
15     freopen("plant.in","r",stdin);
16     freopen("plant.out","w",stdout);
17     scanf("%d",&n);
18     for(int i=1;i<=n;i++)
19     {
20         scanf("%d %d",&tree[i].x,&tree[i].y);
21     }
22     for(int i=1;i<=n;i++)
23        for(int j=i+1;j<=n;j++)
24         {
25             Y=tree[i].y-tree[j].y;
26             X=tree[i].x-tree[j].x;
27             if(X!=0) k=(Y*100000000LL)/X;
28             else k=1;
29             dis[i][j]=k;
30         }
31     for(int i=1;i<=n;i++)
32        for(int j=i+1;j<=n;j++)
33     {
34         
35           for(int l=j+1;l<=n;l++)
36           if(dis[i][j]==dis[j][l])
37           {
38               if(a[i][j]||a[j][l]||a[i][l]) 
39               {
40                   a[i][j]=a[j][l]=a[i][l]=true;
41               }
42               else 
43             {
44                   ans++;
45                   a[i][j]=a[j][l]=a[i][l]=true;
46               }
47           }
48     }
49     cout<<ans<<endl;
50     return 0;
51 }

 

正解:o(N2logN)

半小时内真心看不懂,看的朕心力交瘁,肾虚不治。最后还是老师点破天机。大概思路是先算每两个点的横坐标差、纵坐标差并保存,然后存个方向(bool),就是斜率为正还是为负。就这个方向,解决掉了多点共线重复计算的问题。而就是这个方向,我看代码一直没明白,唉,作为一个学水。建议理解为向量,这样向量的方向是x、y、左右有关的。这样理解要简单一点点。以及最后要用斜率排序,让斜率相同的靠在一起,这样才方便连着算。

然后再解释一下方向判重问题,如图所示:

这个样子到了第二个点的时候1-2与2-3的方向不同,就不会再计算后面的了。

 1 #include <fstream>
 2 #include <cstdlib>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 ifstream fin("plant.in");
 7 ofstream fout("plant.out");
 8 long n;
 9 struct xxx{long x,y;} a[1005];
10 struct kk{long dx,dy;bool c;} k[1005];
11 bool operator <(kk k1,kk k2){
12     if (k1.dx==10001) return k2.dx<10001;
13     if (k2.dx==10001) return 0;
14     return k2.dx*k1.dy<k1.dx*k2.dy;
15 }
16 bool operator ==(kk k1,kk k2){
17     if (k1.dx==10001||k2.dx==10001)
18         return k1.dx==k2.dx;
19     return k2.dx*k1.dy==k1.dx*k2.dy;
20 }
21 void init(){
22     long i;
23     fin>>n;
24     for (i=0;i<n;i++)
25         fin>>a[i].x>>a[i].y;
26 }
27 void tr(){
28     long i,j,s,ans=0;
29     bool f;
30     for (i=0;i<n;i++)
31     {
32         for (j=0;j<n;j++)
33             if (i!=j)
34                 if (a[i].x==a[j].x)
35                 {
36                     k[j].dx=10001;
37                     k[j].c=a[i].y<a[j].y;
38                 }
39                 else
40                 {
41                     k[j].dx=a[j].x-a[i].x;
42                     k[j].dy=a[j].y-a[i].y;
43                     if (k[j].dx<0)
44                     {
45                         k[j].dx=-k[j].dx;
46                         k[j].dy=-k[j].dy;
47                     }
48                     k[j].c=a[i].x<a[j].x;//判断j在i左还是右 
49                 }
50         for (j=i;j<n-1;j++) k[j]=k[j+1]; 
51         sort(k,k+n-1);//以斜率大小为基准排,这样斜率相同就方便计算 
52         f=1; s=0;
53         for (j=0;j<n-2;j++)
54         {
55             s++;
56             f=f&&k[j].c;
57             if (!(k[j]==k[j+1]))
58             {
59                 if (f&&s>1) ans++;//若方向相同且有三个点以上
60                 f=1; s=0;
61             }
62         }
63         if (f&&k[n-2].c&&s>0) ans++;
64     }
65     fout<<ans;
66 }
67 int main(){
68     init();
69     tr();
70     fin.close();
71     fout.close();
72     return 0;
73 }

2.      排队

(lineup.pas/.c/.cpp)

【问题描述】

      小sin所在的班有n名同学,正准备排成一列纵队,但他们不想按身高从矮到高排,那样太单调,太没个性。他们希望恰好有k对同学是高的在前,矮的在后,其余都是矮的在前,高的在后。如当n=5,k=3时,假设5人从矮到高分别标为1、2、3、4、5,则(1,5,2,3,4)、(2,3,1,5,4)、(3,1,4,2,5)都是可行的排法。小sin想知道总共有多少种可行排法。

【输入】

      输入文件名为lineup.in。

      一行两个整数n和k,意义见问题描述。

【输出】

      输出文件名为lineup.out。

      输出一个整数,表示可行排法数。由于结果可能很大,请输出排法数mod 1799999的值。

 

【输入输出样例】

lineup.in

lineup.out

5 3

15

 

【数据范围】

      对于20%的数据,有n≤10,k≤40;

      对于60%的数据,有n≤100,k≤500;

      对于100%的数据,有n≤100,k≤n*(n-1)/2。

正解:找规律,简单,上代码。(当然,你没找到规律就傻眼儿吧)

#include<iostream>
#include<cstdio>
using namespace std;
const int p=1799999;
int n,k,a[105][5000];
int main()
{
    freopen("lineup.in","r",stdin);
    freopen("lineup.out","w",stdout);
    scanf("%d %d",&n,&k);
    for(int i=2;i<=n;i++)
    {a[i][1]=i-1; a[i][0]=1; a[i][i*(i-1)/2]=1;}
    for(int i=3;i<=n;i++)
       for(int j=2;j<(i*(i-1)/2);j++)
       {
           for(int z=j;z>=j-i+1&&z>=0;z--)
       {
           a[i][j]=(a[i-1][z]+a[i][j])%p;
       }
       }
    
    cout<<a[n][k]<<endl;
    return 0;
}

1.      科技节

(scifest.pas/.c/.cpp)

【问题描述】

      一年一度的科技节即将到来。同学们报名各项活动的名单交到了方克顺校长那,结果校长一看皱了眉头:这帮学生热情竟然如此高涨,每个人都报那么多活动,还要不要认真学习了?!这样不行!……于是,校长要求减少一些活动,使每位学生只能参加一项(一名同学要参加某活动,必须已报名且该活动未被去掉)。当然,他也不希望哪位同学因此不能参加任何活动。他想知道自己的方案能否实行。

【输入】

      输入文件名为scifest.in。

      输入数据包括多组。

      对于每组数据:

      第一行两个正整数n和m,分别表示活动数和学生数。

      接下来n行,每行m个为0或1的数。第i+1行第j列的数若为1,表示j同学报名参加活动i,否则表示j同学没有报名参加活动i。

【输出】

      输出文件名为scifest.out。

对于每组数据输出一行,若校长方案可行则输出“Yes”,否则输出“No”。(均不包括引号)

 

 

【输入输出样例】

scifest.in

scifest.out

3 3

0 1 0

0 0 1

1 0 0

4 4

0 0 0 1

1 0 0 0

1 1 0 1

0 1 0 0

Yes

No

 

【数据范围】

      对于20%的数据,n≤10,m≤200,数据组数≤10;

      对于60%的数据,n≤16,m≤300,数据组数≤100;

      对于100%的数据,n≤16,m≤300,数据组数≤1,000。

 正解:位运算,针对活动来解决。优化是从人多的那个活动开始补。我觉得不如理解为拼图:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int n,m,a[20][10];
 6 int h[20],c,k;
 7 int o;
 8 void can(int act,int x[10],int students)
 9 {
10      if(act==n)
11      {
12          if(students==m) o=1;
13          return ;
14      }
15      int bili,t[10];
16      for(bili=0;bili<10;bili++)
17      {
18          if((x[bili]&a[act][bili])>0) break;
19          t[bili]=x[bili]|a[act][bili];
20      }
21      if(bili==10) can(act+1,t,students+h[act]);
22      if(!o) can(act+1,x,students);//取了没用就不取 
23 }
24 int main()
25 {
26     freopen("scifest.in","r",stdin);
27     freopen("scifest.out","w",stdout);
28     while(~scanf("%d %d",&n,&m))
29     {
30         
31         memset(a,0,sizeof(a));
32         for(int i=0;i<n;i++)
33         {
34             h[i]=k=0;
35             for(int j=0;j<m;j++)
36            {
37                scanf("%d",&c);
38                if(c)
39                {
40                    a[i][k/31]+=1<<(k%31);
41                    h[i]++;
42                }
43                k++;
44            }
45         }
46     for (int i=0;i<n-1;i++)
47         for (int j=i+1;j<n;j++)
48             if (h[i]<h[j])
49             {
50                 k=h[i]; h[i]=h[j]; h[j]=k;
51                 for (int l=0;l<10;l++)
52                 {
53                     k=a[i][l]; a[i][l]=a[j][l]; a[j][l]=k;
54                 }
55             }
56         o=0;
57         can(0,a[19],0);
58         if(o) cout<<"Yes"<<endl;
59         else cout<<"No"<<endl;
60     }
61     return 0;
62 }

 

posted @ 2016-07-15 22:09  sci  阅读(220)  评论(0编辑  收藏  举报