voidadd(int head[], int from, int to, int val){ //head表示表头,可能不止建一张图 edge[++ idx].to = to, edge[idx].val = val, edge[idx].next = head[from], head[from] = idx; }
// 滑动窗口,求区间内的最大值和最小值 int q[MAXN], hh, tt, n, k, a[MAXN]; //队列存放的是下标 //一共有n个数,区间长度为k voidsolve(){ n = read(), k = read(); for (int i = 1; i <= n; i++) a[i] = read(); hh = 1, tt = 0;
//处理最小值 for (int i = 1; i <= n; i ++) { while (hh <= tt && a[i] <= a[q[tt]]) tt--; //队尾不单调 q[++tt] = i; if (i - k >= q[hh]) hh++; //队头已不在队列中 if (i >= k) cout << a[q[hh]] << " "; } cout << endl;
//处理最大值 hh = 1, tt = 0; for (int i = 1; i <= n; i++) { while (hh <= tt && a[i] >= a[q[tt]]) tt--; //队尾不单调 q[++tt] = i; if (i - k >= q[hh]) hh++; //队头已不在队列中 if (i >= k) cout << a[q[hh]] << " "; } cout << endl; }
ll son[MAXN][30]; //字典树 ll cnt[MAXN], tot; //结尾处的标记 char s[MAXN]; int len; char c[10]; voidinsert(char s[], int len){ //往字典树中插入一个字符串 int now = 0; //0为根节点,now表示节点编号 for (int i = 1; i <= len; i++) { int u = s[i] - 'a'; if (!son[now][u]) son[now][u] = ++ tot; //如果没有这个字符就给他加上 now = son[now][u]; //往下层继续寻找 } cnt[now]++; //标记终点 }
intquery(char s[], int len){ //统计某个字符串出现的次数 int now = 0; for (int i = 1; i <= len; i++) { int u = s[i] - 'a'; if (!son[now][u]) return0; //找不到了 now = son[now][u]; //往下层继续寻找 } return cnt[now]; }
voidsolve(){ int n; n = read(); while (n --) { scanf("%s", c); scanf("%s", s + 1); int len = strlen(s + 1); if (c[0] == 'I') insert(s, len); else printf("%d\n", query(s, len)); } }
模板2, 统计前缀
不仅要统计有多少个字符串是给定字符串的前缀,也要求出给定字符串是多少个字符串的前缀并求出他们的和
类似的定义一个数组 sum ,表示节点编号为 i 的点被 经过 的次数。判断有多少字符串是给定字符串的前缀依然照旧计算。迭代到最后时,在累加上 sum[now] 即为给定字符串可匹配的前缀的个数
ll n, m; int son[500005][15], tot, cnt[MAXN], sum[MAXN]; int s[MAXN];
voidinsert(int s[], int len){ int now = 0; for (int i = 1; i <= len; i++) { int u = s[i]; if (!son[now][u]) son[now][u] = ++ tot; now = son[now][u]; sum[now] ++; //经过now } cnt[now]++; //以now结束 }
ll query(int s[], int len){ int now = 0; ll ans = 0; for (int i = 1; i <= len; i++) { int u = s[i]; if (cnt[now]) ans += cnt[now]; if (!son[now][u]) return ans; now = son[now][u]; //if(cnt[now]) //ans+=sum[now]应该这样写,但为了方便就换成这种写法,因为后面还要减去cnt[now]+sum[now]有点啰嗦 } ans = ans + sum[now]; //给定字符串作为前缀的匹配数量 return ans; }
voidsolve(){ n = read(), m = read(); for (int i = 1; i <= n; i ++) { //插入n个字符串 int len = read(); for (int j = 1; j <= len; j ++) s[j] = read(); insert(s, len); } for (int i = 1; i <= m; i ++) { //统计前缀 int len = read(); for (int j = 1; j <= len; j++) s[j] = read(); printf("%lld\n", query(s, len)); } }
voidinsert(ll x){ int now = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; //第i位的数 if (!son[now][u]) son[now][u] = ++tot; now = son[now][u]; } }
ll query(ll x){ ll ans = 0; ll now = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; if (son[now][!u]) { ans = ans * 2 + !u; now = son[now][!u]; } else { ans = ans * 2 + u; now = son[now][u]; } } return ans; }
voidsolve(){ n = read(); for (int i = 1; i <= n; i++) a[i] = read(); ll ans = 0; for (int i = 1; i <= n; i++) { insert(a[i]); ll b = query(a[i]); ans = max(ans, a[i] ^ b); } printf("%lld\n", ans); }
模板2,01trie的删除和维护
给定一个长度为 n 的序列 si ,求 max(si+sj)⨁sk∣i=j,j=k,i=k
ll n, m; ll son[MAXN][2], tot, cnt[MAXN * 10]; ll a[MAXN];
voidupdate(ll x, ll y){ int now = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; now = son[now][u]; cnt[now] += y; } }
ll query(ll x){ int now = 0; ll ans = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; if (son[now][!u] && cnt[son[now][!u]]) { //如果这个节点存在并且没有被删除掉 ans = ans * 2 + !u; now = son[now][!u]; } else { ans = ans * 2 + u; now = son[now][u]; } } return ans; }
voidsolve(){ n = read(); for (int i = 1; i <= n; i++) a[i] = read(), update(a[i], 1); ll ans = 0; for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { update(a[i], -1), update(a[j], -1); ans = max(ans, (a[i] + a[j]) ^ (query(a[i] + a[j]))); update(a[i], 1), update(a[j], 1); } } printf("%lld\n", ans); }
ll son[MAXN][2], tot, tmp; ll pre[400005], suf[400005]; ll n, a[400005]; voidinsert(ll x){ int now = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; if (!son[now][u]) son[now][u] = ++tot; now = son[now][u]; } }
ll query(ll x){ int now = 0; ll ans = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; if (son[now][!u]) { ans = ans * 2 + !u; now = now[son][!u]; } else { ans = ans * 2 + u; now = now[son][u]; } } return ans; }
voidsolve(){ n = read(); for (int i = 1; i <= n; i++) a[i] = read(); tmp = 0; for (int i = 1; i <= n; i++) { tmp ^= a[i]; insert(tmp); pre[i] = max(pre[i - 1], query(tmp) ^ tmp); } insert(0); for (int i = n; i >= 1; i--) { tmp ^= a[i]; insert(tmp); suf[i] = max(suf[i + 1], query(tmp) ^ tmp); } ll ans = 0; for (int i = 1; i < n; i++) ans = max(ans, pre[i] + suf[i + 1]); printf("%lld\n", ans); }
voidinsert(int id, int last, int now) //id表示当前插入的前缀和编号为i,last表示上个版本下和现在这个版本上和now同等意义的点 { mx[now] = id; //更新最新版本信息 for (int i = 31; i >= 0; i --) //枚举每一位 { int u = sum[id] >> i & 1; //取出当前位数 //如果前一个节点存在当前节点没有的分支,就把当前这个节点的空的路径指过去,相当于复制 if (last) son[now][!u] = son[last][!u]; son[now][u] = ++ tot; //正常的插入操作
//可以注意到此时只复制了!u这个点的信息,u方向的还没复制过 //不是说不用复制了 //而是因为now现在插入的数字是u,而last版本下这条路径也是u,所以暂时不需要复制 //如果之后的某个位置与last版本出现了偏差,在从那里开始复制就好了 last = son[last][u], now = son[now][u]; //同步往下走(递归) mx[son[now][u]] = id; //更新当前点的最新版本信息 } }
ll query(int now, int l, ll x) { ll ans = 0; for (int i = 31; i >= 0; i --) { int u = (x >> i) & 1; if (mx[son[now][!u]] >= l) //1.!u这个点要出现过 //2.!u这个点的最新版本出现时间要大于等于l //2包含1,只要满足2那么1也一定满足 { now = son[now][!u]; ans = ans * 2 + !u; } else { now = son[now][u]; ans = ans * 2 + u; } } return ans; }
int n, m; int son[MAXM][2], sum[MAXN], mx[MAXM], tot; int rt[MAXM];
voidinsert(int id, int last, int now){ mx[now] = id; for (int i = 31; i >= 0; i--) { int u = (sum[id] >> i) & 1; if (last) son[now][!u] = son[last][!u]; son[now][u] = ++ tot; last = son[last][u], now = son[now][u]; mx[son[now][u]] = id; } }
ll query(int now, int l, ll x){ ll ans = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; if (mx[son[now][!u]] >= l) { now = son[now][!u]; ans = ans * 2 + !u; } else { now = son[now][u]; ans = ans * 2 + u; } } return ans; }
voidsolve(){ n = read(), m = read(); mx[0] = -114514; rt[0] = ++tot; sum[0] = 0; insert(0, 0, rt[0]); for (int i = 1; i <= n; i++) { ll tmp; tmp = read(); sum[i] = sum[i - 1] ^ tmp; rt[i] = ++ tot; insert(i, rt[i - 1], rt[i]); } while (m--) { char s[10]; scanf("%s", s); if (s[0] == 'A') { //插入一个数 ll x; x = read(); n ++; //多了一个数 sum[n] = sum[n - 1] ^ x; //更新前缀和 rt[n] = ++ tot; //新增一个版本 insert(n, rt[n - 1], rt[n]); } else { ll x, l, r; l = read(), r = read(), x = read(); //query返回的是从r-1这个版本进去,最早版本为l-1,与sum[n]^x异或后最大的数 printf("%lld\n", query(rt[r - 1], l - 1, sum[n] ^ x) ^ (sum[n] ^ x)); } } }
并查集
朴素并查集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
int f[MAXN]; //祖先节点
voidinit(){ //初始化 for (int i = 1; i <= n; i ++) f[i] = i; }
intask(int x){ int ans = 0; for (int i = x; i; i -= lowbit(i)) { ans += c[i]; } return ans; }
intkth(int x){ int l = 1, r = n; int ans; while (l <= r) { int mid = l + r >> 1; if (ask(mid) >= x) { ans = mid; r = mid - 1; } else l = mid + 1; } return ans; }
voidupdate(int x, int val){ for (int i = x; i <= n; i += lowbit(i)) c[i] += val; }
voidsolve(){ n = read(), q = read(); for (int i = 1; i <= n; i++) { int x = read(); update(x, 1); } for (int i = 1; i <= q; i++) { int opt = read(); if (opt > 0) { update(opt, 1); } elseif (opt < 0) { opt = -opt; int num = kth(opt); update(num, -1); } } if (!ask(n)) { puts("0"); return; } for (int i = 1; i <= n; i++) { if (ask(i) - ask(i - 1) > 0) { printf("%d\n", i); return; } } }
二维树状数组求子矩阵
第一行两个整数,n(1 <= n <= 1000)和m(1 <= m <= 100000),分别代表正方形格子的边长和询问次数。
接下来n行,每一行有n个bool形数字(0或1),代表灯泡的状态。
接下来m行,每一行第一个数字f(1或2)代表操作的类型,如果f是1,那么接下来输入一个坐标(x, y)(1 <= x, y <= n),对于当前位置的灯泡状态进行改变,如果是2,那么接下来输入两个坐标(x1, y1)(1 <= x1, y1 <= n), (x2, y2)(1 <= x2, y2 <= n),确定子矩阵的位置,输出子矩阵中亮着的灯泡数量并换行。