凡是最优解“最少情况”,一定利用贪心或者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函数是对字节为单位进行赋值的;
如果用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元素的赋值了。
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函数有三个参数:
//降序
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的时候好好学啊!