POJ 2443Set Operation(好題! 每32位壓縮處理)

果7發表於2013-09-27
Set Operation
Time Limit: 3000MS   Memory Limit: 65536K
Total Submissions: 2286   Accepted: 876

Description

You are given N sets, the i-th set (represent by S(i)) have C(i) element (Here "set" isn't entirely the same as the "set" defined in mathematics, and a set may contain two same element). Every element in a set is represented by a positive number from 1 to 10000. Now there are some queries need to answer. A query is to determine whether two given elements i and j belong to at least one set at the same time. In another word, you should determine if there exist a number k (1 <= k <= N) such that element i belongs to S(k) and element j also belong to S(k).

Input

First line of input contains an integer N (1 <= N <= 1000), which represents the amount of sets. Then follow N lines. Each starts with a number C(i) (1 <= C(i) <= 10000), and then C(i) numbers, which are separated with a space, follow to give the element in the set (these C(i) numbers needn't be different from each other). The N + 2 line contains a number Q (1 <= Q <= 200000), representing the number of queries. Then follow Q lines. Each contains a pair of number i and j (1 <= i, j <= 10000, and i may equal to j), which describe the elements need to be answer.

Output

For each query, in a single line, if there exist such a number k, print "Yes"; otherwise print "No".

Sample Input

3
3 1 2 3
3 1 2 5
1 10
4
1 3
1 5
3 5
1 10

Sample Output

Yes
Yes
No
No

Hint

The input may be large, and the I/O functions (cin/cout) of C++ language may be a little too slow for this problem.


題目大意:題目意思很簡單,給你n個集合,每個集合有若干個元素。問你是否存在一個集合使得a,b都在裡面。

 解題思路:現在考慮時間複雜度,n為10^3,元素的個數為10^4,查詢語句q為10^5.自己開始寫的是直接10^5*10^3,還需要考慮測試資料的組數,這樣查詢肯定會超時。當時用vector,把一個元素出現的行儲存起來,查詢的時候看元素a在哪些行出現過,然後列舉這些行是否出現過b。那樣訪問的次數就會少很多,不過還是很險會超時,2800ms.

 題目地址:Set Operation

沒有壓縮處理的AC程式碼:
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdio>
#include<set>
#include<vector>
using namespace std;
int n;
short visi[1002][10002];
struct mm
{
    int aa;
    int bb;
    int fla;
};
mm node[200002];
vector<int> mq[10002];
vector<int>::iterator it;

int main()
{
    int i,j,t,x,q,flag;
    int a,b;
    int tt;
    while(~scanf("%d",&n))
    {
        for(i=0;i<n;i++)
            for(j=1;j<=10000;j++)
                visi[i][j]=0;
        for(i=1;i<=10000;i++)
           mq[i].clear();
        for(i=0;i<n;i++)
        {
            scanf("%d",&t);
            for(j=0;j<t;j++)
            {
                scanf("%d",&x);
                visi[i][x]=1;
                mq[x].push_back(i);
            }
        }

        scanf("%d",&q);
        int len=0;
        for(i=0;i<q;i++)
        {
            flag=0;
            scanf("%d%d",&a,&b);
            tt=0;
            for(j=0;j<len;j++)  //如果已經訪問過,就不往下找了,直接輸出
            {
                if((a==node[j].aa&&b==node[j].bb)||(b==node[j].aa&&a==node[j].bb))
                {
                    tt=1;
                    flag=node[j].fla;
                    break;
                }
            }
            if(tt)
            {
                if(flag) printf("Yes\n");
                else printf("No\n");
            }
            else   //沒訪問過,暴力列舉a存在的所有的行,看b是否存在
            {
                 for(it=mq[a].begin();it!=mq[a].end();it++)
                 {
                     int tmp=*it;
                     if(visi[tmp][b])
                     {
                         flag=1;
                         break;
                     }
                 }
                 if(!flag)
                 {
                     node[len].aa=a,node[len].bb=b,node[len].fla=0;
                     printf("No\n");
                 }
                 else
                 {
                     node[len].aa=a,node[len].bb=b,node[len].fla=1;
                     printf("Yes\n");
                 }
            }
        }
    }
    return 0;
}

//2797MS


下面這種方法就很精妙了,關鍵需要這種思維方式。我們通過一個元素在某一行存在與否可以得到一個01矩陣,就是上面寫的visi矩陣。如果按照開始寫的方式,那麼就是列舉1000行看看是否有visi[i][a]與visi[i][b]相與為1的情況,如果有,說明a,b在第i行兩個一起出現過。不過這種方法的時間複雜度就是上面計算的10^8而且還沒計算測試的資料量。肯定會超時。我們可以把每32行儲存到一個int裡面,按位壓縮。這樣時間複雜度就降為了10^6。這種方法真的很奇特!

AC程式碼:
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdio>
#include<set>
#include<vector>
using namespace std;
int n;
short visi[1002][10002];
int p[10002][32]; //把每個元素存在每32行壓縮到到一個int內

int main()
{
    int i,j,k,t,x,q,flag;
    int a,b;
    int len;  //分成32位的長度
    while(~scanf("%d",&n))
    {
        for(i=0;i<n;i++)
            for(j=1;j<=10000;j++)
                visi[i][j]=0;

        for(i=1;i<=n;i++)
        {
            scanf("%d",&t);
            for(j=0;j<t;j++)
            {
                scanf("%d",&x);
                visi[i][x]=1;
            }
        }

        len=n/32;
        if(n%32!=0)
            len++;

        for(i=0;i<len-1;i++)   //壓縮處理
        {
            for(j=1;j<=10000;j++)
            {
                p[j][i]=0;
                int tmp=1;
                for(k=1+i*32;k<=32+i*32;k++)
                {
                    p[j][i]+=visi[k][j]*tmp;
                    tmp<<=1;
                }
            }
        }
        for(j=1;j<=10000;j++)
        {
            p[j][i]=0;
            int tmp=1;
            for(k=1+i*32;k<=n;k++)
            {
                p[j][i]+=visi[k][j]*tmp;
                tmp<<=1;
            }
        }

        scanf("%d",&q);
        for(i=0;i<q;i++)
        {
            flag=0;
            scanf("%d%d",&a,&b);
            for(j=0;j<len;j++)
            {
                if(p[a][j]&p[b][j])  //兩個相與得到1說明有一行a,b都存在
                {
                    flag=1;
                    break;
                }
            }
            if(flag) printf("Yes\n");
            else printf("No\n");
        }
    }
    return 0;
}
//891MS



相關文章