板子4

主席树(求区间第K小)

int cnt, T[maxn];
struct Tree{
    int l, r, val;
} tre[maxn*40];

void init() {
    cnt = 0;
}

int build(int l, int r) {
    int nod = ++cnt;
    tre[nod].val = 0;
    if (l == r) return nod;
    int m = l + r >> 1;
    tre[nod].l = build(l, m);
    tre[nod].r = build(m+1, r);
    return nod;
}

int update(int pre, int l, int r, int val) {
    int nod = ++cnt;
    tre[nod] = tre[pre]; tre[nod].val++;
    if (l == r) return nod;
    int m = l + r >> 1;
    if (val <= m) tre[nod].l = update(tre[pre].l, l, m, val);
    else tre[nod].r = update(tre[pre].r, m+1, r, val);
    return nod;
}

int query(int v, int u, int l, int r, int k) {
    if (l == r) return l;
    int m = l + r >> 1;
    int num = tre[tre[u].l].val - tre[tre[v].l].val;    //左边的数多少
    if (num >= k) return query(tre[v].l, tre[u].l, l, m, k);
    else return query(tre[v].r, tre[u].r, m + 1, r, k - num);
}

int a[maxn], n, m, b[maxn];

void solve() {
    while (cin >> n >> m) {
        init();
        for (int i = 1; i <= n; i++) {
            scanf("%d", a + i);
            b[i] = a[i];
        }
        sort(b+1, b+1+n);
        int xx = unique(b+1, b+1+n) - (b + 1);  //离散
        T[0] = build(1, xx);    //根节点
        for (int i = 1; i <= n; i++) {
            int h = lower_bound(b+1, b+1+xx, a[i]) - (b + 1);
            T[i] = update(T[i-1], 1, xx, h+1);
        }
        while (m--) {
            int l, r, k; scanf("%d%d%d", &l, &r, &k);
            printf("%d\n", b[query(T[l-1], T[r], 1, xx, k)]);
        }
    }
}

  后缀数组(求重复子串的个数)

 

char s[maxn];
int sa[maxn], t[maxn], t2[maxn], tt[maxn], c[maxn], ss[maxn];
int Rank[maxn], height[maxn];

bool cmp(int *r, int a, int b, int l) {
    return r[a] == r[b] && r[a+l] == r[b+l];
}
// 名次数组:名次数组 Rank[i]保存的是 Suffix(i)在所有后缀中从小到大排列的“名次”。
/*后缀数组:后缀数组 SA 是一个一维数组,它保存 1..n 的某个排列 SA[1],
SA[2],......,SA[n],并且保证 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。
也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺
次放入 SA 中。*/
//SA数组最后1-len代表的是所有的下标、SA[0]是添加的末尾
//height是1-len代表 定义 height[i]=suffix(sa[i-1])和 suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。
//一定要记住下标为0 的是添加的0,height和SA都是
void build_sa(int *r, int n, int m) {
    int *x = t, *y = t2;
    for (int i = 0; i < m; i++) c[i] = 0;
    for (int i = 0; i < n; i++) c[x[i] = r[i]]++;
    for (int i = 1; i < m; i++) c[i] += c[i-1];
    for (int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
    for (int j = 1; ; j <<= 1) {
        int p = 0;
        for (int i = n - j; i < n; i++) y[p++] = i;
        for (int i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i] - j;
        for (int i = 0; i < n; i++) tt[i] = x[y[i]];
        for (int i = 0; i < m; i++) c[i] = 0;
        for (int i = 0; i < n; i++) c[tt[i]]++;
        for (int i = 1; i < m; i++) c[i] += c[i-1];
        for (int i = n - 1; i >= 0; i--) sa[--c[tt[i]]] = y[i];
        swap(x, y), p = 1, x[sa[0]] = 0;
        for (int i = 1; i < n; i++)
            x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p - 1 : p++;
        if (p >= n) break;
        m = p;
    }
}

void getHeight(int *r, int n) {
    int k = 0;
    for (int i = 1; i <= n; i++) Rank[sa[i]] = i;
    for (int i = 0; i < n; i++) {
        if (k) k--;
        int j = sa[Rank[i] - 1];
        while (r[i+k] == r[j+k]) k++;
        height[Rank[i]] = k;
    }
}

void solve() {
    scanf("%s", s);
    int len = strlen(s);
    for (int i = 0; i < len; i++)
        ss[i] = s[i];
    ss[len] = 0; //使用ss代替s,并且在最后添加一个0构成  s + '0'
    build_sa(ss, len+1, 256);
    getHeight(ss, len);
    int ans = 0;
    for (int i = 1; i <= len; i++) {
        ans += height[i] > height[i-1] ? height[i] - height[i-1] : 0;
    }
    cout << ans << endl;
}

  树的点分治

//树的点分治,该代码是求树上有多少对点的dis<=k
//vis代表该点是否已经当做过重心,siz是子树节点个数、mv是子树中最大的节点数
int n, cnt, head[maxn], k, vis[maxn], root, maxx, dis[maxn];
int ans, num, tot, siz[maxn], mv[maxn]; //tot代表当前树的节点个数、如果遇到时间过长,考虑重心是否找对
struct Edge {
    int v, w, next;
} e[maxn<<1];

void init() {
    ans = cnt = 0;
    memset(vis, 0, sizeof(vis));
    memset(head, -1, sizeof(head));
}

void add(int u, int v, int w) {
    e[cnt].v = v;
    e[cnt].w = w;
    e[cnt].next = head[u];
    head[u] = cnt++;
}

void getroot(int u, int fa) {
    siz[u] = 1, mv[u] = 0;
    for (int i = head[u]; ~i; i = e[i].next) {
        int v = e[i].v;
        if (v == fa || vis[v]) continue;
        getroot(v, u);
        siz[u] += siz[v];
        mv[u] = max(mv[u], siz[v]);
    }
    mv[u] = max(mv[u], tot - siz[u]);
    if (mv[u] < mv[root])
        root = u;
}

void getdis(int u, int fa, int dep) {
    dis[num++] = dep;
    for (int i = head[u]; ~i; i = e[i].next) {
        int v = e[i].v;
        if (v == fa || vis[v]) continue;
        getdis(v, u, dep + e[i].w);
    }
}

int cal(int u, int f) {
    int res = 0;
    num = 0;
    getdis(u, -1, f);
    sort(dis, dis + num);
    int r = num - 1;
    for (int l = 0; l < r; l++) {
        while (dis[l] + dis[r] > k && l < r)
            r--;
        res += r - l;
    }
    return res;
}

void work(int u) {
    vis[u] = 1;
    ans += cal(u, 0);
    for (int i = head[root]; ~i; i = e[i].next) {
        int v = e[i].v;
        if (vis[v]) continue;
        ans -= cal(v, e[i].w);
        mv[root=0] = tot = siz[v];
        getroot(v, -1);
        work(root);
    }
}

void solve() {
    while (cin >> n >> k) {
        if (!n && !k) break;
        init();
        for (int i = 1; i < n; i++) {
            int u, v, w; scanf("%d%d%d", &u, &v, &w);
            add(u, v, w); add(v, u, w);
        }
        mv[root=0] = tot = n;
        getroot(1, -1);
        work(root);
        cout << ans << endl;
    }
}

  

无源汇有上下界的网络流

将原弧(u,v)分离出一条必要弧和一条非必要弧: 假设B(u,v)是下界,则分离出两条弧: C1(u,v) = B(u,v) -- 必要弧 C2(u,v) = C(u,v) – B(u,v)

添加附加点x,y。想像一条不限上界的(x, y),用必 要弧将它们“串”起来,即对于有向必要弧(u, v),添加 (u, x),(y, v),容量为必要弧容量。这样就建立了一个等 价的网络。

即建图为:

AddEdge(u[i], v[i], f[i] - l[i]); //l[i]代表下界,f[i]代表上界
AddEdge(s, v[i], l[i]);
AddEdge(u[i], t, l[i]);

若此图上的最大流能够占满与Y相连的所有边的容量(自 然也就会占满所有连到x的边的容量),那么原图上就存 在满足上下界条件的可行流。若最大流不能够占满与Y相 连的所有边的容量,则原图不存在可行流。

动态节点的线段树

int T[maxn], L[maxn], R[maxn], val[maxn], tot, flag;

void update(int &id, int l, int r, int pos, int x) {
    if (!id) {
        id = ++tot;
        val[id] = x;
    }
    if (val[id] > x) val[id] = x;
    if (l == r) return;
    int mid = l + r >> 1;``
    if (pos <= mid) update(L[id], l, mid, pos, x);
    else update(R[id], mid + 1, r, pos, x);
}

void query(int id, int l, int r, int ql, int qr, int x) {
    if (!id || flag) return;
    if (ql <= l && qr >= r) {
        if (val[id] <= x) flag = 1;
        return;
    }
    int mid = l + r >> 1;
    if (ql <= mid) query(L[id], l, mid, ql, qr, x);
    if (qr > mid) query(R[id], mid + 1, r, ql, qr, x);
}

void init() {
    tot = 0;
    memset(T, 0, sizeof(T));
    memset(L, 0, sizeof(L));
    memset(R, 0, sizeof(R));
}

  最小费用最大流

int cnt;
int n, m, s, t;
bool vis[maxn];
ll d[maxn], a[maxn]; //a -> 当前可以的流量  d -> 花费,类似距离
int cur[maxn], pre[maxn]; //pre -> 上一条弧
struct Edge {
    int u, v;
    ll cap, flow;
    ll cost;
} e[maxn*16]; //因为是双向边  所以记得开二倍

vector<int> G[maxn];

void init(int tot) {
    for (int i = 0; i <= tot; i++) //一定不要对vector使用memset!!!!!
        G[i].clear();
    cnt = 0;
}

void add(int u, int v, ll cap, ll f, ll cost) {
    e[cnt].u = u;
    e[cnt].cap = cap;
    e[cnt].flow = f;
    e[cnt].v = v;
    e[cnt].cost = cost;
}

void AddEdge(int u, int v, ll cap, ll cost) {
    add(u, v, cap, 0, cost);
    G[u].push_back(cnt++);
    add(v, u, 0, 0, -cost);
    G[v].push_back(cnt++);
}

bool SPFA(ll& flow, ll& cost, int tot) {
    for (int i = 0; i <= tot; i++) d[i] = INF;
    memset(vis, 0, sizeof(vis));
    d[s] = 0, vis[s] = 1, pre[s] = 0, a[s] = INF;
    queue<int> q;
    q.push(s);
    while (q.size()) {
        int u = q.front(); q.pop();
        vis[u] = 0;
        for (int i = 0; i < G[u].size(); i++) {
            Edge& v = e[G[u][i]];
            if (v.cap > v.flow && d[v.v] > d[u] + v.cost) {
                d[v.v] = d[u] + v.cost;
                pre[v.v] = G[u][i];
                a[v.v] = min(a[u], v.cap - v.flow);
                if (!vis[v.v]) {
                    q.push(v.v);
                    vis[v.v] = 1;
                }
            }
        }
    }
    if (d[t] == INF) return false;
    flow += a[t];
    cost += d[t] * a[t];
    for (int u = t; u != s; u = e[pre[u]].u) {
        e[pre[u]].flow += a[t];
        e[pre[u]^1].flow -= a[t];
    }
    return true;
}

ll MincostMaxflow(ll& cost, int tot) {
    ll flow = 0; cost = 0;
    while (SPFA(flow, cost, tot));
    return flow;
}

  区间合并线段树(入门题的, 带修改的最长连续1串的长度)

struct Tree {
    int l, r, ls, rs, val; //ls -> 代表左边连续的1, rs代表右边连续的1, val代表当前区间最长的连续1
} tre[maxn<<2];

void build(int id, int l, int r) {
    tre[id].l = l, tre[id].r = r, tre[id].val = tre[id].ls = tre[id].rs = 1;
    if (l == r) return;
    int mid = l + r >> 1;
    build(id<<1, l, mid);
    build(id<<1|1, mid + 1, r);
    tre[id].ls = tre[id].rs = tre[id].val = tre[id<<1].val + tre[id<<1|1].val;
}

void update(int id, int pos, int f) {
    if (tre[id].l == tre[id].r) {
        tre[id].val = tre[id].ls = tre[id].rs = f;
        return;
    }
    int mid = tre[id].l + tre[id].r >> 1;
    if (mid >= pos) update(id<<1, pos, f);
    else update(id<<1|1, pos, f);
    tre[id].ls = tre[id<<1].ls;
    if (tre[id<<1].ls == tre[id<<1].r - tre[id<<1].l + 1) tre[id].ls += tre[id<<1|1].ls;    //如果左子节点全部都是连续的1
    tre[id].rs = tre[id<<1|1].rs;
    if (tre[id<<1|1].rs == tre[id<<1|1].r - tre[id<<1|1].l + 1) tre[id].rs += tre[id<<1].rs; //如果右子节点全部是连续的1
    tre[id].val = max(tre[id<<1].rs + tre[id<<1|1].ls, max(tre[id<<1].val, tre[id<<1|1].val));
}

int query(int id, int pos) {
    if (tre[id].l == tre[id].r) return tre[id].val;
    int mid = tre[id].l + tre[id].r >> 1;
    if (pos <= mid) {
        if (pos >= tre[id<<1].r - tre[id<<1].rs + 1) return tre[id<<1|1].ls + tre[id<<1].rs;  //如果当前位置所在的位置是在左子节点最右边的连续(1)段中
        return query(id<<1, pos);
    }
    else {
        if (pos <= tre[id<<1|1].l + tre[id<<1|1].ls - 1) return tre[id<<1].rs + tre[id<<1|1].ls; //类似上面的
        return query(id<<1|1, pos);
    }
}

  数位DP

ll dp[40][40][40];
int a[40];
//数位DP最主要的是记忆化! 比如当前的状态只要前面够成过就好啦,比如当前题, 对于当前位置来说,只要当前的1的个数和2的个数是出现过的,那么就代表
//当前状态是出现过的,就可以直接记录啦!
ll dfs(int pos, int z, int y, bool limit, bool lead) {  //z和y分别代表0和1的个数
    if (pos == -1) {
        return z >= y; //枚举到结尾看是否合法, 二进制中1的个数大于2的个数
    }
    if (!limit && dp[pos][z][y] != -1) return dp[pos][z][y];
    int up = limit ? a[pos] : 1;
    ll ans = 0;
    for (int i = 0; i <= up; i++) {
        int z_n = z;
        if (!lead && !i) z_n++;
        ans += dfs(pos - 1, z_n, y + (i == 1), limit & i == a[pos], lead & (i == 0));
    }
    if (!limit) dp[pos][z][y] = ans;
    return ans;
}

ll gao(ll x) {
    int pos = 0;
    while (x) {
        a[pos++] = x & 1;
        x >>= 1;
    }
    return dfs(pos - 1, 0, 0, true, true);
}

void solve() {
    ll l, r;

    memset(dp, -1, sizeof(dp));
    while (cin >> l >> r) {
        cout << gao(r) - gao(l - 1) << endl;
    }
}

  二分图的最小点权覆盖:

将原图的边权赋为INF,然后增加超级源汇点,S连左半图,左半图连T,权值为点上的值

      二分图的最大点权独立集

  所有节点的值减去二分图的最小点权覆盖

最大权闭合图

在原图点集的基础上增加源 和汇 s t ;将原图每条有向边的容量替换为INF,然后s连所有权值为正的点,边的权值为点的权值,所有权值为负的点连t,权值为点权值的绝对值,最后所有权值为正的和减去最小割即为所求

最大密度子图

  转化图:

  

  然后就转化成了求最大权闭合图   h ( g ) = max ⎨ ∑ 1 ⋅ x e − ∑ g ⋅ x v ⎬ 其中g是答案  二分g, 如果h(g) <= 0 (其实是 == 0, 因为最大权闭合图最小为0?) 说明g偏大 r = mid;  否则 l = mid;

double的Dinic模板, 求最大密度子图(本题是选取一些数,使得逆序数 / 选取的数的个数 最大 注意比较大小都要有sgn!  不然各种错误!)

const db eps = 1e-7;
int head[maxn], cnt;
int n, m, s, t;
bool vis[maxn];
int d[maxn], cur[maxn];
struct Edge {
    int u, v;
    db cap, flow;
} e[maxn*40]; //因为是双向边  所以记得开二倍

vector<int> G[maxn];
int sgn(db x) {
    return x < -eps ? -1 : x < eps ? 0 : 1;
}

void init() {
    memset(head, -1, sizeof(head));
    for (int i = 0; i < maxn; i++) //一定不要对vector使用memset!!!!!
        G[i].clear();
    cnt = 0;
}

void add(int u, int v, db cap, db f) {
    e[cnt].u = u;
    e[cnt].cap = cap;
    e[cnt].flow = f;
    e[cnt].v = v;
}

void AddEdge(int u, int v, db cap) {
    add(u, v, cap, 0);
    G[u].push_back(cnt++);
    add(v, u, 0, 0);
    G[v].push_back(cnt++);
}

bool BFS() {
    memset(vis, 0, sizeof(vis));
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    d[s] = 0;
    while (!q.empty()) {
        int v = q.front(); q.pop();
        for (int i = 0; i < G[v].size(); i++) {
            Edge &te = e[G[v][i]];
            if (!vis[te.v] && sgn(te.cap - te.flow) > 0) { //只考虑残量网络的弧
                vis[te.v] = 1;
                d[te.v] = d[v] + 1;
                q.push(te.v);
            }
        }
    }
    return vis[t];
}

db dfs(int x, db a) {
    if (x == t || sgn(a) == 0) return a;
    db flow = 0, f;
    for (int &i = cur[x]; i < G[x].size(); i++) { //从上次考虑的弧
        Edge &te = e[G[x][i]];
        if (d[x] + 1 == d[te.v] && sgn(f = dfs(te.v, min(a, te.cap - te.flow))) > 0) {
            te.flow += f;
            e[G[x][i]^1].flow -= f;
            flow += f;
            a -= f;
            if (sgn(a) == 0) break;
        }
    }
    return flow;
}

db Dinic() {
    db flow = 0;
    while (BFS()) {
        memset(cur, 0, sizeof(cur));
        flow += dfs(s, INF);
    }
    return flow;
}
int a[maxn];

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    db l = 0, r = n * n;
    while (sgn(l - r) < 0) {
        db g = (l + r) / 2.0;
        db num = 0; init();
        s = 0, t = maxn - 1;
        int cnt = n;
        for (int i = 1; i <= n; i++) {
            AddEdge(i, t, g);
            for (int j = 1; j < i; j++) {
                if (a[j] > a[i]) {
                    num++; cnt++;
                    AddEdge(s, cnt, 1);
                    AddEdge(cnt, i, INF);
                    AddEdge(cnt, j, INF);
                }
            }
        }
        db tmp = Dinic();
        if (sgn(num - tmp) <= 0) r = g;
        else l = g;
    }
    printf("%.10f\n", l);
}

 

 

二进制的高维前缀:

 

    for (int i = 0; i < k; i++) {
        for (int j = 0; j < (1 << k); j++)
            if (!((1 << i) & j)) dp[j] += dp[j | (1 << i)]; // 当前的表示包括本身的超集的值的和
            //比如dp[100] = dp[100] + dp[101] + dp[110] + dp[111];
    }

  AC自动机(当前题是取一个最长的串,看其他串是否都为最长串的子串)

int ch[maxnNode][26], cnt;
int val[maxnNode], f[maxnNode], last[maxn];

struct Ac {
    int sz;
    Ac() {
        sz = 1; memset(val, 0, sizeof(val));
        memset(ch[0], 0, sizeof(ch[0]));
    }
    int idx(char c) { return c - 'a'; }
    void Insert(char *s, int v) {
        int u = 0, n = strlen(s);
        for (int i = 0; i < n; i++) {
            int c = idx(s[i]);
            if (!ch[u][c]) {
                memset(ch[sz], 0, sizeof(ch[sz]));
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] += v;
        //字符串的字符的附加信息
    }
    void getFaile() {
        queue<int> q;
        f[0] = 0;
        for (int c = 0; c < 26; c++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0; q.push(u);
                last[u] = 0;
            }
        }
        while (!q.empty()) {
            int r = q.front(); q.pop();
            for (int c = 0; c < 26; c++) {
                int u = ch[r][c];
                if (!u) continue;
                q.push(u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void cal(int j) {
        while (j) {
            cnt += val[j];
            val[j]=0;
            j = last[j];
        }
    }
    void Find(char *T) {
        int n = strlen(T);
        int j = 0;
        for (int i = 0; i < n; i++) {
            int c = idx(T[i]);
            while (j && !ch[j][c]) j = f[j];
            j = ch[j][c];
            if (val[j]) cal(j);
            else if (last[j]) cal(last[j]);
        }
    }
};
char s[maxn];

void solve() {
    int n; cin >> n;
    Ac ac;
    int mlen = 0, k, clen = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%s", s + clen);
        ac.Insert(s + clen, 1);
        int len = strlen(s + clen);
        if (len > mlen) {
            mlen = len;
            k = clen;
        }
        clen += len;
    }
    ac.getFaile();
    s[k + mlen] = 0;
    cnt = 0;
    ac.Find(s + k);
    if (cnt == n) puts(s + k);
    else cout << "No\n";
}

  二进制子集的前缀:

   

for (int j = 0; j < m; j++)
            for (int k = 0; k < (1 << m); k++)
                if (((1 << j) & k)) dp[i-1][k] = (dp[i-1][k] + dp[i-1][k ^ (1 << j)]) % mod;
        

  

posted @ 2017-08-05 15:59  ost_xg  阅读(259)  评论(0)    收藏  举报