Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Computing Maximum Cliques and Cover Strings via Graph Algorithms and Data Structures

Tech 1

This analysis covers problems involving interval graphs on cycles and string covering patterns.

Maximum Clique in a Circular Interval Graph Given intervals placed on a circle, where an edge connects two intervals if they intersect, the objective is to find the largest clique. For intervals on a line, the maximum clique size equals highest point coverage count. Breaking the circle at a chosen point transforms the problem. Let intervals crossing this breakpoint be set A, and others be set B. Within set B, any maximum clique must share a common intersection point. The problem reduces to selecting a common point for set B intervals while including all compatible intervals from set A. This is a bipartite maximum independent set problem, where edges represent conflicts defined by 2D partial order. A greedy flow algorithm resolves this in O(n log n). The total complexity is O(n² log n).

#include <bits/stdc++.h>
using namespace std;
struct Interval { int l, r; };
vector<Interval> cross, noncross;
vector<int> add[1000001], rem[1000001];
bool active[2001];
int main() {
    ios::sync_with_stdio(false);
    int tests; cin >> tests;
    while(tests--) {
        int n; cin >> n;
        cross.clear(); noncross.clear();
        for(int i = 0; i < n; i++) {
            int a, b; cin >> a >> b;
            if(b == 1000000) cross.push_back({0, a});
            else if(a > b) cross.push_back({b, a});
            else noncross.push_back({a, b});
        }
        sort(cross.begin(), cross.end(), [](Interval x, Interval y){ return x.l < y.l; });
        sort(noncross.begin(), noncross.end(), [](Interval x, Interval y){ return x.l < y.l; });
        for(auto &seg : noncross) {
            add[seg.l].push_back(&seg - &noncross[0]);
            rem[seg.r + 1].push_back(&seg - &noncross[0]);
        }
        int result = 0, cnt = 0;
        for(int pos = 1; pos <= 1000000; pos++) {
            if(add[pos].empty() && rem[pos].empty()) continue;
            for(int idx : add[pos]) active[idx] = true, cnt++;
            for(int idx : rem[pos]) active[idx] = false, cnt--;
            multiset<int> endpoints;
            int conflict = 0, ptr = 0;
            for(int i = 0; i < (int)noncross.size(); i++) if(active[i]) {
                while(ptr < (int)cross.size() && cross[ptr].l < noncross[i].l)
                    endpoints.insert(cross[ptr++].r);
                auto it = endpoints.upper_bound(noncross[i].r);
                if(it != endpoints.end()) conflict++, endpoints.erase(it);
            }
            result = max(result, cnt - conflict);
        }
        for(int i = 1; i <= 1000000; i++) add[i].clear(), rem[i].clear();
        cout << result + (int)cross.size() << '\n';
    }
    return 0;
}

Finding Cover Strings for Prefixes For a string s, a cover string t must appear such that every position in s is within at least one occurrence of t. The goal is to compute, for each prefix, the length of its shortest cover, with results XORed and processed online. Every cover of s is also a border of s. The set of covers forms a tree that is a subtree of the border tree. To find the maximal cover for each prefix, traverse the border tree. A key observation: any border longer than half the string length is a cover. This means on the border tree, only O(log n) edges may connect nodes where the border is not a cover. Using Link-Cut Trees (LCT) to track the last occurrence position of each border, one can binary search along chains of borders that are covers. This yields an O(n log² n) solution. An optimized approach leveraging the fact that large borders form arithmetic sequences (and thus chains in the tree) can achieve O(n log n) via tree decomposition without LCT.

#include <bits/stdc++.h>
struct LCTNode {
    int ch[2], par;
    bool isRoot(int x) { return ch[par][0]!=x && ch[par][1]!=x; }
    bool side(int x) { return ch[par][1]==x; }
    void link(int p, int c, bool d) { ch[p][d]=c; if(c) par=c; }
    void rotate(int x) {
        int p=par, g=par[p], d=side(x);
        if(!isRoot(p)) ch[g][side(p)]=x;
        par=x=g; link(p,ch[x][!d],d); link(x,p,!d);
    }
    void splay(int x) {
        while(!isRoot(x)) {
            if(!isRoot(par)) rotate(side(x)^side(par)?x:par);
            rotate(x);
        }
    }
    int access(int x) {
        int last=0;
        for(int y=x; y; y=par[last=y]) splay(y), ch[y][1]=last;
        return last;
    }
    void attach(int p, int c) {
        int r=access(p);
        while(ch[r][1]) r=ch[r][1];
        link(r,c,1); splay(c);
    }
    int furthest(int x) {
        splay(x);
        while(ch[x][1]) x=ch[x][1];
        splay(x);
        return x;
    }
} lct;
int main() {
    int n, mode; scanf("%d%d",&n,&mode);
    char str[n+2]; scanf("%s",str+1);
    int border[n+1]={0}, cover[n+1]={0}, chainTop[n+1]={0};
    int jump[n+1][20]={0}, prefixAns[n+1]={0};
    long long totalXor=0;
    auto validCover = [&](int i, int len) -> bool {
        return !len || (i - lct.furthest(len) <= len);
    };
    for(int i=1, j=0; i<=n; ++i) {
        if(mode) str[i] = (str[i] + prefixAns[i-1]) % 26 + 'a';
        if(i>1) {
            while(j && str[j+1]!=str[i]) j=border[j];
            if(str[j+1]==str[i]) ++j;
        }
        border[i]=jump[i][0]=j;
        for(int b=1; b<20; ++b) jump[i][b]=jump[jump[i][b-1]][b-1];
        int cur=border[i];
        while(chainTop[cur] && !validCover(i, chainTop[cur]))
            cur=border[chainTop[cur]];
        if(cur) {
            if(validCover(i,cur)) cover[i]=cur;
            else {
                for(int b=19; b>=0; --b)
                    if(jump[cur][b]>chainTop[cur] && !validCover(i, jump[cur][b]))
                        cur=jump[cur][b];
                cover[i]=border[cur];
            }
        }
        if(border[i]==cover[i] && cover[i]) chainTop[i]=chainTop[cover[i]];
        else chainTop[i]=i;
        prefixAns[i]=cover[i] ^ prefixAns[cover[i]];
        if(cover[i]) lct.attach(cover[i], i);
        totalXor += prefixAns[i];
    }
    printf("%lld\n", totalXor);
    return 0;
}

Selecting Paths for Maximum Common Overlap Given a weighted tree and a set of paths, select k paths to maximize the length of their common intersection. Consider a candidate node x as the deepest endpoint of the intersection. Paths with exactly one endpoint in x's subtree are relevant. Adding +1 to nodes on such paths and finding the farthest node from x with count ≥ k yields a candidate answer. Using DSU on tree, maintain a Fenwick tree per heavy path to support range updates. Two monotonic regions exist: ancestors of x have non-decreasing counts upwards, and descendants have non-decreasing counts downwards. Binary search on these segments using the Fenwick trees finds the farthest suitable node. Complexity is O(n log³ n).

#include <bits/stdc++.h>
using ll = long long;
std::vector<int> adj[200001], pathList[200001];
int parent[200001], depth[200001], heavy[200001], head[200001];
int tin[200001], tout[200001], timer=0;
ll dist[200001];
int pathU[230001], pathV[230001], pathLCA[230001];
struct BIT {
    std::vector<int> data; int sz;
    void init(int n) { data.assign(sz=n+1,0); }
    void update(int i, int d) { for(; i<=sz; i+=i&-i) data[i]+=d; }
    int query(int i) { int s=0; for(; i>0; i-=i&-i) s+=data[i]; return s; }
    int findKth(int k) {
        int idx=0, bit=1<<(31-__builtin_clz(sz));
        for(; bit; bit>>=1) if(idx+bit<=sz && data[idx+bit]<k) k-=data[idx+=bit];
        return idx+1;
    }
} bit[200001];
int lca(int u, int v) {
    while(head[u]!=head[v]) {
        if(depth[head[u]] < depth[head[v]]) std::swap(u,v);
        u = parent[head[u]];
    }
    return depth[u] < depth[v] ? u : v;
}
void dfsHeavy(int u, int p) {
    parent[u]=p; heavy[u]=-1; int maxSub=0;
    for(int v : adj[u]) if(v!=p) {
        depth[v]=depth[u]+1;
        dfsHeavy(v, u);
        if(subtreeSize[v] > maxSub) maxSub=subtreeSize[v], heavy[u]=v;
    }
}
void dfsDecompose(int u, int h) {
    head[u]=h; tin[u]=++timer;
    if(heavy[u] != -1) dfsDecompose(heavy[u], h);
    for(int v : adj[u]) if(v!=parent[u] && v!=heavy[u]) dfsDecompose(v, v);
    tout[u]=timer;
}
ll bestLen=0; int bestU, bestV;
void updateBest(int u, int v, int w) {
    ll d = dist[u]+dist[v]-2*dist[w];
    if(d > bestLen || (d==bestLen && (u<bestU || (u==bestU && v<bestV))))
        bestLen=d, bestU=u, bestV=v;
}
int activePathEnd[230001];
std::vector<int> modifiedChains;
void modifyChain(int x, int y, int delta) {
    while(true) {
        int h = head[x];
        int pos = depth[x] - depth[h];
        bit[h].update(pos+1, delta);
        if(h == head[y]) {
            bit[h].update(depth[y]-depth[h]+2, -delta);
            break;
        }
        x = parent[h];
    }
}
void processSubtree(int u, bool clear) {
    for(int v : adj[u]) if(v!=parent[u] && v!=heavy[u]) processSubtree(v, true);
    if(heavy[u]!=-1) processSubtree(heavy[u], false);
    for(int v : adj[u]) if(v!=parent[u] && v!=heavy[u]) {
        for(int pid : pathList[v]) {
            int other = pathU[pid] ^ pathV[pid] ^ v;
            if(activePathEnd[pid]) modifyChain(activePathEnd[pid], pathLCA[pid], -1);
            modifyChain(activePathEnd[pid]=other, pathLCA[pid], 1);
        }
    }
    for(int pid : pathList[u]) {
        int other = pathU[pid] ^ pathV[pid] ^ u;
        if(activePathEnd[pid]) modifyChain(activePathEnd[pid], pathLCA[pid], -1);
        modifyChain(activePathEnd[pid]=other, pathLCA[pid], 1);
    }
    int candidate = u;
    for(int b=17; b>=0; --b) {
        int anc = parent[candidate];
        if(anc && bit[head[anc]].query(depth[anc]-depth[head[anc]]+1) >= k)
            candidate = anc;
    }
    if(candidate != u) updateBest(u, candidate, candidate);
    if(clear) {
        for(int pid : pathList[u]) {
            if(activePathEnd[pid]) {
                modifyChain(activePathEnd[pid], pathLCA[pid], -1);
                activePathEnd[pid]=0;
            }
        }
    }
}
int main() {
    int n, m; scanf("%d%d%d",&n,&m,&k);
    for(int i=1; i<n; i++) {
        int a,b,w; scanf("%d%d%d",&a,&b,&w);
        adj[a].push_back(b); adj[b].push_back(a);
    }
    dfsHeavy(1,0); dfsDecompose(1,1);
    for(int i=1; i<=n; i++) if(head[i]==i) bit[i].init(depth[i]-depth[head[i]]+1);
    for(int i=1; i<=m; i++) {
        scanf("%d%d",&pathU[i],&pathV[i]);
        pathLCA[i] = lca(pathU[i], pathV[i]);
        pathList[pathU[i]].push_back(i);
        pathList[pathV[i]].push_back(i);
    }
    processSubtree(1, true);
    printf("%lld\n%d %d\n", bestLen, bestU, bestV);
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.