SUBSETS - EDITORIAL

PROBLEM LINK:

Practice

Contest: Division 1

Contest: Division 2

Setter: Erfan Alimohammadi

Tester: Teja Vardhan Reddy

Editorialist: Teja Vardhan Reddy

DIFFICULTY:

PREREQUISITES:

Meet in the middle, prime factorisation, SOS dp.

PROBLEM:

Given an array A of n numbers ,construct a graph of n vertices by adding edges between $i$th and $j$th vertex (i \lt j) if and only if A_i* A_j is not divisible by a cubic number. Find number of cliques in this graph (note empty clique with no vertices is also counted).

EXPLANATION

If a number is divisible by a cubic number, then its prime factorisation contains a prime which occurs more than 2 times.

Firstly we will find prime factorisation of each of the N numbers and store them. Complexity of finding factorisation is O(\sqrt{A_i}) for i th number. Complexity of finding factorisation of all N numbers is O(N*\sqrt{A_{max}})

Now, iterate over all pairs of (i,j) such that (i \lt j) and combine their prime factorisations to obtain prime factorisation of A_i*A_j and check if any prime occurs more than 2 times in prime factorisation of A_i*A_j. If yes, then do not add edge between i and j otherwise add an edge between i and j. Each number has at most log(A_{max}) terms in prime factorisation. Hence, complexity of this step is O(n*n*log(n)).

Now, we have a undirected graph with n vertices. We want to find number of cliques in this graph.

Since n can be upto 40, we can encode adjacency list of each vertex in a bitmask of length n (long datatype would suffice). (In adjacency bitmask of ith vertex,we will also mark ith bit, we will see why so in a moment).

Now to check if a subset of vertices represented by mask p forms a clique. We want all of them to be connected to everything else in p. So now, every element in p must be adjacent to every other element. Here is where marking i th bit in adjacency bitmask of i will help. Now , we want p to be submask of the adjacency bitmasks of vertices present in p. So we can check this in O(n) for a single bitmask p.

Let us find for each subset of \{1,2,3,...,n/2\}, if it is clique or not (we will represent subsets using n/2 length bitmask). Checking for each mask takes O(n) time. Checking for all the masks takes O(2^{n/2}*n) time.

Now we know for each subset of first n/2 elements, if it is a clique or not. (Let us call this B array and its 1 if clique can be formed for this mask otherwise 0).

Similarly we can calculate for each subset of last n/2 vertices, if it is a clique or not.

Now, we will find for each mask of first n/2 elements, number of submasks of this mask which are cliques i.e for each mask m, we want summation of B[p] where p is submask of m.

This is a standard problem solved by SOS dp. Complexity is O(2^{n/2}*n).

Now, we have for each subset of first n/2 vertices, how many subsets of that subset are cliques.

We will iterate over all subsets of last n/2 vertices. And for each mask p, we will find count of masks p1 of first n/2 vertices such that vertices from p and p1 together form clique. Let us call the vertices from p and p1 together as set S.

We will identify some necessary conditions for vertices from p and p1 together to form a clique.
Condition 1: vertices from p need to form a clique
Condition 2: all vertices from p1 must be adjacent to all the vertices in p.
Condition 3: vertices from p1 need to form a clique.

Now, Lets prove these are sufficient.
Condition 1 and 2 state that all vertices from p have edges to all vertices in S (obviously except self edge).

Condition 2 and 3 state that all vertices from p1 have edges to all vertices in S (obviously except self edge).

Hence, S forms a clique if and only if above all conditions are true.

So we will iterate over subsets of last (n/2) vertices. And consider only those mask p that satisfy condition 1.

So now, we will find all vertices belonging to first (n/2) vertices which are adjacent to all vertices in p. Lets say this set of vertices are represented by mask p2. Now any subset of p2 combined with p satisfy condition 2. And no other subset of first n/2 vertices combined with p satisfy condition 2. Now, we want to find number of subsets of p2 that are cliques (Because p1 is a clique). Yayy!!, we have solved this above using SOS dp.

NOTE: “we will find all vertices belonging to first n/2 vertices which are adjacent to all vertices in p.” for this, we will take bitwise and of all the adjacency bitmasks of vertices in p and take first n/2 bits of it. This take O(n) complexity per mask. And after finding p2, it will take O(1) comp$O(nnlog(n))$lexity because we already know count of cliques in each mask of first n/2 vertices. So total complexity across all subsets of last n/2 vertices is O(2^{n/2}*n).

TIME COMPLEXITY

O( n*n*log(n) + n*\sqrt{A_{max}} + 2^{n/2}*n).

SOLUTIONS:

Setter's Solution
#include <bits/stdc++.h>

using namespace std;

const int MAXN = 43;
const int MAXDPSIZE = (1 << (MAXN / 2)) + 10;

int t, n, m, a[MAXN], firstPartNeighbors[MAXN];
long long dp[MAXDPSIZE], ans;
bool isClique[MAXDPSIZE], mat[MAXN][MAXN];
unordered_map<int, int> factorsMap;
set<pair<int, int> > factorsSet;

void factorize(int x) {
	for (int i = 2; i * i <= x; i++)
		while (x % i == 0) {
			factorsSet.erase({i, factorsMap[i]});
			factorsMap[i]++;
			factorsSet.insert({i, factorsMap[i]});
			if (factorsMap[i] >= 3) //for optimization
				return;
			x /= i;
		}
	if (x > 1) {
		factorsSet.erase({x, factorsMap[x]});
		factorsMap[x]++;
		factorsSet.insert({x, factorsMap[x]});
	}
}

bool adjacent(int x, int y) {
	factorsMap.clear();
	factorsSet.clear();
	factorize(x);
	factorize(y);
	for (auto i: factorsSet)
		if (i.second >= 3)
			return false;
	return true;
}

int main() {
	ios::sync_with_stdio(0);
	cin >> t;
	while (t--) {
		cin >> n;
		for (int i = 0; i < n; i++)
			cin >> a[i];
		memset(mat, 0, sizeof mat);
		memset(firstPartNeighbors, 0, sizeof firstPartNeighbors);
		int half = n / 2;
		for (int i = 0; i < n; i++)
			for (int j = i + 1; j < n; j++)
				if (adjacent(a[i], a[j])) {
					if (j < half)
						firstPartNeighbors[i] |= (1 << j);
					if (i < half)
						firstPartNeighbors[j] |= (1 << i);
					mat[i][j] = mat[j][i] = true;
				}
		isClique[0] = true;
		for (int mask = 1; mask < (1 << half); mask++) {
			int v = __builtin_ctz(mask);
			int mask2 = (mask ^ (1 << v));
			if (!isClique[mask2]) {
				isClique[mask] = false;
				continue;
			}
			isClique[mask] = true;
			for (int i = 0; i < half; i++) {
				if ((mask2 & (1 << i)) && !mat[v][i]) {
					isClique[mask] = false;
					break;
				}
			}
		}
		dp[0] = 1;
		for (int mask = 1; mask < (1 << (n - half)); mask++) {
			int v = __builtin_ctz(mask);
			int mask2 = (mask ^ (1 << v));
			dp[mask] = dp[mask2];
			int mask3 = 0;
			for (int i = 0; i < n - half; i++)
				if (mat[v + half][i + half] && ((1 << i) & mask))
					mask3 |= (1 << i);
			dp[mask] += dp[mask3];
		}
		ans = 0;
		for (int mask = 0; mask < (1 << half); mask++) {
			if (!isClique[mask])
				continue;
			int mask2 = 0;
			for (int i = half; i < n; i++) {
				if ((firstPartNeighbors[i] & mask) == mask)
					mask2 |= (1 << (i - half));
			}
			ans += dp[mask2];
		}
		cout << ans << endl;
	}
	return 0;
}
Tester's Solution
//teja349
#include <bits/stdc++.h>
#include <vector>
#include <set>
#include <map>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <utility>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <iomanip>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp> 
//setbase - cout << setbase (16); cout << 100 << endl; Prints 64
//setfill -   cout << setfill ('x') << setw (5); cout << 77 << endl; prints xxx77
//setprecision - cout << setprecision (14) << f << endl; Prints x.xxxx
//cout.precision(x)  cout<<fixed<<val;  // prints x digits after decimal in val
 
using namespace std;
using namespace __gnu_pbds;
 
#define f(i,a,b) for(i=a;i<b;i++)
#define rep(i,n) f(i,0,n)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define pb push_back
#define mp make_pair
#define vi vector< int >
#define vl vector< ll >
#define ss second
#define ff first
#define ll long long
#define pii pair< int,int >
#define pll pair< ll,ll >
#define sz(a) a.size()
#define inf (1000*1000*1000+5)
#define all(a) a.begin(),a.end()
#define tri pair<int,pii>
#define vii vector<pii>
#define vll vector<pll>
#define viii vector<tri>
#define mod (1000*1000*1000+7)
#define pqueue priority_queue< int >
#define pdqueue priority_queue< int,vi ,greater< int > >
#define flush fflush(stdout) 
#define primeDEN 727999983
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
 
// find_by_order()  // order_of_key
typedef tree<
int,
null_type,
less<int>,
rb_tree_tag,
tree_order_statistics_node_update>
ordered_set;
 
 
vector< map<int,int> > mapi(100);
int gg[100];
int good1[(1<<21)+10],wow[100],haha[100],bad[100][100];
int a[123];
int main(){
    std::ios::sync_with_stdio(false); cin.tie(NULL);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        int i;
        rep(i,n){
            cin>>a[i];
            gg[i]=0;
        }
        int j;
        rep(i,n){
            mapi[i].clear();
            for(j=2;j*j<=a[i];j++){
                while(a[i]%j==0){
                    mapi[i][j]++;
                    a[i]/=j;
                }
 
            }
            if(a[i]!=1){
                mapi[i][a[i]]++;        
            }
        }
        map<int,int>::iterator it;
        rep(i,n){
            for(it=mapi[i].begin();it!=mapi[i].end();it++){
                if(it->ss>=3){
                    gg[i]=1;
                }
            }
        }
        rep(i,n){
            rep(j,n){
                bad[i][j]=0;
            }
        }
        rep(i,n){
            f(j,i+1,n){
                if(gg[i] || gg[j]){
                    bad[i][j]=1;
                    bad[j][i]=1;
                }
                for(it=mapi[j].begin();it!=mapi[j].end();it++){
                    if(mapi[i].find(it->ff)!=mapi[i].end() && mapi[i][it->ff]+it->ss>=3){
                        bad[i][j]=1;
                        bad[j][i]=1;
                        break;
                    }
                }
            }
        }
        if(n==1){
            cout<<2<<endl;
            continue;
        }
        int part1=n/2;
        int part2 = n-n/2;
        rep(i,n){
            wow[i]=0;
        }
        rep(i,(n/2)){
            rep(j,n/2){
                if(!bad[i][j]){
                    wow[i]|=(1<<j);
                }   
            }
        }
        int ans,ans1;
        ll tot=0;
        rep(i,(1<<part1)){
            good1[i]=0;
            ans=(1<<part1)-1;
            rep(j,part1){
                if((1<<j)&i){
                    ans=ans&wow[j];
                }
            }
            if((ans&i)==i){
                good1[i]=1;
            }
        }
        rep(i,part1){
            rep(j,(1<<part1)){
                if(j&(1<<i)){
                    good1[j]+=good1[j^(1<<i)];
                }
            }
        }
        rep(i,n){
            wow[i]=0;
            haha[i]=0;
        }
        rep(i,part2){
            rep(j,part2){
                if(!bad[i+part1][j+part1]){
                    wow[i]|=(1<<j);
                }
            }
            //cout<<wow[i]<<endl;
        }
        rep(i,part2){
            rep(j,part1){
                if(!bad[i+part1][j]){
                    haha[i]|=(1<<j);
                }
            }
        }
        
        rep(i,(1<<part2)){
            ans=(1<<part2)-1;
            ans1=(1<<part1)-1;
            rep(j,part2){
                if((1<<j)&i){
                    ans=ans&wow[j];
                    ans1=ans1&haha[j];
                }
            }
            
            if((ans&i)==i){
                
                tot+=good1[ans1];               
            }
        }
        // if(tot>1e8){
        //  assert(0);
        // }
        cout<<tot<<endl;
    }
    return 0;   
}

Feel free to Share your approach, If it differs. Suggestions are always welcomed. :slight_smile:

10 Likes

How to find this? Can you please elaborate?

I guess we can use the operator AND (&) for all the adjacency bitmasks of vertices of this subset,called X, this takes O(n). And to check if p is a subset, simply check if ( p & X ) == p.
Sorry, i did some typo lmao.

What should be the output for this case?
1
5
1 2 4 8 16

According to me it should be,
3
since,

()
(1,2)
(1,4)
are the possibillities. Am I correct?

Count subsets with only one element also.

1 Like