前言
- 本模板只经过了本人不到半年的算法练习与比赛的使用与验证,有些高级的数据结构在本人的算法水平下几乎没有遇到过,还不太禁得住时间的考验,慎用。
- 模板来源主要为yxc算法模板,jiangly算法模板,以及各路神仙帖子的摘取,并做了一定程度的修改与注释。
- 兼顾了低常数与STL,并且做了压行,更方便在赛场上抄取,充足的注释也更方便现场理解。
- 应该能适用到邀请赛银牌的水平(大概吧)
- 如有不足请在评论区指出,我将在第一时间修正。
快速读入
#define rd read()
inline int read()
{
int x=0,y=1;char c=getchar();//y代表正负(1.-1),最后乘上x就可以了。
while (c<'0'||c>'9') {if (c=='-') y=-1;c=getchar();}//如果c是负号就把y赋为-1
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;//乘起来输出
}
高精度
高精度加法
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
高精度减法
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
高精度乘低精度
// C = A * b, A >= 0, b >= 0
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
高精度除以低精度
vector<int> div(vector<int> &A, int b, int &r)
{
vector<int> C;
r = 0;
for (int i = A.size() - 1; i >= 0; i -- )
{
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
数学
快速幂
int qpow(int m, int k, int p)
{
int res = 1 % p, t = m;
while (k)
{
if (k&1) res = res * t % p;
t = t * t % p;
k >>= 1;
}
return res;
}
快速组合数/排列数(模运算)
//预处理
f[0]=g[0]=1; //初始值
for (int i=1;i<=n;i++){
f[i]=f[i-1]*i%mod; //计算i的阶乘
g[i]=g[i-1]*qpow(i,mod-2)%mod; //计算i的乘法逆元 qpow为快速幂
}
int C(int n,int m){ //得到C(n,m)的组合数答案
return f[n]*g[m]*g[n-m]%mod;
}
int A(int n,int m){
return f[n]*g[n-m]%mod;
}
质数判定
bool is_prime(int n){//O(sqrt(N))
if(n<2)return false;
for(int i=2;i<=n/i;i++){
if(n%i==0)return false;
}
return true;
}
分解质因数
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
欧拉筛
vector<bool> st;
vector<int> primes;
void sieve(int n)
{ // O(n)
st.assign(n+1, false);
for (int i = 2; i <= n; i++)
{
if (!st[i])
primes.push_back(i); // 这里筛出来的是质数
for (int j = 0; primes[j] <= n / i; j++)
{ // 枚举前面求出过的质因子,与i相乘,就可以枚举出所有合数,prime一定小于等于i,所以枚举出的合数一定只会出现一次
st[primes[j] * i] = true;
if (i % primes[j] == 0)
break; // 如果这个质因子是i的最小质因子,则退出,因为
}
}
}
最大公约数 最小公倍数
int gcd(int a,int b){//返回a和b的最大公约数 O(logn)
return b ? gcd(b, a % b) : a;
}
int lcm(int a, int b){
return a / gcd(a, b)*b;//警示后人:先除再乘防止越界
}
二分
左查找
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;//mid向下取整
if (check(mid)) r = mid;//如果mid满足答案区间的性质,左移r
else l = mid + 1;//如果mid不满足答案区间的性质,右移l,l要跳过mid,所以+1
}
return l;
}
//或者
lower_bound(begin,end,x,cmp);
右查找
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;//mid向上取整
if (check(mid)) l = mid;//如果mid满足答案区间的性质,右移l
else r = mid - 1;//如果mid不满足答案区间的性质,左移r,r要跳过mid
}
return l;
}
//或者
upper_bound(begin,end,x,cmp);
浮点数二分
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
前缀和与差分
一维前缀和
s[i]=a[i]+s[i-1];//前缀和
s[r]-s[l-1]//[l,r]的区间和
一维差分
b[i]=a[i]-a[i-1];//若a为前缀和数组,差分后则为原数组
b[l]+=c,b[r+1]-=c;//在区间[l,r]加c
二维前缀和
//构建前缀和数组
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
//求[x1,y1],[x2,y2]子矩阵和
sum=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
二维差分
void insert(int x1,int y1,int x2,int y2,int c)
{ //对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
//构建差分数组
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
insert(i, j, i, j, a[i][j]);
//或者
b[i][j] = a[i][j] − a[i − 1][j] − a[i][j − 1] + a[i −1 ][j − 1]
搜索
DFS
void dfs(int k){
if(){
记录答案
return;
}
for(枚举这个空能填的选项){
if(这个选项是合法的){
保存现场
dfs(k+1);
恢复现场(回溯)
}
}
}
BFS
Q.push(初始状态)
while(!Q.empty()){
statu u =Q.front();取出队首
Q.pop();出队
for(枚举所有可拓展状态)找到u的所有可达状态v
{
if(是合法的)v需要满足某些条件,如未访问过,未在队内等
Q.push(v);入队,同时可能需要维护某些必要信息
}
}
离散化
版本1
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int idx(int x){ // 找到第一个大于等于x的位置
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, ...n
}
//或者
lower_bound(alls.begin(),alls.end(),x);
//或者用map映射储存映射值
版本2
//数组版离散化
map<int,int>idx;//用来储存映射值
sort(b+1,b+1+btop);//排序
b[0] = -0x3f3f3f3f;//b数组用来暂存待离散化的值,离散化后b就没用了
for (int i = 1; i <= btop;i++){//去重
if(b[i]!=b[i-1])
alls[++top] = b[i], idx[b[i]] = top;
}
版本3
//另一种离散化
void work(int a[]){//结果是直接将a数组转化为了离散化的数字
for(int i=1;i<=n;i++)p[i]=i;//p是辅助数组
sort(p + 1, p + 1 + n, [&](int x, int y)
{ return a[x] < a[y]; });//p存的是a中第i小的数的下标
for(int i=1;i<=n;i++)
a[p[i]] = i;
}
排序
归并排序
void merge_sort(int q[], int l, int r)
{
if(l>=r)return;
int mid = r + l >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);//左右两边都有序了
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j])tmp[k++] = q[i++];
else tmp[k++] = q[j++];
while (i <= mid)tmp[k++] = q[i++];
while (j <= r)tmp[k++] = q[j++];
for (int i = l, j = 0; i <= r; i++, j++)q[i] = tmp[j];
}
快速排序
void Qsort(int a[], int l, int r){
int i = l, j = r, key = a[l + r>>1];
do {
while (a[i] < key)i++;
while (a[j] > key)j--;
if (i <= j)swap(&a[i++], &a[j--]);
} while (i <= j);
if (l < j)Qsort(a, l, j);
if (i < r)Qsort(a, i, r);
}
单调队列
//双端队列
for(int i=0;i<n;i++){//队列中存的是下标
if(hh<=tt&&i-k+1>q[hh])hh++;//判断队头是否已经滑出窗口;
while(hh<=tt&&a[q[tt]]>=a[i])tt--;//维护队列单调性 如果队尾大于要入列的元素,弹出队尾//双端队列
q[++tt]=i;//新元素入队
}
//a[q[tt]]>=a[i] 单调递增队列,队首为最小值
//a[q[tt]]<=a[i] 单调不增队列,队首为最大值
单调栈
//找到每一个元素在入栈方向上第一个满足对该元素的单调条件的元素
for(int i=0;i<n;i++){
while(tt&&stk[tt]>=x)tt--;//出栈 第一个小于等于stk[tt]的数是x
stk[++tt]=i;//入栈//存的是下标
}
树状数组
要实现区间修改+单点查询,就在a的差分数组上建树状数组
要实现区间修改+区间查询,用线段树即可
int n;
int a[N],c[N];
int lowbit(int x){
return x & (-x);
}
int sum(int x){//前缀和查询
int res=0;
for (int i = x; i;i-=lowbit(i))
res += c[i];
return res;
}
void add(int x,int y){//单点修改
for (int i = x; i <= n;i+=lowbit(i))
c[i] += y;
}
int pre[N];
void init(){//初始化
for(int i=1;i<=n;i++){
pre[i]=pre[i-1]+a[i];
c[i] = pre[i] - pre[i - lowbit(i)];
}
}
ST表
int f[N][20]//[a][b]以a为开头,长度为2^b的区间最值
void init()
{
for (int i = 1; i <= n; i++)
f[i][0] = a[i];
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i <= n - (1 << j) + 1; i++)
f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
RMQ有两种方法
第一种是
int RMQ(int l, int r){
int L = r - l + 1, ans = -INF;
for (int i = 0, j = l; (1 << i) <= L; i++)
if ((L >> i) & 1){
ans = max(ans, f[j][i]);
j += (1 << i);
}
return ans;
}
另一种是
for(int i=2;i<=n;i++)//预处理
log_2[i] = log_2[i >> 1] + 1;
int query_max(int l,int r){
int x = log_2[r-l + 1];
return max(rmax[l][x], rmax[r - (1 << x) + 1][x]);
}
Tire字典树
int son[N][26];//[N]结点的[26]儿子的下标
int cnt[N];//以[N]结尾的单词的数量
int idx;//树结点的编号
//下标是0的点,既是根结点,又是空结点
void insert(char str[]){
int p=0;//当前在树的哪个位置
for(int i=0;str[i];i++){
int u = str[i]-'a';//映射
if(!son[p][u])son[p][u]=++idx;//如果这个字母不在树上,创建结点
p=son[p][u];//更新位置
}
cnt[p]++;
}
int query(char str[]){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u])return 0;
p = son[p][u];
}
return cnt[p];
}
KMP
void get_Next(string s, int next[]) //这个函数对字符串s进行预处理得到next数组
{
int j = 0;
next[0] = 0;//初始化
for(int i = 1; i<s.size(); i++){//i指针指向的是后缀末尾,j指针指向的是前缀末尾
while(j>0&&s[i]!=s[j]) j = next[j-1];//前后缀不相同,去找j前一位的最长相等前后缀
if(s[i]==s[j]) j++;//前后缀相同,j指针后移
next[i] = j;//更新next数组
}
}
int strSTR(string s, string t) //是从s中找到t,如果存在返回t出现的位置,如果不存在返回-1
{
if(t.size()==0) return 0;
get_Next(t, next);
for(int i = 0,j = 0; i < s.size(); i++){
while(j>0&&s[i]!= t[j]) j = next[j-1];
if(s[i]==t[j]) j++;
if(j==t.size()) return i - t.size() + 1;
}
return -1;
}
并查集
//警示后人:并查集一定要记得初始化!!!
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
int find(int x){// 返回x的祖宗节点
return (p[x] == x) ? p[x] : (p[x]=find(p[x]));
}
p[find(a)] = find(b);// 合并a和b所在的两个集合:
(2)维护size的并查集:
int p[N], size[N];//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
int find(int x){// 返回x的祖宗节点
return (p[x] == x) ? p[x] : (p[x]=find(p[x]));
}
for (int i = 1; i <= n; i ++ ){//初始化
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
for (int i = 1; i <= n; i ++ )// 初始化
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
拓扑排序
vector<int> topsort(){//如果返回的数组大小不等于点的个数,说明不存在拓扑排序/该图有环/该图不是DAG
vector<int>res;
queue<int>Q;
for(int i=1;i<=n;i++){
if(!ind[i])Q.push(i);
}
while(!Q.empty()){
int u=Q.front();
Q.pop();
res.push_back(u);
for(int i=0;i<g[u].size();i++){
int t=g[u][i];
ind[t]--;
if(!ind[t])Q.push(t);
}
}
return res;
}
最短路
堆优化dijkstra–O(mlogn)
struct edge{
int to, cost;
};
vector<edge> g[N]; // vector模拟邻接表
int dist[N]; // 从起点走到这个点所需要的最短距离
bool st[N]; // 这个点是否已经确定
typedef pair<int, int> PII;
int dijkstra()
{
// 初始化
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1}); // 存<距离,点>
while (heap.size())
{
// 利用堆,找到距离最小的点
auto t = heap.top(); // 堆顶的点就是距离最小的点
heap.pop();
int ver = t.second, distance = t.first; // 点,距离
if (st[ver])
continue;
st[ver] = true;
// 利用这个点来更新其它点
for (int i = 0,sz=g[ver].size();i<sz;i++)
{ // 从更新的点走到下一个点
int to = g[ver][i].to;
int cost = g[ver][i].cost;
if (dist[to] > distance + cost)
{ // 如果下一个点的原值不是最优
dist[to] = distance + cost; // 更新下一个点
heap.push({dist[to], to}); // 把更新的点加入到优先队列
}
}
}
if (dist[n] == 0x3f3f3f3f)
return -1;
return dist[n];
}
朴素dijkstra–O(n2)
int g[N][N];
int dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
for (int i = 1; i <= n; i++){
int t = -1;
for (int j = 1; j <= n; j++) // 遍历所有点
if (!st[j] && (t == -1 || dist[t] > dist[j]))//找到不在st中的最近点
t = j;
if(t==n)//可以不用写
break;//剪枝优化
st[t] = true;
for (int j = 1; j <= n; j++)//用t点来更新其它点的距离
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
if (dist[n] == 0x3f3f3f3f)
return -1;
return dist[n];
}
Bellman-Ford
const int N = 510, M = 10010;
int n, m, k;
int dist[N], backup[N];
struct Edge{
int a, b, w;
} edges[M];
bool bellman_ford(){
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
for (int i = 0; i < k; i++){
memcpy(backup, dist, sizeof(dist));//要用备份去更新
for (int j = 0; j < m; j++){
auto e = edges[j];
dist[e.b] = min(dist[e.b], backup[e.a] + e.w); // 用备份来更新,防止串联
}
}
if (dist[n] > 0x3f3f3f3f / 2) return false;//因为存在负权边,所以不能直接判断dist[n]==0x3f3f3f3f
else return true;
}
SPFA
const int N = 1000010;
int n, m;
struct edge{
int to, cost;
};
vector<edge> g[N]; // vector模拟邻接表
int dist[N]; // 从起点走到这个点所需要的最短距离
bool st[N]; // 这个点是否已在队列内
int SPFA()
{
memset(dist, 0x3f, sizeof(dist));
dist[1]=0;
queue<int>Q;
Q.push(1);
st[1] = true;
while(!Q.empty()){
int t=Q.front();
Q.pop();
st[t]=false;
for(int i=0,sz=g[t].size();i<sz;i++){
int j = g[t][i].to;
int w = g[t][i].cost;
if(dist[j]>dist[t]+w){
dist[j] = dist[t] + w;
if(!st[j]){
Q.push(j);
st[j] = false;
}
}
}
}
if(dist[n]==0x3f3f3f3f)return false;
else return true;
}
Floyed
const int N = 1000010;
int n, m;
struct edge{
int to, cost;
};
vector<edge> g[N]; // vector模拟邻接表
int dist[N]; // 从起点走到这个点所需要的最短距离
bool st[N]; // 这个点是否已在队列内
int SPFA()
{
memset(dist, 0x3f, sizeof(dist));
dist[1]=0;
queue<int>Q;
Q.push(1);
st[1] = true;
while(!Q.empty()){
int t=Q.front();
Q.pop();
st[t]=false;
for(int i=0,sz=g[t].size();i<sz;i++){
int j = g[t][i].to;
int w = g[t][i].cost;
if(dist[j]>dist[t]+w){
dist[j] = dist[t] + w;
if(!st[j]){
Q.push(j);
st[j] = false;
}
}
}
}
if(dist[n]==0x3f3f3f3f)return false;
else return true;
}
最小生成树
Prim
const int N = 510,INF=0x3f3f3f3f;
int g[N][N];//邻接矩阵存图
int dist[N];//每个点距离集合的距离
int n,m;
bool st[N];//记录点是不是在连通块内
int prim(){//如果存在最小生成树,则返回边权和,反之则返回INF
memset(dist,0x3f,sizeof dist);
int res = 0;//存的是最小生成树里面所有边的长度之和
for(int i=0;i<n;i++){//循环n次,i没有意义
int t = -1;
for (int j = 1; j <= n;j++)//这个循环 结束后,t就是距离集合最近的点
if(!st[j]&&(t==-1||dist[t]>dist[j]))t = j;
if (i) res += dist[t]; // 更新距离和
if(i&&dist[t]==INF)//说明这个图不连通
return INF;
for(int j=1;j<=n;j++) dist[j] = min(dist[j], g[j][t]);//更新其他点到集合的距离
st[t] = true;//标记这个点已经加入连通块
}
return res;
}
kruskal
using namespace std;
const int N = 200010;
int n, m,pre[N];
struct Edge{
int a, b, w;
bool operator<(const Edge &W) const {
return w < W.w;
}
}edges[N];
int find(int x){
return x == pre[x] ? x : (pre[x] = find(pre[x]));
}
int main(){
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,w;
cin>>a>>b>>w;
edges[i] = {a, b, w};
}
sort(edges, edges + m);//将所有边排序
for (int i = 1;i<=n;i++)pre[i]=i;//初始化并查集
int res = 0, cnt = 0;
for(int i=0;i<m;i++){
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if(a!=b){
res += w;//总边权++
cnt++;//边的数量++
pre[a] = b;
}
}
if(cnt<n-1)puts("impossible");
else
cout << res;
return 0;
}
二分图
染色法判定二分图
const int N = 100010, M = 200010;
int n, m;
vector<int> g[N];
int color[N];
bool dfs(int u, int c)
{
color[u] = c;
for (int i = 0, sz = g[u].size(); i < sz; i++){ // 遍历跟这个点连接的所有点
int j = g[u][i];
if (!color[j]){ // 如果下一个点没有被染色
if (!dfs(j, 3 - c))
return false;
}
else if (color[j] == c) // 如果下一个点跟这个点颜色相同
return false;
}
return true;
}
int main()
{
cin >> n >> m;
while (m--){
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
bool flag = true;
for (int i = 1; i <= n; i++)
if (!color[i]) // 如果这个点,没有被染色
if (!dfs(i, 1)){ // 从这个点开始染色
flag = false; // 如果发生矛盾
break;
}
if (flag == true)
puts("Yes");
else
puts("No");
return 0;
}
匈牙利算法
const int N = 510, M = 100010;
int n1, n2, m;
vector<int> g[N];
int match[N]; // 右边的点匹配的点
bool st[N]; // 判重
bool find(int x){
for (int i = 0,sz=g[x].size(); i<sz; i++){ // 遍历能匹配的所有点
int j = g[x][i];
if (!st[j]){// 如果这个点没有找过
st[j] = true;// 标记为找过
if (match[j] == 0 || find(match[j])) {
match[j] = x;
return true;
}
}
}
return false;
}
int main(){
cin >> n1 >> n2 >> m;
while (m--){
int a, b;
cin >> a >> b;
g[a].push_back(b);
}
int res = 0;
for (int i = 1; i <= n1; i++){
memset(st, false, sizeof st);
if (find(i))
res++;
}
cout << res;
return 0;
}
经典动态规划
背包问题
01背包
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++)
for (int j = m; j >= v[i]; j--)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m];
return 0;
}
完全背包
const int N = 1010;
int n,m;
int v[N], w[N];
int f[N];
int main(){
cin>>n>>m;
for (int i = 1;i<=n;i++)cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for (int j = v[i]; j <= m;j++)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m];
return 0;
}
二进制优化多重背包
inline void solve(){
int n = rd, m = rd;
int cnt = 0;
for (int i = 1; i <= n; i++){
int a=rd,b=rd,s=rd;
int k = 1;
while(k<=s){
cnt++;
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
if(s>0){
cnt++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt;
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m];
}
分组背包
inline void solve()
{
int n = rd, m = rd;
for (int i = 1; i <= n; i++)
{
s[i] = rd;
for (int j = 0; j < s[i]; j++)
v[i][j] = rd, w[i][j] = rd;
}
for (int i = 1; i <= n; i++)
for (int j = m; j >= 0; j--)
for (int k = 0; k < s[i]; k++)
if (v[i][k] <= j)
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
cout << f[m];
}
子序列问题
最长公共子序列
void solve()
{
cin >> n >> m >> a >> b;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i - 1] == b[j - 1])
f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
}
}
cout << f[n][m];
}
最长上升子序列
int main(){
int n;
cin>>n;
int a[N]={},q[N]={};
for(int i=1;i<=n;i++)cin>>a[i];
int len = 0;
q[0]=-0x3f3f3f3f;
for (int i = 1;i<=n;i++){
int l=0,r=len;
while(l<r){
int mid=l+r+1>>1;
if(q[mid]<a[i])l=mid;
else r=mid-1;
}
len=max(len,r+1);
q[r + 1] = a[i];
}
cout << len;
return 0;
}