Implicit dijkstra: use the priority queue to find the first k small in the state set (to be continued)

This kind of technique is a long time ago, but I suddenly encountered a few new problems recently, so I summed up my experience.

It should be noted that the premise of this algorithm is that the size of the "state set" mentioned in the title is too large to be enumerable (otherwise, it can be directly enumerated qaq) , and k is generally below the order of magnitude of 1e6.

Prerequisites: Dijkstra's Algorithm, its idea and proof of correctness .
Portal 1: Proof of Ideas and Correctness
Portal 2: Priority Queue Optimization dijkstra

Let's look at a question first:

Given 1<m<10 integer sequences of length n<1e5, select a number from each sequence and add it, and find the largest k<1e5 in the resulting sum.

(The first obvious thing to do is to sort each sequence, from largest to smallest)

Consider the case of m=2:

For the binary answer A, for each element in sequence 1, find the set of elements that can be paired with it in sequence 2 so that the sum is above A. It is easy to know that this set is a prefix of sequence 2, and the length of the prefix increases with i, so it only takes linear time to find all such prefixes for an A. The number of solutions whose sum is greater than A is the sum of the lengths of these prefixes.

Above code:
#include <bits/stdc++.h>
using namespace std;
#define iinf 1000000000
#define linf 1000000000000000000LL
#define ulinf 10000000000000000000ull
#define MOD1 1000000007LL
#define mpr make_pair
typedef long long LL;
typedef unsigned long long ULL;
typedef unsigned long UL;
typedef unsigned short US;
typedef pair < int , int > pii;
clock_t __stt;
inline void TStart(){__stt=clock();}
inline void TReport(){printf("\nTaken Time : %.3lf sec\n",(double)(clock()-__stt)/CLOCKS_PER_SEC);}
template < typename T > T MIN(T a,T b){return a<b?a:b;}
template < typename T > T MAX(T a,T b){return a>b?a:b;}
template < typename T > T ABS(T a){return a>0?a:(-a);}
template < typename T > void UMIN(T &a,T b){if(b<a) a=b;}
template < typename T > void UMAX(T &a,T b){if(b>a) a=b;}
int n,m,k,a[2][100005];
bool check(int v){
    int i,j,t,cnt;
    for(t=0;t<n && a[0][t]+a[1][0]<v;++t);
    cnt=n-t;
    for(i=1;i<n;++i){
        for(;t<n && a[0][t]+a[1][i]<v;++t);
        cnt+=n-t;
        if(cnt>=k) return 1;
    }
    return cnt>=k;
}
int main(){
    // inputting start
    // 数据结构记得初始化! n,m别写反!
    scanf("%d%d%d",&n,&m,&k); //m=2
    int i,j;
    for(i=0;i<2;++i){
        for(j=0;j<n;++j){
            scanf("%d",&a[i][j]);
        }
    }
    #ifdef LOCAL
        TStart();
    #endif
    // calculation start
    // 数据结构记得初始化! n,m别写反!
    sort(a[0],a[0]+n);
    sort(a[1],a[1]+n);
    reverse(a[1],a[1]+n);
    int low=0,high=iinf,mid;
    while(low<high){
        mid=((low+high+1)>>1);
        if(check(mid))
            low=mid;
        else
            high=mid-1;
    }
    printf("%d\n",low);
    #ifdef LOCAL
        TReport();
    #endif
    return 0;
}

This approach is not the focus of this article, but a large number of problems that can be solved with implicit dijkstra, which will be introduced shortly, can be solved with this dichotomous approach with slightly worse performance.

Some problems seem to be able to use implicit dijkstra, but in fact there will be big problems. In this case, you can consider using this kind of binary answer instead.

The following question is recommended.

USACO 2016DEC PLATINUM T3
Problem Solution

For the general case (m<=10), it is necessary to change the method.

(Knock on the blackboard! Important point!)

We build a graph in our mind, and each node corresponds to a selection scheme. Scheme A to B has a directed edge, if and only if the corresponding sum of A is greater than the sum of B, the edge weight is the absolute value of the difference between the two sums.

Remember that the maximum number of all sequences constitutes the S state, then for any T state: cost(T)=cost(S)-dist(S,T)

Then what we require is the point with the kth shortest distance to the S state (including S itself). Note that although this is a DAG, it cannot be DP due to too many nodes.

So, consider dijkstra.

Conclusion first:

When dijkstra runs on a positive weight graph, the distance from the node popped by the priority queue to S must be increasing.

Prove:

If dist(S,P)>dist(S,Q) and P pops up before Q, then:

At the moment before P is popped, since P is the closest to S in the priority queue, Q to S is closer to S than any state in the priority queue, and Q is not in the priority queue before P is popped, and it has never been pushed into it. pass.

So Q cannot be obtained from the state in the priority queue through several relaxation operations.

Therefore, Q cannot be pushed into the queue after P is popped, and it is impossible to pop after P.

Contradictions arise, the proof is over.

inference:

For any k, the first k nodes popped up in the priority queue must be the k closest nodes to the source.

I will do it! Ah ha ha ha ha ha!
Run dijkstra once, the kth point popped up in the priority queue is the answer!

Wait a minute, count the complexity.

The average degree of each node is recorded as d, because k rounds of relaxation are done, and d new nodes are pressed in each round, so the total time complexity is:

O(kd*log(k)), approximately equal to 1e56. hmmmm...

(Knock on the blackboard! Here comes the point again!)

This algorithm, there are two main optimization ideas, the first one is used here:

Optimize the connection! Wow hahahahaha!

It is easy to know that for a fixed T, the sum of the weights of any ST path is all equal.

So, if some edges are removed so that the connectivity of the graph remains the same, the answer remains the same.

In other words, delete some edges so that S still has at least one path to each point.

So modify the edge connection strategy:

There is a directed edge from A to B, and only if the corresponding sum of A is greater than the sum of B, the edge weight is the absolute value of the difference between the two sums.

In addition, it must be satisfied that the corresponding schemes of A and B only have different subscripts of the numbers selected in one sequence (the ones selected in the other m-1 sequences are all the same), and the two selected numbers with different subscripts , must also be adjacent elements. (array is sorted)

To delete some edges so that S still has at least one path to each point.

Are you satisfied? Satisfied.

And now, d<=m. So the total complexity is reduced to O(mk*log(k)), bingo!

The specific implementation hint:

In fact, it is possible to enumerate all the neighbors of each point, so there is no need to build an adjacency list.

(I don't know the official name of this algorithm (maybe it doesn't exist?), so I'll call it "implicit dijkstra" in this article.)

Above code:
#include <bits/stdc++.h>
using namespace std;
#define iinf 1000000000
#define linf 1000000000000000000LL
#define ulinf 10000000000000000000ull
#define MOD1 1000000007LL
#define mpr make_pair
typedef long long LL;
typedef unsigned long long ULL;
typedef unsigned long UL;
typedef unsigned short US;
typedef pair < int , int > pii;
clock_t __stt;
inline void TStart(){__stt=clock();}
inline void TReport(){printf("\nTaken Time : %.3lf sec\n",(double)(clock()-__stt)/CLOCKS_PER_SEC);}
template < typename T > T MIN(T a,T b){return a<b?a:b;}
template < typename T > T MAX(T a,T b){return a>b?a:b;}
template < typename T > T ABS(T a){return a>0?a:(-a);}
template < typename T > void UMIN(T &a,T b){if(b<a) a=b;}
template < typename T > void UMAX(T &a,T b){if(b>a) a=b;}
int n,m,k,a[2][100005];
struct P{
    int x,y;
    int val(){return a[0][x]+a[1][y];}
    bool operator <(P b) const{
        return a[0][x]+a[1][y]<b.val();
    }
};
P make_P(int x,int y){
    P R;
    R.x=x;R.y=y;
    return R;
}
priority_queue < P > pq;
map < pii , int > vis;
int main(){
    // inputting start
    // 数据结构记得初始化! n,m别写反!
    scanf("%d%d%d",&n,&m,&k); //m=2
    int i,j;
    for(i=0;i<2;++i){
        for(j=0;j<n;++j){
            scanf("%d",&a[i][j]);
        }
    }
    #ifdef LOCAL
        TStart();
    #endif
    // calculation start
    // 数据结构记得初始化! n,m别写反!
    sort(a[0],a[0]+n);
    reverse(a[0],a[0]+n);
    sort(a[1],a[1]+n);
    reverse(a[1],a[1]+n);
    pq.push(make_P(0,0));
    while(!pq.empty()){
        P cur=pq.top();
        pq.pop();
        --k;
        if(!k){
            printf("%d\n",cur.val());
            return 0;
        }
        if(cur.x<n-1 && !vis[mpr(cur.x+1,cur.y)]){
            vis[mpr(cur.x+1,cur.y)]=1;
            pq.push(make_P(cur.x+1,cur.y));
        }
        if(cur.y<n-1 && !vis[mpr(cur.x,cur.y+1)]){
            vis[mpr(cur.x,cur.y+1)]=1;
            pq.push(make_P(cur.x,cur.y+1));
        }
    }
    #ifdef LOCAL
        TReport();
    #endif
    return 0;
}

Summary 1: Applicable conditions for implicit dijkstra

1. Reasonable data range

It should be noted that the premise of this algorithm is that the size of the "state set" mentioned in the title is too large to be enumerable (otherwise, it can be directly enumerated qaq), and k is generally below the order of magnitude of 1e6.

The "state set" here refers to the set of all nodes in the constructed graph.

2. State that is easy to represent and compare

Operations in the priority queue are based on comparisons.

If the complexity of the comparison size is too high it will be TLE, and if the space complexity of storing the state is too high it will be the MLE.

3, the right map

Edge weights must all be non-negative numbers.

Practical knowledge: SGU421

Title:

Given a sequence of integers (both positive and negative) of length n (<1e4), choose m (<13) numbers to multiply, and ask the k-th largest product.

answer:

First divide the sequence into two arrays according to positive and negative, and then sort them respectively.

Here are a few elements of implicit dijkstra:

Status meaning:

A state represents a selection scheme, a dyad of a subset of positive numbers and a subset of negative numbers.

The size relationship between states:

That is, the size relationship of the product. Compare the sign (parity of negative numbers) first, then the absolute value (high precision).

Starting state S:

Consider using multiple starting states. Each S is the largest of the states with a set of negative numbers of size x. x is any natural number.

Connection plan:

If only one selected number of states u and v is different, and these two different numbers are adjacent elements, then the edge is connected from the larger state to the smaller state, and the edge weight is the quotient of the division of the weight.

The answer is the value of the k-th state popped from the priority queue.

Proof of correctness:

Note that this graph is not weakly connected, but each weakly connected component (containing states with the same negative subset size) has exactly one starting state, so the correctness remains.

Above code:
#include <bits/stdc++.h>
using namespace std;
#define iinf 2000000000
#define linf 1000000000000000000LL
#define ulinf 10000000000000000000ull
#define MOD1 1000000007LL
#define mpr make_pair
typedef long long LL;
typedef unsigned long long ULL;
typedef unsigned long UL;
typedef unsigned int US;
typedef pair < int , int > pii;
clock_t __stt;
inline void TStart(){__stt=clock();}
inline void TReport(){printf("\nTaken Time : %.3lf sec\n",(double)(clock()-__stt)/CLOCKS_PER_SEC);}
template < typename T > T MIN(T a,T b){return a<b?a:b;}
template < typename T > T MAX(T a,T b){return a>b?a:b;}
template < typename T > T ABS(T a){return a>0?a:(-a);}
template < typename T > void UMIN(T &a,T b){if(b<a) a=b;}
template < typename T > void UMAX(T &a,T b){if(b>a) a=b;}
int n,m,k;
vector < int > neg,pos;
struct bigint{
    int len,cnt0;
    int d[85];
    void init(){
        memset(d,0,sizeof(d));
        len=1;
        d[0]=1;
        cnt0=0;
    }
    void reduct(){
        while(len>1 && !d[len-1]) --len;
    }
    void multiply(int v){
        if(!v){
            ++cnt0;
            return;
        }
        int i;
        for(i=0;i<len;++i) d[i]*=v;
        for(i=0;i<len;++i){
            d[i+1]+=d[i]/10;
            d[i]%=10;
        }
        while(d[len]){
            d[len+1]+=d[len]/10;
            d[len++]%=10;
        }
    }
    void divide(int v){
        if(!v){
            --cnt0;
            return;
        }
        int i,j,c=0;
        for(i=len-1;i>=0;--i){
            c=c*10+d[i];
            d[i]=0;
            if(c>=v){
                d[i]=c/v;
                c%=v;
            }
        }
        for(i=0;i<len;++i){
            d[i+1]+=d[i]/10;
            d[i]%=10;
        }
        while(d[len]){
            d[len+1]+=d[len]/10;
            d[len++]%=10;
        }
        reduct();
    }
    void print(bool sig){
        if(cnt0){
            printf("0\n");
            return;
        }
        int i;
        reduct();
        if(sig && !(len==1&&d[0]==0)) printf("-");
        for(i=len-1;i>=0;--i) printf("%d",d[i]);
        printf("\n");
    }
};
bool operator <(bigint &A,bigint &B){
    if(A.cnt0) return !B.cnt0;
    if(B.cnt0) return 0;
    if(A.len!=B.len) return A.len<B.len;
    int i;
    for(i=A.len-1;i>=0;--i){
        if(A.d[i]!=B.d[i]) return A.d[i]<B.d[i];
    }
    return 0;
}
struct state{
    int vp[15],vn[15],cp,cn;
    bigint val;
    state(){
        cp=cn=0;
        val.init();
    }
    bool sign(){
        return cn&1;
    }
    bool editp(int p,int d){
        if(vp[p]+d<0 || vp[p]+d>=(int)pos.size()) return 0;
        if((p && vp[p-1]==vp[p]+d)||(p<cp-1 && vp[p+1]==vp[p]+d)) return 0;
        val.divide(pos[vp[p]]);
        vp[p]+=d;
        val.multiply(pos[vp[p]]);
        return 1;
    }
    bool editn(int p,int d){
        if(vn[p]+d<0 || vn[p]+d>=(int)neg.size()) return 0;
        if((p && vn[p-1]==vn[p]+d)||(p<cn-1 && vn[p+1]==vn[p]+d)) return 0;
        val.divide(neg[vn[p]]);
        vn[p]+=d;
        val.multiply(neg[vn[p]]);
        return 1;
    }
    int super_cdd(){
        int ret=cp*101+cn,i;
        for(i=0;i<cp;++i){
            ret=ret*101+vp[i];
        }
        for(i=0;i<cn;++i){
            ret=ret*101+vn[i];
        }
        return ret;
    }
};
const bool operator <(state A,state B){
    if(A.sign()!=B.sign()) return A.sign()>B.sign();
    A.val.reduct();B.val.reduct();
    return (A.sign()?(B.val<A.val):(A.val<B.val));
}
priority_queue < state > pq;
map < int , bool > vis;
int main(){
    // inputting start
    // 数据结构记得初始化! n,m别写反!
    scanf("%d%d%d",&n,&m,&k);
    int i,j;
    for(i=0;i<n;++i){
        scanf("%d",&j);
        if(j<0){
            neg.push_back(-j);
        }
        else{
            pos.push_back(j);
        }
    }
    #ifdef LOCAL
        TStart();
    #endif
    // calculation start
    // 数据结构记得初始化! n,m别写反!
    sort(pos.begin(),pos.end());
    sort(neg.begin(),neg.end());
    for(i=0;i<=m;i+=2){
        state tmp;
        for(j=0;j<i && j<(int)neg.size();++j){
            tmp.vn[tmp.cn++]=(int)neg.size()-1-j;
            tmp.val.multiply(neg[(int)neg.size()-1-j]);
        }
        if(j>=i){
            for(j=0;j<m-i && j<(int)pos.size();++j){
                tmp.vp[tmp.cp++]=(int)pos.size()-1-j;
                tmp.val.multiply(pos[(int)pos.size()-1-j]);
            }
            if(j>=m-i){
                vis[tmp.super_cdd()]=1;
                pq.push(tmp);
            }
        }
    }
    for(i=1;i<=m;i+=2){
        state tmp;
        for(j=0;j<i && j<(int)neg.size();++j){
            tmp.vn[tmp.cn++]=j;
            tmp.val.multiply(neg[j]);
        }
        if(j>=i){
            for(j=0;j<m-i && j<(int)pos.size();++j){
                tmp.vp[tmp.cp++]=j;
                tmp.val.multiply(pos[j]);
            }
            if(j>=m-i){
                vis[tmp.super_cdd()]=1;
                pq.push(tmp);
            }
        }
    }
    while(k--){
        state cur=pq.top();
        pq.pop();
        if(!k){
            cur.val.print(cur.sign());
            break;
        }
        for(i=0;i<cur.cp;++i){
            if(cur.editp(i,(cur.sign()?1:-1))){
                if(!vis[cur.super_cdd()]){
                    vis[cur.super_cdd()]=1;
                    pq.push(cur);
                }
                cur.editp(i,(cur.sign()?-1:1));
            }
        }
        for(i=0;i<cur.cn;++i){
            if(cur.editn(i,(cur.sign()?1:-1))){
                if(!vis[cur.super_cdd()]){
                    vis[cur.super_cdd()]=1;
                    pq.push(cur);
                }
                cur.editn(i,(cur.sign()?-1:1));
            }
        }
    }
    #ifdef LOCAL
        TReport();
    #endif
    return 0;
}

Practice brings true knowledge 2: To be updated

This question is an idea I plan to come up with... Let me put it on oj and then talk about it qaq...

(Actually I think this may be the best question in this article qaq)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325395322&siteId=291194637