算法訓練 審美課

問題描述

《審美的歷程》課上有n位學生,帥老師展示了m幅畫,其中有些是梵高的作品,另外的都出自五歲小朋友之手。老師請同學們分辨哪些畫的作者是梵高,但是老師自己並沒有答案,因爲這些畫看上去都像是小朋友畫的……老師只想知道,有多少對同學給出的答案完全相反,這樣他就可以用這個數據去揭穿披着皇帝新衣的抽象藝術了(支持帥老師_)。
  答案完全相反是指對每一幅畫的判斷都相反。

輸入格式

第一行兩個數n和m,表示學生數和圖畫數;
  接下來是一個n*m的01矩陣A:
  如果aij=0,表示學生i覺得第j幅畫是小朋友畫的;
  如果aij=1,表示學生i覺得第j幅畫是梵高畫的。

輸出格式

輸出一個數ans:表示有多少對同學的答案完全相反。

樣例輸入

3 2
1 0
0 1
1 0

樣例輸出

2

樣例說明

同學1和同學2的答案完全相反;
同學2和同學3的答案完全相反;
所以答案是2。

數據規模和約定

對於50%的數據:n<=1000;
對於80%的數據:n<=10000;
對於100%的數據:n<=50000,m<=20。

解題思路與代碼

暴力求解

思路就是定義一個足夠空間的二維數組,每一行存放每位同學對畫的看法,一共n行,m列。
輸入數據後,
依次比較每一行元素是否都相反,是則ans++

由此時間複雜度爲O(n2*m),由於n<=50000,m<=20,可能會超時。

代碼
#include <iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int a[50001][21];
int Compare(int i,int j,int m)
{
    int x;
    //比較第i行和第j行的所有數據是否完全相反,和爲1
    for(x=1;x<=m;x++){
       if(a[i][x]+a[j][x]==0||a[i][x]+a[j][x]==2)//都是0或者都是1則不符合完全相反的條件
          return 0;
    }
    if(x>m)
        return 1;
        //for循環執行完了才說明第i行和第j行的所有數據完全相反
}
int main()
{
   int n,m;//兩個數n和m,表示學生數和圖畫數;
   cin>>n>>m;//n<=50000,m<=20。
   //接下來輸入n行,m列
   for(int i=1;i<=n;i++)
   for(int j=1;j<=m;j++){
        cin>>a[i][j];
   }
   int ans=0;//最終的學生對數
   for(int i=1;i<=n;i++)
   for(int j=i+1;j<=n;j++){
      if(Compare(i,j,m)==1)
        ans++;
   }
   cout<<ans;
   return 0;
}
結果,只能過60%的數據。

在這裏插入圖片描述

二進制保存,取反操作

如果把每一位同學的看法(即每一行01序列)看成是一個二進制表示的數,則220-1最大不超過15000000,完全可以用32位的int表示。

如何判斷兩位同學的看法完全相反?

學過數字邏輯原理的課程,有異或,同或操作,異或的運算法則可以概括爲相同爲0,不同爲1
如果一個二進制串與11…1異或,則實現了按位取反操作。這就是本思路的來源。

由此,所有同學的看法信息可以保存在一個一維數組中,整個算法的時間複雜度也降爲O(n2)。

代碼
#include <iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;
int a[50001];//n<=50000
int main()
{
	int n,m;//兩個數n和m,表示學生數和圖畫數;
	//n<=50000,m<=20。
	cin>>n>>m;
	//接下來輸入n行
	memset(a,0,sizeof(a));//a數組初值爲0
	for(int i=1;i<=n;i++){
		int temp;
		for(int j=1;j<=m;j++){
			cin>>temp;
			a[i] = (a[i]<<1) + temp;//構造二進制表達的整型數字
		}
	}
	int ans = 0;
	int max = (1<<m) - 1;//構造11...1序列

	for(int i=1;i<=n;i++)
	for(int j=i+1;j<=n;j++){
		if(a[i] == (a[j]^max))//如果a[i] == a[j]按位取反
			ans++;
	}

	cout<<ans<<endl;
    return 0;
}
結果,只能過80%的數據

在這裏插入圖片描述

增強版二進制按位取反

只能過80%的數據說明O(n2)的時間複雜度還是可能會超時,必須再進行優化,優化的思路是:

統計每種看法二進制序列表示的數值相同的同學個數,將時間複雜度降爲O(n)

這樣還需要定義一個b數組,每次求出a[i]的值之後b[a[i]]++;
b數組的最大長度爲220-1 ;
這裏就定義int b[15000001];

代碼
#include <iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;
int a[50001];
int b[1500001];
int main()
{
	int n,m;//兩個數n和m,表示學生數和圖畫數;
	//n<=50000,m<=20。
	cin>>n>>m;
	//接下來輸入n行
	memset(a,0,sizeof(a));//a數組初值爲0
	memset(b,0,sizeof(b));//b數組初值爲0
	for(int i=1;i<=n;i++){
		int temp;
		for(int j=1;j<=m;j++){
			cin>>temp;//構造二進制表達的整型數字
			a[i] = (a[i]<<1) + temp;
		}
		b[a[i]]++;//統計持看法數值爲a[i]的學生數
	}
	int ans = 0;
	int max = (1<<m) - 1;//構造11...1序列

	for(int i=1;i<=n;i++){
		int temp = a[i]^max;//對i同學的看法數值進行按位取反,
		//temp爲按位取反之後的數值
		//b[temp]爲與i同學持完全相反觀點的學生個數
		ans += b[temp];
	}
	ans = ans/2;//要求出的是持完全相反觀點的學生對數,對ans要減半。
	cout<<ans<<endl;
    return 0;
}
結果,全部數據通過

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章