1. 程式人生 > 其它 >關於 相似的數集 的思路+時間複雜度分析+程式碼

關於 相似的數集 的思路+時間複雜度分析+程式碼

技術標籤:集訓

最優解法可以直接參考這位學長的文章

題目來源:NEFU OJ-2119 相似的數集簡單版
以及NEFU OJ-???? 相似的數集高階版
後者詳細連結將於賽後補上。

主要區別在高階版資料範圍和資料量均大於前者,接下來將以高階版為主。

題目描述

給出兩個數集,它們的相似程度定義為Nc/Nt*100%。其中,Nc表示兩個數集中相等的、兩兩互不相同的元素個數,而Nt表示兩個數集中總共的互不相同的元素個數。請計算任意兩個給出數集的相似程度。

輸入描述

輸入第一行給出一個正整數N(N<=50),是集合的個數。隨後N行,每行對應一個集合。每個集合首先給出一個正整數M(M<=104

),是集合中元素的個數;然後跟M個[0, 109]區間內的整數。
之後一行給出一個正整數K(K<=2000),隨後K行,每行對應一對需要計算相似度的集合的編號(集合從1到N編號)。數字間以空格分隔。
第二行給出N個數字,第i個數字表示第i道題通過的人數ai (0≤ai≤M)。

輸出描述

輸出共K行,每行一個保留2位小數的實數,表示給定兩個集合的相似度值。

輸入樣例

3
3 99 87 101
4 87 101 5 87
7 99 101 18 5 135 18 99
2
1 2
1 3

輸出樣例

50.00%
33.33%

OP

本題大體思路不復雜,主要在如何降低時間複雜度上。

思路

就題幹來說,Nc為被詢問的兩個集合中重複元素的對數,Nt為兩個集合中有多少個不同的數。

用數學語言來說,Nc為被詢問的兩個集合的交集元素個數,Nt為並集元素個數。

所以我們的目標很明確:1.去重;2.交集計數,經測試,oj的測試組中含有重複詢問組,所以還有 3.記憶化。
對於去重,我們可以用set / 陣列+unique / 桶排。

對於計數,我們可以雙指標計重 / 求交集函式 / map模擬桶排查詢鍵值

去重

set

set性質,不多說;

陣列+unique

陣列接收後,sort排序,再用unique函式完全去重;

桶排

對於109的數量級,開陣列是不現實的(簡單版範圍較小,可以使用),便想用unordered_map模擬桶排(此路後面會被斃)。

交集計數

下面的時間複雜度都是對於單次詢問的,m,n分別為兩個數集的長度

PLAN A

來自學長和lpc大佬。
時間複雜度O(min(m , n))
雙指標在排序後的陣列中實現交集計數

while(sp1<arr[s1-1][10000]&&sp2<arr[s2-1][10000]) 
		{//小的元素指標+1,元素相等same+1
			if(arr[s1-1][sp1]<arr[s2-1][sp2])sp1++;
			else if(arr[s1-1][sp1]>arr[s2-1][sp2])sp2++;
			else
			{
				same++;
				sp1++;
				sp2++;
			}
		}

這種方法不加記憶化處理也能在時間限制內橫著走。

注:經ph大佬測試,在set中使用迭代器實現這種方法同樣會超時,原因目前認為是容器問題。推測map模擬桶排使用這種方法也會超時。

PLAN B

來自ph大佬
時間複雜度O(m+n)
使用取交集函式也可以參考這篇文章)。

  	set_intersection(l[p].begin(),l[p].end(),
  						l[q].begin(),l[q].end(),
  							inserter(s,s.begin()));
	int cou = s.size();

注:使用取交集函式時,可以如下圖,方便一些
在這裡插入圖片描述在這裡插入圖片描述
注2:這種方法時間限制壓的很死,需要搭配記憶化才能避免TLE。

PLAN C

時間複雜度O(m * n)
妥妥TLE,map的.find()函式時間複雜度是O(log n),unorder_map也救不了。
資料來源:這裡這裡

//要定義迭代器it
 for(it=s[p].begin(); it!=s[p].end(); it++)
            if(s[q].find(it->first)!=s[q].end())
            //找到即數集q中含有it->first
            {
                cou++;
            }

使用這種方法時,如果像下片寫程式碼,會存在引用不存在鍵值的情況,將出現一些問題,詳細描述及解決方法可以參照這篇文章

 for(it=s[p].begin();it!=s[p].end();it++)
            if(s[q][it->first])cou++;

即某些在p數集中存在的鍵值在q中不存在,但在q中被引用。
注:下片的時間複雜度或許小一些?

程式碼

①陣列+unique去重,雙指標交集計數;

#include <bits/stdc++.h>
using namespace std;

int main()
{
	int arr[50][10001]; 
	double ans[50][50]={0},r; //記錄查詢
	int n,num,s1,s2,same,tmp,sp1,sp2;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&arr[i][10000]);
		for(int j=0;j<arr[i][10000];j++)
		{
			scanf("%d",&arr[i][j]);
		}
		sort(arr[i],arr[i]+arr[i][10000]); 
		arr[i][10000]=unique(arr[i],arr[i]+arr[i][10000])-arr[i]; //去重
	}
	scanf("%d",&n);
	while(n--)
	{
		scanf("%d %d",&s1,&s2);
		if(ans[s1-1][s2-1]!=0) 
		{
			printf("%.2f%\n",ans[s1-1][s2-1]);
			continue;
		}
		same=0;
		sp1=sp2=0;
		while(sp1<arr[s1-1][10000]&&sp2<arr[s2-1][10000]) 
		{
			//指標
			if(arr[s1-1][sp1]<arr[s2-1][sp2])sp1++;
			else if(arr[s1-1][sp1]>arr[s2-1][sp2])sp2++;
			else
			{
				same++;
				sp1++;
				sp2++;
			}
		}
		r=same*100.0/(arr[s1-1][10000]+arr[s2-1][10000]-same); //計算
		ans[s1-1][s2-1]=ans[s2-1][s1-1]=r;
		printf("%.2f%\n",r);
	}
	return 0;
}

②陣列,取交集函式;

#include <bits/stdc++.h>

using namespace std;

int main()
{
    set<int> l[51];
    double ans[50][50]={0};
    int n,g,i,m,p,q,cou;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d",&g);
            l[i].insert(g);
        }
    }
    scanf("%d",&n);
    while(n--)
    {
        set<int>s;
        cou=0;
        scanf("%d%d",&p,&q);
        if(ans[p-1][q-1]>=0.0001)
        {
            printf("%.2lf%%\n", ans[p-1][q-1]);
        }
        else{
        set_intersection(l[p].begin(),l[p].end(),l[q].begin(),l[q].end(),inserter(s,s.begin()));
        int cou = s.size();
        ans[p-1][q-1]=ans[q-1][p-1]=cou*100.0/(l[p].size()+l[q].size()-cou);
        printf("%.2lf%%\n", ans[p-1][q-1]);}
    }
    return 0;
}

③被TLE斃掉的:map模擬桶排,查詢鍵值。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int a[51]= {0};
    double ans[50][50]={0};
    unordered_map<int,bool> s[51];
    unordered_map<int,bool>::iterator it;
    int n,g,i,m,p,q,cou;
    scanf("%d",&n);
    for(i=1; i<=n; i++)
    {
        scanf("%d",&m);
        a[i]=m;
        while(m--)
        {
            scanf("%d",&g);
            if(!s[i][g])s[i][g]=1;
            else a[i]--;
        }
    }
    scanf("%d",&n);
    while(n--)
    {
        cou=0;
        scanf("%d%d",&p,&q);
        if(ans[p-1][q-1]>=0.0001)
        {
            printf("%.2lf%%\n", ans[p-1][q-1]);
        }
        else{
        if(a[p]>a[q])swap(p,q);
        for(it=s[p].begin(); it!=s[p].end(); it++)
            if(s[q].find(it->first)!=s[q].end())
            {
                cou++;
            }
            ans[p-1][q-1]=ans[q-1][p-1]=cou*100.0/(a[p]+a[q]-cou);
        printf("%.2lf%%\n", ans[p-1][q-1]);
        }
    }
    return 0;
}

ED

這兩道題我一共submit 52次!!!
AC率不用要了555。