關於 相似的數集 的思路+時間複雜度分析+程式碼
技術標籤:集訓
最優解法可以直接參考這位學長的文章
題目來源: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為兩個集合中有多少個不同的數。
所以我們的目標很明確: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。