洛谷P1056_排座椅 贪心+桶排

洛谷P1056_排座椅

凡是最优解“最少情况”,一定利用贪心或者dp,本题贪心即可解决。

分别开两个数组x,y,其中x[1]存储通道在第一行时可以隔开的对数,x[2]存储通道在第二行时可以隔开的对数,……y[1]存储通道在第一列时可以隔开的对数,y[2]存储通道在第二列时可以隔开的对数,……

怎么求这个对数呢?没有必要按顺序搞个二重循环依次求出x[i]然后i++每次扫一遍struct数组,既然x,y数组只是计数器,那么跟他们求出的顺序无关。可以直接在输入数据时处理,若xi==pi,则同行不同列,特判yi与qi大小,若yi>qi,y[qi]++;反之y[yi]++;若yi==qi,则同列不同行,特判xi与pi大小,若xi>pi,则x[pi]++;反之x[xi]++;特判大小时可以使用min函数。

最后利用桶排序,得到x,y数组中前k大和前l大的数的下标即为所求

 关于桶排序,在算法竞赛中详述。

实际编程过程中,为了防止洛谷IDE把x[1005]与struct chat中的x混淆,把x,y数组改成a,b数组;

以下是AC代码:

 1 #include<iostream>
 2 using namespace std;
 3 const int D=2005;
 4 int a[1005]={};  //row
 5 int b[1005]={};  //column
 6 int c[1005]={},o[1005]={};
 7 struct chat{
 8     int x,y;
 9     int p,q;
10 }g[D];
11 int main(){
12     int m,n,k,l,d;
13     cin >> m >> n >> k >> l >> d;
14 //行相同列不同b,列相同行不同a 
15     for(int i=1;i<=d;i++){
16         cin >> g[i].x >> g[i].y >> g[i].p >> g[i].q;
17         if(g[i].x==g[i].p){
18             if(g[i].y<g[i].q)
19                 b[g[i].y]++;
20             else  b[g[i].q]++;
21         }
22         else if(g[i].y==g[i].q){
23             if(g[i].x<g[i].p)
24                 a[g[i].x]++;
25             else  a[g[i].p]++;
26         }
27     }
28 //bucketsort
29     for(int i=1;i<=k;i++){
30         int maxn=-1;   //用于找第i大的值 
31         int p;           //用于记录第i大的值的下标 
32         for(int j=1;j<m;j++){
33             if(a[j]>maxn){
34                 maxn=a[j];
35                 p=j; 
36             }
37         }
38         a[p]=0;
39         c[p]++;
40     }
41     for(int i=1;i<=l;i++){
42         int maxn=-1;
43         int r;
44         for(int j=1;j<n;j++){
45             if(b[j]>maxn){
46                 maxn=b[j];
47                 r=j;
48             }
49         }
50         b[r]=0;
51         o[r]++;
52     }
53 //行尾没有空格 
54     for(int i=1;i<=m-1;i++){
55         if(c[i])
56             cout << i << " ";
57     }
58     cout << endl;
59     for(int i=1;i<=n;i++){
60         if(o[i])  cout << i << " ";
61     }
62     cout << endl;
63     return 0;
64 

注意:在输入的同时如果能够进行所有的处理(不需要后续处理诸如对原数据进行排序之类),为了节省空间没必要开结构体数组,因为数据用一次即可。

下面是luogu用户的题解,我觉得写得非常简洁。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std; 
 7 int m,n,k,l,d;//变量名最好起与题目一致的 
 8 int x[1005],y[1005];//横纵坐标数组 
 9 int c[1005],o[1005];//桶排要用的数组
10 int main() {
11     scanf("%d%d%d%d%d",&m,&n,&k,&l,&d); 
12     for(int i=1;i<=d;i++) { 
13         int xi,yi,pi,qi; 
14         scanf("%d%d%d%d",&xi,&yi,&pi,&qi); 
15         if(xi==pi) 
16             x[min(yi,qi)]++;//表示隔开这两排的价值 
17         else 
18             y[min(xi,pi)]++; //记得取min,即过道与前一个坐标保持一致 
19     } 
20     for(int i=1;i<=k;i++){//开始桶排 
21         int maxn=-1;//为了求出每次的最大值,需要每次扫一遍 
22         int p; 
23         for(int j=1;j<m;j++){ 
24             if(y[j]>maxn){ 
25                 maxn=y[j]; 
26                 p=j; 
27             } 
28         } 
29         y[p]=0;//求出max之后一定要记得清零!!否则无论排多少次都是一个答案 
30         c[p]++;//桶排不解释 
31     } 
32     for(int i=1;i<=l;i++){ 
33         int maxn=-1; 
34         int p; 
35         for(int j=1;j<n;j++){ 
36             if(x[j]>maxn){ 
37                 maxn=x[j]; 
38                 p=j; 
39             } 
40         } 
41         x[p]=0; //同上 
42         o[p]++; 
43     } 
44     for(int i=0;i<1005;i++)//输出答案 
45     { 
46         if(c[i])//表示需要隔开这行 
47             printf("%d ",i); 
48     } 
49     printf("\n"); 
50     for(int i=0;i<1005;i++) 
51     { 
52         if(o[i]) 
53             printf("%d ",i); //同上 
54     } 
55     return 0; 
56 }

差距:

1. 不知道桶排序思想,因而不知道可以这样选择前k个

2. 不知道在输入时处理数据可以完全不需要结构体数组啊!同时输入时是可以处理数据的啊!没说必须要输入完毕再处理,这种方法在“师座操作系统”中使用过,不过那个更为自然,因为模拟了命令行

3. 不知道algorithm头文件中的函数用法。下面给出algorithm中可能常用的函数

  • fill函数

fill()函数参数:fill(first,last,val);
// first 为容器的首迭代器,last为容器的末迭代器,val为将要替换的值。

举例:

1 int a[200];
2 fill(a, a+100, 1);

注意:
fill()中 ,它的原理是把那一块单元赋成指定的值,也就是说任何值都可以
memset(),则是将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,即0 、-1.(如果是char型数组,则任意数都可以,因为memset是一个一个字节赋值的,而char正好1Byte,但是int是4Bytes,假如赋值为1,则a数组中每个字节都用转成二进制的1(即00000001)填充,那么一个int内就是00000001000000010000000100000001,用16进制表示即为0x01010101,就等于16843009,就完成了对一个int元素的赋值了。


下面是memset函数详解

首先要知道memset函数是对字节为单位进行赋值的;

void *memset(void *s, int ch,  size_t n);
函数解释:将s中前n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
其实这里面的ch就是ascii为ch的字符;
将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII
/*************************************************************************************************************/
memset:作用是在一段内存块中填充某个给定的值,它是对较大的 结构体数组进行清零操作的一种最快方法[1] 

如果用memset(a,1,20);(实际上与memset(a,1,5*sizeof(int))结果是一样的)就是对a指向的内存的20个字节进行赋值,每个都用ASCⅡ为1的字符去填充,转为二进制后,1就是00000001,占一个字节。一个INT元素是4字节,合一起是0000 0001,0000 0001,0000 0001,0000 0001,转化成十六进制就是0x01010101,就等于16843009,就完成了对一个INT元素的赋值了。

/****************************************************************************************************************/
清零是memset(a,0,sizeof(a));
初始化为无穷大为memset(a,0x3f,sizeof(0x3f));
下面谈谈无穷大的用法;

0x3f3f3f3f的十进制是1061109567,也就是10^9级别的(和0x7fffffff一个数量级),而一般场合下的数据都是小于10^9的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。

另一方面,由于一般的数据都不会大于10^9,所以当我们把无穷大加上一个数据时,它并不会溢出(这就满足了“无穷大加一个有穷的数依然是无穷大”),事实上0x3f3f3f3f+0x3f3f3f3f=2122219134,这非常大但却没有超过32-bit int的表示范围,所以0x3f3f3f3f还满足了我们“无穷大加无穷大还是无穷大”的需求。

最后,0x3f3f3f3f还能给我们带来一个意想不到的额外好处:如果我们想要将某个数组清零,我们通常会使用memset(a,0,sizeof(a))这样的代码来实现(方便而高效),但是当我们想将某个数组全部赋值为无穷大时(例如解决图论问题时邻接矩阵的初始化),就不能使用memset函数而得自己写循环了(写这些不重要的代码真的很痛苦),我们知道这是因为memset是按字节操作的,它能够对数组清零是因为0的每个字节都是0,现在好了,如果我们将无穷大设为0x3f3f3f3f,那么奇迹就发生了,0x3f3f3f3f的每个字节都是0x3f!所以要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))。

所以在通常的场合下,0x3f3f3f3f真的是一个非常棒的选择。

因为char是1字节,memset是按照字节赋值的,相当于把每个字节都设为那个数,所以char型的数组可赋任意值,int是4个字节,当memset(,1,sizeof()); 1相当于ASSCII码的1,1转为二进制00000001,当做一字节,一字节8位,int为4字节,所以初始化完每个数为00000001000000010000000100000001 = 16843009;

初始化最大值的方法:

如果你想初始最大化,第一位为符号位,不能为1,剩下全是1,也就是7个1,1111111化为十六进制正好为0x7f,所以memset(,0x7f,sizeof());就可以了


  • swap函数

这个函数其实一般都是我自己写,一般和qsort,mergesort函数搭配。特别注意如果需要结构体整体swap时应该structname* a,structname* b;

但是int* a,int* b也不是不行,只是要写两句swap比较麻烦。

swap的STL实现,但是没有学OOP好像看不太明白

1 // TEMPLATE FUNCTION swap (from <algorithm>)
2 template<class _Ty> inline
3  void swap(_Ty& _Left, _Ty& _Right)
4  { // exchange values stored at _Left and _Right
5  _Ty _Tmp = _Move(_Left);
6  _Left = _Move(_Right);
7  _Right = _Move(_Tmp);
8  }
  • sort函数

sort函数有三个参数:

(1)第一个是要排序的数组的起始地址。
(2)第二个是结束的地址(最后一位要排序的地址的下一地址)
(3)第三个参数是排序的方法,可以是从大到小也可是从小到大,还可以不写第三个参数,此时默认的排序方法是从小到大排序。
Sort函数使用模板:
Sort(start,end,排序方法)
//降
bool cmp1(int a,int b){
    return a>b;
} 
//升
bool cmp2(int a,int b){
    return a<b;
}
sort(a,a+10);
sort(a,a+10,cmp2);
sort(a,a+10,cmp1);

STL实现比较复杂,参见STL sort 函数实现详解

还有很多其他函数,在下周学OOP的时候好好学啊!

猜你喜欢

转载自www.cnblogs.com/horizonlc/p/10355775.html