十二省联考 2019

由于比较懒,按难度顺序排序

 

D1T1

给一个序列,求前 $k$ 大区间异或和的和

$n \leq 500000,k \leq min(n^2,200000)$

sol:

超级钢琴

对每个 $i$,维护一个三元组 $(l,r,i)$ 表示左端点在 $[l,r]$,右端点在 $i$ 的区间异或最值,维护一个堆,按这个异或最值排序,每次将堆顶拿出来,分裂成最多两个区间,查一下异或最大值即可,区间异或最大值可以前缀和+可持久化 Trie 来解决

#include <bits/stdc++.h>
#define LL long long
#define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i)
#define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i)
using namespace std;
inline LL read() {
    LL x = 0, f = 1; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f;
    for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0';
    return x * f;
}
const int maxn = 5e5 + 10;
int n, k; LL a[maxn];
int root[maxn], ch[maxn << 6][2], idx[maxn << 6], v[maxn << 6], ToT;
void Insert(int &x, int pre, LL val, int LG, int id) {
    x = ++ToT; ch[x][0] = ch[pre][0]; ch[x][1] = ch[pre][1]; v[x] = v[pre] + 1; idx[x] = idx[pre];
    if(LG == -1) { idx[x] = id; return; }
    int px = (val >> LG) & 1; px = px ? 1 : 0;
    Insert(ch[x][px], ch[pre][px], val, LG - 1, id);
}
int query(int x, int pre, LL val, int LG) {
    if(LG == -1) return idx[x]; int px = (val >> LG) & 1; px = px ? 1 : 0;
    if(v[ch[x][!px]] - v[ch[pre][!px]] > 0)  return query(ch[x][!px], ch[pre][!px], val, LG - 1); 
    else return query(ch[x][px], ch[pre][px], val, LG - 1);
}
struct Node {
    int l, r, mxpos, i; LL ret;
    Node() {}
    Node(int _l, int _r, int _i) {
        l = _l, r = _r, i = _i;
        mxpos = query(root[r], root[l - 1], a[i], 31);
        ret = a[i] ^ a[mxpos - 1];
    }
    bool operator < (const Node &b) const { return ret < b.ret; }
}; priority_queue<Node> q;
int main() {
//    freopen("2.in","r",stdin);
    n = read(), k = read(); rep(i, 1, n) a[i] = a[i - 1] ^ read();
    rep(i, 1, n) Insert(root[i], root[i - 1], a[i - 1], 31, i);
    rep(i, 1, n) q.push(Node(1, i, i)); LL ans = 0;
    while(k--) {
        Node cur = q.top(); q.pop(); ans += cur.ret;
        if(cur.l < cur.mxpos) q.push(Node(cur.l, cur.mxpos - 1, cur.i));
        if(cur.r > cur.mxpos) q.push(Node(cur.mxpos + 1, cur.r, cur.i));
    } cout << ans << endl;
}
View Code

 

D2T2

一棵以 $1$ 号点为根的树,每个点有点权,两个点能放在一堆当且仅当这两个点没有祖先-后代关系,每堆的代价为堆内点权最大值,定义一个方案的代价为一些点组成的一些堆的权值和(如果放完之后还有单点,则这些单点每个算一堆),求所有方案中最小代价

$n \leq 2 \times 10^5$

sol:

考虑一条链($1$ 号点不一定为链头)的解决方法,显然每堆里有一个左边的点,一个右边的点,那就每次拿出最大的两个合并就可以了

考虑多条链,发现...没什么区别嘛

然后就过了

#include <bits/stdc++.h>
#define LL long long
#define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i)
#define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i)
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f;
    for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0';
    return x * f;
}
const int maxn = 200010;
int n, a[maxn], id[maxn], tmp[maxn], _tim;
LL ans;
priority_queue<int> q[maxn];
vector<int> G[maxn];
void dfs(int x, int fa) {
    id[x] = ++_tim;
    for(auto to : G[x]) {
        if(to == fa) continue;
        dfs(to, x);
        if(q[id[to]].size() > q[id[x]].size()) swap(id[x], id[to]);
        int k = q[id[to]].size();
        rep(i, 1, k) {
            tmp[i] = max(q[id[x]].top(), q[id[to]].top());
            q[id[x]].pop(); q[id[to]].pop();
        }
        rep(i, 1, k) q[id[x]].push(tmp[i]);
    } q[id[x]].push(a[x]);
}
int main() {
    n = read();
    rep(i, 1, n) a[i] = read();
    rep(i, 2, n) {
        int fa = read();
        G[fa].push_back(i);
    } dfs(1, 1);
    while(q[id[1]].size()) { ans += q[id[1]].top(); q[id[1]].pop(); }
    cout << ans << endl;
}
View Code

 

D1T2

给一个字符串,给 $n_a$ 个 $A$ 类子串,$n_b$ 个 $B$ 类子串,给出 $m$ 个 $A \rightarrow B$ 的有向边,存在 $A_i \rightarrow A_j$ 当且仅当存在 $B$ 使得 $A_i \rightarrow B$ 且 $B$ 是 $A_j$ 的前缀,求最长链,如果有环输出 $-1$

$|S|,n_a,n_b,m \leq 2 \times 10^5$

sol:

把串反过来,这样 $B$ 串就要向作为后缀包含它的所有 $A$ 串连边了

在 parent 树上倍增找到 $B$ ,则 $B$ 会向 $B$ 子树里的所有 $A$ 连边

前 $80$ 分($A$ 串都比 $B$ 串长)这么做是没问题的,对于 $100$ 分这么做有点问题

因为后缀自动机里是一些 $endpos$ 等价的节点,这些节点的长度不一定是对的,有可能一个点里存在 $B$ 串比 $A$ 串长,但在上面的做法中会把 $B->A$ 全都连上,众所周知后缀缀长度是要小于等于整个串的,于是就不对了

可以把边有序地连起来防止这种问题,具体大概有好多种方法,我想到的是比较简单的一种

一开始,不要连树边

观察到你跑的路径一定是 $A \rightarrow B \rightarrow 树 \rightarrow A$,又观察到,其实如果你的路径是 $A \rightarrow B \rightarrow B  \rightarrow  B  \rightarrow ...  \rightarrow B  \rightarrow 树  \rightarrow A $ 也无所谓,因为这么多 $B$ 可以用他们之中的任意一个来表示,而树也是没有意义的,因为你可以直接 $A  \rightarrow B  \rightarrow A$

由此可以知道 $B$ 和树本质是相同的,然后就可以大概想到以下建图:

你可以把一个点里的若干字符串按长度排个序,然后把树上的点当成 "第 $0$ 个 $B$ 节点,长度为 $0$,且这个点没有入边",这样你可以把所有 $B$ 按长度顺序从小到大用一条有向链穿起来,然后每个 $A$ 建一个从第一个小于等于它的 $B$ 连向它的边(可以是第 $0$ 个 $B$ ,即树,这里不会错是因为上树前至少经过了一个 $B$),然后为了保证这个图是连通的,我们把父亲的最后一个 $B$ 向儿子连边

这样跑最长路就是对的了,边数大概是 $O(6n)$

因为数组懒得动态清空,直接垫底

#include <bits/stdc++.h>
#define LL long long
#define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i)
#define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i)
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f;
    for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0';
    return x * f;
}
const int maxn = 2000010;
char s[maxn];
vector<int> G[maxn], lnk[maxn];
int ind[maxn], type[maxn], len[maxn], a[maxn], b[maxn], lst[maxn];
LL dp[maxn];
int n, m, na, nb, cnt;
int root, last, dfn;
int tr[maxn][26], fa[maxn], mxlen[maxn], pos[maxn], anc[23][maxn];
inline void extend(int c, int id) {
    int p = last, np = last = ++dfn;
    mxlen[np] = mxlen[p] + 1;
    for(; p && !tr[p][c]; p = fa[p]) tr[p][c] = np;
    if(!p) fa[np] = root;
    else {
        int q = tr[p][c];
        if(mxlen[q] == mxlen[p] + 1) fa[np] = q;
        else {
            int nq = ++dfn;
            mxlen[nq] = mxlen[p] + 1;
            fa[nq] = fa[q]; fa[q] = fa[np] = nq;
            memcpy(tr[nq], tr[q], sizeof(tr[nq]));
            for(; p && tr[p][c] == q; p = fa[p]) tr[p][c] = nq;
        }
    } pos[id] = np;
}
void init() {
    rep(i, 1, cnt) len[i] = 0, ind[i] = 0, type[i] = 0, dp[i] = 0;
    rep(i, 1, dfn) {
        mxlen[i] = 0, fa[i] = 0;
        memset(tr[i], 0, sizeof(tr[i]));
    }
    rep(i, 0, 22) memset(anc[i], 0, sizeof(anc[i]));
    root = last = dfn = 1; cnt = 0;
}
inline int kth(int x, int y) {
    dwn(i, 22, 0) if(mxlen[anc[i][x]] >= y) x = anc[i][x];
    return x;
}
void build() {
    cnt = dfn;
    //rep(i, 1, maxn - 1) G[i].clear();
    rep(i, 1, dfn) anc[0][i] = fa[i], lnk[i].clear();
    rep(i, 1, 22) rep(j, 1, dfn) anc[i][j] = anc[i - 1][anc[i - 1][j]];
}
void gett(int typ) {
    int x = read(), y = read();
    y = y - x + 1; x = pos[x];
    int fpos = kth(x, y);
    len[++cnt] = y; type[cnt] = typ;
    lnk[fpos].push_back(cnt);
}
void solve() {
    queue<int> q;
    LL ans = 0; int tot = 0;
    rep(i, 1, cnt) {
        if(!ind[i]) q.push(i);
        if(!type[i]) len[i] = 0;
    }
    while(!q.empty()) {
        int now = q.front(); q.pop();
        ans = max(ans, dp[now] + len[now]);
        for(auto to : G[now]) {
            ind[to]--;
            dp[to] = max(dp[to], dp[now] + len[now]);
            if(!ind[to]) q.push(to);
        }
    } int flg = 0;
    rep(i, 1, cnt) if(ind[i]) { flg = 1; cout << -1 << endl; break; }
    if(!flg) cout << ans << endl;
}
int main() {
    dwn(T, read(), 1) {
        init();
        scanf("%s", s + 1); n = strlen(s + 1);
        dwn(i, n, 1) extend(s[i] - 'a', i); build();
        na = read(); rep(i, 1, na) gett(1), a[i] = cnt;
        nb = read(); rep(i, 1, nb) gett(0), b[i] = cnt;
        rep(i, 1, cnt) G[i].clear();
        rep(i, 1, dfn) len[i] = mxlen[i];
        rep(i, 1, dfn) sort(lnk[i].begin(), lnk[i].end(), [&](const int &i, const int &j) { return len[i] > len[j] || (len[i] == len[j] && type[i] > type[j]);});
        int cur;
        rep(i, 1, dfn) {
            cur = i; reverse(lnk[i].begin(), lnk[i].end());
            for(auto to : lnk[i]) {
                 G[cur].push_back(to); ind[to]++;
                 if(type[to] == 0) cur = to;
            } lst[i] = cur;
        }
        rep(i, 2, dfn) G[lst[fa[i]]].push_back(i), ind[i]++; m = read();
        rep(i, 1, m) {
            int u = read(), v = read();
            G[a[u]].push_back(b[v]); 
            ind[b[v]]++;
        } solve();
    }
}
View Code

 

D1T3

给你所有测试数据,要求写一个代码长度 100K 以内的程序满足要求

sol:

不想 sol,肝了 50 多分肝不动了

 

D2T1

 

posted @ 2019-04-08 14:38  探险家Mr.H  阅读(298)  评论(0编辑  收藏  举报