省选小复习 2024
省选小复习 2024
主要以代码能力为主,个人向(临时抱佛脚)。
本文同步(可能有延迟)发布于:cnblogs, luogu blog.
二分
应用场景:单调性/二段性问题求解,部分最优化问题转换为判定性问题,三分,整体二分。
转换方法是:求 XXX 最小值 -> 答案值域为
例题:P7514,AGC006D
注意一些二分可以解决的问题双指针也可以解决,具体问题具体分析。
常规二分模板:曾经写的博客
三分可以求单峰函数极值点,注意如果一个函数是凸函数或者凹函数,则一定是单峰函数,反之不一定成立。
整体二分就是把同一类可以二分的多个问题放在一起二分判定求解,注意过程中的数据结构优化,代码框架如下:
void solve(int lval, int rval, int ql, int qr) // 当前二分的范围为 [lval,rval],询问在数组中编号 [ql,qr]
{
if (ql > qr)
return;
if (lval == rval)
{
for (int i = ql; i <= qr; i++)
// 求得这部分答案为 lval
return;
}
int mid = (lval + rval) >> 1;
int ltot = 0, rtot = 0;
for (int i = ql; i <= qr; i++)
{
// 对整体进行二分划分
if (q[i].y <= mid)
lq[++ltot] = q[i];
else
rq[++rtot] = q[i];
}
for (int i = 1; i <= ltot; i++)
q[ql + i - 1] = lq[i];
for (int i = 1; i <= rtot; i++)
q[ql + ltot + i - 1] = rq[i];
// 分治求解
solve(lval, mid, ql, ql + ltot - 1);
solve(mid + 1, rval, ql + ltot, qr);
}
二叉堆、哈夫曼树
优先队列的写法:
priority_queue<int, vector<int>, less<int>> q1; // 大根堆,重载 < 运算符
priority_queue<int, vector<int>, greater<int>> q1; // 小根堆,重载 > 运算符
二叉堆能解决的常见问题:ABC254Ex
数位 DP
好像暑假回来就没碰过这个东西了,求解的问题是某一个区间满足某个性质的数的个数。实现方法有组合计数和记忆化搜索。只会记忆化搜索,代码模板:
string l, r;
int a[N];
int dp[N][N];
int len;
int dfs(int pos, int r, bool limit)
{
// 当前填 pos 位
if (pos == 0)
return /*初始状态*/;
if (!limit && (dp[pos][r] != -1))
return dp[pos][r]; // 无限制条件下的记忆化搜索
int ans = 0;
int up = limit ? a[pos] : 9;
for (int i = 0; i <= up; i++)
if (/*分析条件*/)
ans = (ans + dfs(pos - 1, /*转移*/, limit && i == a[pos])) % mod;
if (!limit)
dp[pos][r] = ans;
return ans;
}
int solve(string &s)
{
memset(dp, -1, sizeof dp);
len = s.length();
for (int i = 0; i < len; i++)
a[i + 1] = s[len - i - 1] - '0'; // 把数字 s 按位填
return dfs(len, 0, 1);
}
例题:CF628D,
鸽巢原理
原理内容小学生都知道(应该),考试要想得到。尤其是类似于存在性问题、构造等。
CF618F
启发式合并
每次把大的集合合并到小的啥的,树上数颜色可以考虑重链剖分什么的。
可以考虑线段树合并(个人感觉更好写)。
CF600E,P3224
倍增
参考 LCA 的倍增求法,可以记录 DP 起点为 dp[][i] = dp[dp[][i-1]][i-1] 这样的转移。
P1081
可持久化线段树
没啥好说的,考前看眼代码咋写。
(话说这个菜鸡到现在一种树套树都没学)
struct node
{
int lc, rc;
int val;
} t[N * 80];
int tot, root[N];
int push_up(int p)
{
return t[p].val = t[t[p].lc].val + t[t[p].rc].val, p;
}
int insert(int p, int id, int cl, int cr)
{
int q = ++tot;
t[q] = t[p]; // 先复制
if (cl == cr)
return t[q].val++, q;
int mid = (cl + cr) >> 1;
if (id <= mid)
t[q].lc = insert(t[p].lc, id, cl, mid);
else
t[q].rc = insert(t[p].rc, id, mid + 1, cr);
return push_up(q);
}
int query(int p, int q, int k, int cl, int cr)
{
if (cl == cr)
return cl;
int mid = (cl + cr) >> 1, lv = t[t[q].lc].val - t[t[p].lc].val;
if (k <= lv)
return query(t[p].lc, t[q].lc, k, cl, mid);
return query(t[p].rc, t[q].rc, k - lv, mid + 1, cr);
}
字符串哈希
老朋友了。考场上可以想一些随机权值哈希(不可以,总司令)。【数据结构】Hash 学习笔记。
状态压缩 DP
先预处理出可以互相转移的状态。
子集枚举技巧:for (int ov = (ou - 1) & ou; ov; ov = (ov - 1) & ou)
CDQ 分治
解决离线问题的好助手。三维偏序:第一维排序,第二维分治,第三维数据结构。
void cdq(int l, int r)
{
if (l >= r)
return;
int mid = (l + r) / 2;
cdq(l, mid), cdq(mid + 1, r);
int i = l, j = mid + 1, k = l - 1;
while (i <= mid && j <= r)
{
if (da[i].b <= da[j].b) // 按第二维归并排序
BIT::add(da[i].c, da[i].cnt),
db[++k] = da[i++];
else
da[j].res += BIT::query(da[j].c),
db[++k] = da[j++];
}
while (i <= mid)
BIT::add(da[i].c, da[i].cnt),
db[++k] = da[i++];
while (j <= r)
da[j].res += BIT::query(da[j].c),
db[++k] = da[j++];
for (i = l; i <= mid; i++)
BIT::add(da[i].c, -da[i].cnt);
for (i = l; i <= r; i++)
da[i] = db[i];
}
线段树合并
int merge(int p, int q, int cl, int cr)
{
if (!p || !q)
return p | q;
if (cl == cr)
{
// 合并叶子结点
return p;
}
int mid = (cl + cr) / 2;
t[p].lc = merge(t[p].lc, t[q].lc, cl, mid);
t[p].rc = merge(t[p].rc, t[q].rc, mid + 1, cr);
return push_up(p);
}
矩阵相关
矩阵乘法具有结合率,根本原因是乘法对加法具有分配率。
矩阵快速幂加速 DP 方程转移。
网络流
背背 Dinic,这个蒟蒻没学建模。 最大流最小割定理、平面图最小割转对偶图最短路(代码不会写)。
bool bfs()
{
memset(d, 0, sizeof d);
queue<int> q;
q.push(S), d[S] = 1, now[S] = head[S];
while (!q.empty())
{
int u = q.front();
q.pop();
for (int e = head[u]; e; e = nxt[e])
{
int v = to[e], c = lc[e];
if (d[v] || !c)
continue;
d[v] = d[u] + 1;
now[v] = head[v];
q.push(v);
if (v == T)
return true;
}
}
return false;
}
int dinic(int u, int flow)
{
if (u == T)
return flow;
int rest = flow;
for (int &e = now[u]; e; e = nxt[e])
{
int v = to[e], c = lc[e];
if (d[v] != d[u] + 1 || !c)
continue;
int k = dinic(v, min(c, rest));
if (!k)
d[v] = 0;
lc[e] -= k, lc[e ^ 1] += k, rest -= k;
if (!rest)
break;
}
return flow - rest;
}