PAT(甲級)2019年春季考試 7-3 Telefraud Detection

7-3 Telefraud Detection (25分)

Telefraud(電信詐騙) remains a common and persistent problem in our society. In some cases, unsuspecting victims lose their entire life savings. To stop this crime, you are supposed to write a program to detect those suspects from a huge amount of phone call records.算法

A person must be detected as a suspect if he/she makes more than K short phone calls to different people everyday, but no more than 20% of these people would call back. And more, if two suspects are calling each other, we say they might belong to the same gang. A makes a short phone call to B means that the total duration of the calls from A to B is no more than 5 minutes.數組

Input Specification:

Each input file contains one test case. For each case, the first line gives 3 positive integers $K (≤500$, the threshold(閾值) of the amount of short phone calls), $N (≤10​^3$​​, the number of different phone numbers), and $M (≤10​^5$​​, the number of phone call records). Then $M$ lines of one day's records are given, each in the format:網絡

caller receiver duration

where caller and receiver are numbered from 1 to N, and duration is no more than 1440 minutes in a day.函數

Output Specification:

Print in each line all the detected suspects in a gang, in ascending order of their numbers. The gangs are printed in ascending order of their first members. The numbers in a line must be separated by exactly 1 space, and there must be no extra space at the beginning or the end of the line.this

If no one is detected, output None instead.spa

Sample Input 1:

5 15 31
1 4 2
1 5 2
1 5 4
1 7 5
1 8 3
1 9 1
1 6 5
1 15 2
1 15 5
3 2 2
3 5 15
3 13 1
3 12 1
3 14 1
3 10 2
3 11 5
5 2 1
5 3 10
5 1 1
5 7 2
5 6 1
5 13 4
5 15 1
11 10 5
12 14 1
6 1 1
6 9 2
6 10 5
6 11 2
6 12 1
6 13 1

Sample Output 1:

3 5
6

Note: In sample 1, although 1 had 9 records, but there were 7 distinct receivers, among which 5 and 15 both had conversations lasted more than 5 minutes in total. Hence 1 had made 5 short phone calls and didn't exceed the threshold 5, and therefore is not a suspect.code

Sample Input 2:

5 7 8
1 2 1
1 3 1
1 4 1
1 5 1
1 6 1
1 7 1
2 1 1
3 1 1

Sample Output 2:

None

題目限制:

image.png

題目大意:

  1. 一我的給不一樣的K個以上的人打電話,其中給每一個人的通話總和不超過5分鐘的,稱爲短通話,短通話中只有不超過20%的人給他回電;
  2. 知足1的兩我的互相通話,就是同一個團伙。
  3. 要根據通話記錄,分析出團伙的成員,按照從小到大的序號輸出成員編號。

算法思路:

這個題目的本意就是在全部嫌疑人組成的網絡中找到每個幫派的成員,而每個幫派能夠認爲是一個連通份量,這樣就可使用DFS來進行求解,同時也能夠認爲是一個集合,這樣就可使用並查集的方式來求解,這裏給出兩種不一樣的實現方法。orm

預處理:

DFS和並查集的預處理都是同樣的,主要目的就是找出全部的嫌疑人,這樣才方便在全部嫌疑人所構成的網絡中進行查找和合並。首先咱們使用二維數組G保存該圖的邊權,並注意到此圖是一個有向圖,邊權在輸入的時候得累計,使用frequency和callback數組分別表示每個人打電話不超過5分鐘的次數和每個嫌疑人在打的不超過5分種通話中,接收方回電話的人數,最後並用suspecters數組保存全部的嫌疑人。有了以上的容器後,接下來就是獲取全部的嫌疑人了,直接遍歷二維數組G,使用i表示caller,j表示receiver,只要G[i][j]!=0&&G[i][j]<=5,說明當前caller打了一次短電話,累計++frequency[i],在此基礎上,若是G[j][i]!=0,說明receiver回電話了,累計++callback[i],在遍歷完全部caller打的電話記錄後,判斷是否frequency[i]>K&&callback[i]<=floor(0.2*frequency[i])若是是,說明i是嫌疑人,將其加入到suspecters。blog

DFS解法:

使用visited數組標記每個嫌疑人是否被訪問,set<int> gang保存每個幫派的人(一次DFS保存一個),bool hasGang標記是否有幫派,初始爲false,而後就使用DFS遍歷整個圖,每一次DFS遍歷一個gang,遍歷以前得清空gang集合的內容,核心代碼以下:排序

void DFS(int root,int K){
    visited[root] = true;
    gang.insert(root);
    for (int suspecter : suspecters) {
        if(!visited[suspecter]&&G[root][suspecter]!=0&&G[suspecter][root]!=0){
            //訪問每個沒有被訪問而且與當前root相互打電話的嫌疑人
            DFS(suspecter,K);
        }
    }
}
bool hasGang = false;
for (int suspecter : suspecters) {
    if(!visited[suspecter]){
        gang.clear();
        DFS(suspecter,K);
        int num = 0;
        for(auto &item:gang){
            hasGang = true;// 有gang存在
            printf("%d",item);
            if(num<gang.size()-1) printf(" ");
            ++num;
        }
        printf("\n");
    }
}
並查集解法:

在預處理完成以後,就要將全部屬於一個gang的嫌疑人進行合併,合併操做使用Union函數來實現,因爲嫌疑人是從小到大加入的集合,咱們就能夠直接從第一個沒有被訪問的嫌疑人開始,將他和其同屬於一個gang的其餘嫌疑人一併進行輸出便可,這樣就省去了排序的麻煩,其核心代碼以下:

int father[1005];

void init(){
    for(int i=0;i<1005;++i){
        father[i] = i;
    }
}

int getFather(int a){
    while(a!=father[a]){
        a = father[a];
    }
    return a;
}

void Union(int a,int b){
    int fa = getFather(a);
    int fb = getFather(b);
    if(fa!=fb){
        father[fb] = fa;
    }
}
// 合併全部的嫌疑人
for(int i:suspecters){
    for(int j:suspecters){
        if(i!=j&&G[i][j]!=0&&G[j][i]!=0){
            // i和j是一個gang
            Union(i,j);
        }
    }
}
// 嫌疑人是從小到大加入的集合,從小到大直接輸出便可
for(int i=0;i<suspecters.size();++i){
    if(!visited[suspecters[i]]){
        visited[suspecters[i]] = true;
        printf("%d",suspecters[i]);
        for(int j=i+1;j<suspecters.size();++j){
            if(!visited[suspecters[j]]&&getFather(suspecters[i])==getFather(suspecters[j])){
                visited[suspecters[j]] = true;
                printf(" %d",suspecters[j]);
            }
        }
        printf("\n");
    }
}

注意點:

  • 一、嫌疑人的斷定中須要不超過20%的人回電話,這個20%若是是作的乘法運算得向下取整,也可使用回電話的人數乘以5來代替。
  • 二、嫌疑人之間得互相打電話纔算是屬於一個gang。

提交結果:

image.png

AC代碼1:

#include <string>
#include <cmath>
#include <set>
#include <vector>

using namespace std;

int G[1005][1005];
int frequency[1005];// 統計每個人打電話不超過5分鐘的次數
int callback[1005];// 每個嫌疑人被回電話的人數
vector<int> suspecters;// 全部的嫌疑人
bool visited[1005];// 標記每個嫌疑人是否被訪問
set<int> gang;// 每個幫派的嫌疑人

void DFS(int root,int K){
    visited[root] = true;
    gang.insert(root);
    for (int suspecter : suspecters) {
        if(!visited[suspecter]&&G[root][suspecter]!=0&&G[suspecter][root]!=0){
            DFS(suspecter,K);
        }
    }
}

int main(){
    int K,N,M;//閾值,頂點數目,邊數目
    scanf("%d %d %d",&K,&N,&M);
    for (int i = 0; i < M; ++i) {
        int a,b,time;
        scanf("%d %d %d",&a,&b,&time);
        G[a][b] += time;
    }
    for(int i=1;i<=N;++i){
        for (int j = 1; j <=N ; ++j) {
            if(G[i][j]!=0&&G[i][j]<=5){
                ++frequency[i];
                if(G[j][i]!=0){
                    // j給i回電話了
                    ++callback[i];
                }
            }
        }
        if(frequency[i]>K&&callback[i]<=floor(0.2*frequency[i])){
            // 打不超過5分鐘的電話的次數超過了閾值,而且回電話的人數不超過20%
            suspecters.push_back(i);
        }
    }
    bool hasGang = false;
    for (int suspecter : suspecters) {
        if(!visited[suspecter]){
            gang.clear();
            DFS(suspecter,K);
            int num = 0;
            for(auto &item:gang){
                hasGang = true;
                printf("%d",item);
                if(num<gang.size()-1) printf(" ");
                ++num;
            }
            printf("\n");
        }
    }
    if(!hasGang){
        printf("None");
    }
    return 0;
}

AC代碼2:

#include<cstdio>
#include <cmath>
#include <vector>

using namespace std;

int G[1005][1005];
int frequency[1005];// 統計每個人打電話不超過5分鐘的次數
int callback[1005];// 每個嫌疑人被回電話的人數
vector<int> suspecters;// 全部的嫌疑人
bool visited[1005];// 標記每個嫌疑人是否被訪問
int father[1005];

void init(){
    for(int i=0;i<1005;++i){
        father[i] = i;
    }
}

int getFather(int a){
    while(a!=father[a]){
        a = father[a];
    }
    return a;
}

void Union(int a,int b){
    int fa = getFather(a);
    int fb = getFather(b);
    if(fa!=fb){
        father[fb] = fa;
    }
}

int main(){
    init();
    int K,N,M;//閾值,頂點數目,邊數目
    scanf("%d %d %d",&K,&N,&M);
    for (int i = 0; i < M; ++i) {
        int a,b,time;
        scanf("%d %d %d",&a,&b,&time);
        G[a][b] += time;
    }
    for(int i=1;i<=N;++i){
        for (int j = 1; j <=N ; ++j) {
            if(G[i][j]!=0&&G[i][j]<=5){
                ++frequency[i];
                if(G[j][i]!=0){
                    // j給i回電話了
                    ++callback[i];
                }
            }
        }
        if(frequency[i]>K&&callback[i]<=floor(0.2*frequency[i])){
            // 打不超過5分鐘的電話的次數超過了閾值,而且回電話的人數不超過20%
            suspecters.push_back(i);
        }
    }
    // 合併全部的嫌疑人
    for(int i:suspecters){
        for(int j:suspecters){
            if(i!=j&&G[i][j]!=0&&G[j][i]!=0){
                // i和j是一個gang
                Union(i,j);
            }
        }
    }
    // 嫌疑人是從小到大加入的集合,從小到大直接輸出便可
    for(int i=0;i<suspecters.size();++i){
        if(!visited[suspecters[i]]){
            visited[suspecters[i]] = true;
            printf("%d",suspecters[i]);
            for(int j=i+1;j<suspecters.size();++j){
                if(!visited[suspecters[j]]&&getFather(suspecters[i])==getFather(suspecters[j])){
                    visited[suspecters[j]] = true;
                    printf(" %d",suspecters[j]);
                }
            }
            printf("\n");
        }
    }
    if(suspecters.empty()){
        printf("None");
    }
    return 0;
}
相關文章
相關標籤/搜索