Appearance
技巧
取消同步输入输出流:ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
区间dp
cpp
for(int k=1;k<n;k++)
for(int l=1,r=l+1;r<=n;l++,r++)
for(int i=l;i<r;i++)
{
dp[l][r]=min||max(dp[l][i]+dp[i+1][r]+C,dp[l][r]);
}
并查集
cpp
vector<int>father=vector<int>(size);
int sets;
void build(int m){
for(int i=0;i<m;i++){
father[i]=i;
}
sets=m;
}
int find(int i){
if(i!=father[i]){
father[i]=find(father[i]);
}
return father[i];
}
void uni(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx!=fy){
father(fx)=fy;
sets--;
}
}
Dijkstra
cpp
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#define mm 20000001
#define inf 0x7fffffff
using namespace std;
int n,m,s,cnt;
int h[2000001],dis[mm];
bool visit[mm];
struct Edge{
int to,w,next;
}edge[2000001];
struct node
{
int dis;
int pos;
bool operator <( const node &x )const
{
return x.dis < dis;
}
};
priority_queue<node>q;
void init ()
{
memset(visit,0,sizeof(visit));
memset(dis,0x3f,sizeof(dis));
memset(h,-1,sizeof(h));
}
void add(int u,int v,int w)
{
edge[cnt].to=v,edge[cnt].w=w,edge[cnt].next=h[u],h[u]=cnt++;
}
void pintf()
{
for(int i=1;i<=n;i++)
{
cout<<dis[i]<<' ';
}
}
void Dijkstra()
{
while(!q.empty())
{
node temp=q.top();
q.pop();
int x=temp.pos,d=temp.dis;
if(!visit[x])
{
visit[x]=1;
for(int i=h[x];i!=-1;i=edge[i].next)
{
int t=edge[i].to;
if(!visit[t]&&dis[t]>dis[x]+edge[i].w)
dis[t]=dis[x]+edge[i].w,q.push((node){dis[t],t});
}
}
}
return ;
}
int main ()
{
cin>>n>>m>>s;
init();
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
dis[s]=0;
q.push((node){0,s});
Dijkstra();
pintf();
return 0;
}
树状数组
cpp
int lowbit(int x)
{
return x & (-x);
}
void add(int v, int x)
{
for (int i = v; i <= n; i += lowbit(i))
{
c[i] += x;
}
return;
}
int query(int x)
{
int sum = 0;
for (int i = x; i >= 1; i -= lowbit(i))
{
sum += c[i];
}
return sum;
}
LCS 和 LIS 问题
LIS :最长单调上升子序列。 LCS:最长公共子序列。
LIS
考虑
cpp
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>=a[j])
f[i]=max(f[i],f[j]+1);
}
}
可优化为
cpp
for (int i = n; i >= 1; i--)
{
int k = upper_bound(f + 1, f + ans + 1, a[i]) - f;
f[k] = a[i];
ans = max(ans, k);
}
LCS
首先考虑朴素的公共子序列问题 典型的区间
cpp
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
if (a1[i] == a2[j])
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
// 因为更新,所以++;
}
回文也可以简单的理解为颠转顺序来求正反串的最大公共子序列。 考虑找到最大公共子序列,用强制定义顺序,来使 LCS转换成LIS问题。
对于任意两段数字内容序列,首先将其离散化,对任意序列强行定义顺序,在将另一序列用定义的顺序替换即可。
cpp
for (int i = 1; i <= n; i++)
{
int x;
cin>> x;
v[x] = i;
}
位运算
求某个数二进制的第i位:
二进制中 -x=~x+1;
个相同的数异或和,当 为奇数,结果为这个数本身,否则为 。 - 任何数与
异或是这个数本身。
cpp
int lowbit(int x)
{
return x&((~x)+1);
}
快速幂
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1001;
int a, b, p;
int n, m, ans;
int t;
void qmi()
{
int res = 1 % p;
while (b)
{
if (b & 1)
res = res * a % p;
a = a * a % p;
b /= 2;
}
cout << res << "\n";
}
signed main()
{
cin >> t;
while (t--)
{
cin >> a >> b >> p;
qmi();
}
return 0;
}
进制问题
X 进制:
平时我们所说的10进制数是怎么得出来的呢? 比如10进制数 123: 它是由百位上的1 * 10 * 10 加上 十位上的 2 * 10 加上 个位上的 3 得出来的
关于x进制转10进制: 比如题目中给的:11进制(10)、5进制(4)、2进制(0) 对于i位上的数字
就是10*5*2+4*2+0=108
再比如:11进制(1)、5进制(2)、2进制(0)
就是1*5*2+2*2+0=14
组合数学
{ 插板法:解释 公式:
cpp
void build()
{
c[0][0] = c[1][0] = c[1][1] = 1;
for (int i = 2; i <= 2000; i++)
{
c[i][0] = 1;
c[i][i] = 1;
for (int j = 1; j <= i; j++)
{
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % k;
f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1];
if (c[i][j] == 0)
f[i][j]++;
f[i][i + 1] = f[i][i];
}
}
}
}
进制转换
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 101001;
string a, b;
map<char, int> q;
int tra10(int n, string s)
{
int ns = s.size();
int k = 1;
int res = 0;
for (int i = ns - 1; i >= 0; i--)
{
if (s[i] >= 'A' && s[i] <= 'F')
res += (q[s[i]] * k);
else
res += (s[i] - '0') * k;
k *= n;
}
return res;
}
void output(stack<int> s)
{
while (!s.empty())
{
if (s.top() >= 10)
{
cout << (char)(s.top() + 'A' - 10);
}
else
cout << s.top();
s.pop();
}
}
void work(int n, int r)
{
stack<int> s;
while (n)
{
s.push(n % r);
n /= r;
}
output(s);
}
int main()
{
int n, m, ans;
cin >> n >> a >> m;
q['A'] = 10;
q['B'] = 11;
q['C'] = 12;
q['D'] = 13;
q['E'] = 14;
q['F'] = 15;
int n_10 = tra10(n, a);
// cout << n_10;
work(n_10, m);
return 0;
}
可持久化线段树(主席树)
cpp
// 对于建树的存数都是优先存到左区间里
#include <bits/stdc++.h>
#define mm 300000
using namespace std;
// 定义区
struct nihao
{
int l, r, sum, nl, nr, Sum; // 历史的树根节点,左子树,右子树(都是指的节点)//某个区间有几个数
} t[mm << 5];
int a[mm], b[mm]; // 原数组,离散数组
int nodee; // 节点
int inst; // 修改插入的点
int g; // 离散的右边界
int n, m, k;
int anss;
int rt[mm << 2];
// 函数区
void make(int &x, int l, int r) // 建树
{
x = ++nodee;
if (l == r)
return; // 叶节点
int mid = (l + r) >> 1;
make(t[x].l, l, mid);
make(t[x].r, mid + 1, r);
return;
}
void modify(int &xi, int x, int l, int r, int v)
{
xi = ++nodee; // 建立新节点
t[xi] = t[x];
t[xi].sum++;
t[xi].Sum += v; // 传递树节点数据
if (l == r)
return; // 已经到叶节点,不用再建树了
int mid = (l + r) >> 1;
if (inst <= mid)
modify(t[xi].l, t[x].l, l, mid, v);
else
modify(t[xi].r, t[x].r, mid + 1, r, v);
return;
}
int query(int use, int now, int l, int r, int k)
{
int ans;
int mid = (l + r) >> 1;
int h = t[t[now].l].sum - t[t[use].l].sum; // h是权值线段树操作
if (l == r)
return l;
if (k <= h)
ans = query(t[use].l, t[now].l, l, mid, k); // 递归的都是节点
else
ans = query(t[use].r, t[now].r, mid + 1, r, k - h); // 注意要减去左区间的数
return ans;
}
signed /*int*/ main()
{
int l, r;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i], b[i] = a[i];
sort(b + 1, b + n + 1);
g = unique(b + 1, b + n + 1) - b - 1;
make(rt[0], 1, g);
for (int i = 1; i <= n; i++) // 全部建树
{
inst = lower_bound(b + 1, b + g + 1, a[i]) - b; // 找到a[i]对应的离散化下标
modify(rt[i], rt[i - 1], 1, g, a[i]); // modify(rt[i],rt[i-1],1,inf,a[i]);
}
while (m--)
{
cin >> l >> r;
// k = re;
k = (r - l) / 2 + 1;
// cout << k << " ";
anss = query(rt[l - 1], rt[r], 1, g, k);
cout << b[anss] << "\n";
}
return 0;
}
单调队列
**单调队列维护前缀和的时候,由于用到
更新尾指针的时候,比较的下标是
双指针近乎可以优化掉所有的区间内的大小问题(某种数据需要具有单调性)
很多的时候往往要用到初始的长区间序列,所以定义的时候就要保证h=0,t=1;(一般用于前缀和问题,防止初始元素被剪掉)
考虑在
即若
一般的优化考虑的是使用双端队列来优化单调队列,个人认为也可以考虑使用优先队列(这个用来候补,暂时还想不到如何存储数据)
在动态区间维护区间最值也是可以的,可以添加一些性质(平均数)
前缀和单调队列优化 AcWing 135. 最大子序和 AcWing 1087. 修剪草坪
甚至可以考虑使用二维的。
维护过程
考虑维护区间最值时,使用单调队列进行优化,考虑每往队列里面添加一个元素
队列中只需记录元素下标即可,队列中的元素大小递减,过期值递增。
cpp
const int N = 11111111;
int q[N],d[N];// 数组原值,队列数组
int h=0,t=0,k; //头指针,尾指针,区间大小
for(int i=1;i<=n;i++)
{
while(h<t&&q[d[t-1]]<=q[i])t--;
d[t++]=i;
while(d[t-1]-d[h]>k)h++;
}
考虑另一种单调队列的写法
cpp
int h = 0, t = 0;
int q[100100], pos[100012];//此时的q数组相当于原来的a数组
int kk = N;//定义一个可计算的区间
for(int i=1;i<=n;i++)
{
while(h<t&&pos[h]>kk)h++
int x;//一个被计算的需要录入的值
while(h<t&&q[t-1]<=x)t--;
pos[t]=i;
q[t++]=x;
}
线段树
来历已久,久仰大名了。 线段树是解决区间问题,区间加,区间减,区间最值,区间覆盖,凡是可以将区间合并,并快速的寻找任意区间的任意值的话,都可用线段树解决。 所以线段树>st表~树状数组。 线段树分为的步骤:build(建树),push_up(上传结点信息),push_down(下传结点信息),make_tag(区间信息维护),query(区间查询),up_date(结点维护),find(判断建树结果)。 线段树乘法要先乘后加。
up_date中有push_down和push_up,query中有push_down
其实线段树的优点就是快速的维护区间信息的东西
可以维护,公倍数,公因数,最大值,最小值,只要满足区间就可以使用线段树
结构部分
- 树的结构:date,l,r,lz(懒标记)。
cpp
struct nihao
{
int l, r, date, lz, mx, mn, lx, ln;
} t[2813102];
可以开一个答案查询函数
cpp
const int inf = 231231231;
nihao ans;
void init()
{
ans.mx = -inf;
ans.mn = inf;
ans.date = 0;
}
void ans_query(int num)
{
ans.mx = max(ans.mx, t[num].mx);
ans.mn = min(ans.mn, t[num].mn);
ans.date += t[num].date;
}
- build(建树
cpp
void build(int l, int r, int num)
{
t[num].l = l, t[num].r = r;
if (l == r)
{
t[num].date = p[l];
t[num].mx = p[l];
t[num].mn = p[l];
t[num].lx = -inf;
t[num].ln = inf;
return;
}
int mid = (l + r) >> 1;
build(l, mid, num << 1);
build(mid + 1, r, num << 1 | 1);
push_up(num);
}
- push_up(上传结点信息)
cpp
void push_up(int num)
{
t[num].date = t[num << 1].date + t[num << 1 | 1].date;
t[num].mx = max(t[num << 1].mx, t[num << 1 | 1].mx);
t[num].mn = min(t[num << 1].mn, t[num << 1 | 1].mn);
t[num].lx = -inf;
t[num].ln = inf;
}
- push_down(下传结点信息)
cpp
void push_down(int num)
{
int mid = (t[num].l + t[num].r) >> 1;
make_tag(num << 1, t[num].lz, t[num].lx, t[num].ln);
make_tag(num << 1 | 1, t[num].lz, t[num].lx, t[num].ln);
t[num].lz = 0;
t[num].lx = -inf;
t[num].ln = inf;
}
- make_tag(区间信息维护)
cpp
void make_tag(int num, int k, int kk, int kkk) //
{
t[num].mx = max(t[num].mx, kk);
t[num].mn = min(t[num].mn, kkk);
t[num].date += k * (t[num].r - t[num].l + 1);
t[num].lz += k;
t[num].lx = max(t[num].lx, kk);
t[num].ln = min(t[num].ln, kkk);
}
- query(区间查询)
cpp
void query(int l,int r,int num)//查询区间的l,r不会变,只有子树的区间会变
{
if (in(t[num].l, t[num].r, l, r))
{
ans_query(num);
return;
}
if (out(t[num].l, t[num].r, l, r))
{
return;
}
int mid = (t[num].l + t[num].r) >> 1;
push_down(num);
if (l <= mid)
query(l, r, num << 1);
if (r > mid)
query(l, r, num << 1 | 1);
return;
}
- up_date(结点维护)
cpp
void up_date(int l,int r,int k,int num)
{
if (out(l, r, t[num].l, t[num].r))
return;
if (in(t[num].l, t[num].r, l, r))
{
make_tag(num, k, k, k);
return;
}
push_down(num);
up_date(l, r, k, num << 1);
up_date(l, r, k, (num << 1) + 1);
push_up(num);
}
最后加上一个
- find (判断建树结果)
cpp
void find(int l, int r, int num)
{
int mid = (l + r) >> 1;
if (t[num].l == 0 || t[num].r == 0)
return;
cout << t[num].l << " " << t[num].r << " " << t[num].date << " " << t[num].mx << " " << t[num].mn << "\n";
if (l == r)
{
return;
}
find(num << 1, l, mid);
find(num << 1 | 1, mid + 1, r);
return;
}
源代码
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int inf = 231231231;
struct nihao
{
int l, r, date, lz, mx, mn, lx, ln;
} t[2813102];
int p[213123112];
int n;
// 可以开一个答案查询函数
nihao ans;
void init()
{
ans.mx = -inf;
ans.mn = inf;
ans.date = 0;
}
void push_up(int num)
{
t[num].date = t[num << 1].date + t[num << 1 | 1].date;
t[num].mx = max(t[num << 1].mx, t[num << 1 | 1].mx);
t[num].mn = min(t[num << 1].mn, t[num << 1 | 1].mn);
// cout << t[num << 1].l << " " << t[num << 1 | 1].r << " " << t[num << 1].mx << " " << t[num << 1].mn << " " << t[num].mx << " " << t[num].mn << " "
// << "\n";
t[num].lx = -inf;
t[num].ln = inf;
}
void build(int l, int r, int num)
{
t[num].l = l, t[num].r = r;
if (l == r)
{
t[num].date = p[l];
t[num].mx = p[l];
t[num].mn = p[l];
t[num].lx = -inf;
t[num].ln = inf;
return;
}
int mid = (l + r) >> 1;
build(l, mid, num << 1);
build(mid + 1, r, num << 1 | 1);
push_up(num);
}
bool in(int l, int r, int L, int R)
{
return (l >= L) && (r <= R);
}
bool out(int l, int r, int L, int R)
{
return (l > R) || (r < L);
}
void make_tag(int num, int k, int kk, int kkk) //
{
t[num].mx = max(t[num].mx, kk);
t[num].mn = min(t[num].mn, kkk);
t[num].date += k * (t[num].r - t[num].l + 1);
t[num].lz += k;
t[num].lx = max(t[num].lx, kk);
t[num].ln = min(t[num].ln, kkk);
}
void push_down(int num)
{
int mid = (t[num].l + t[num].r) >> 1;
make_tag(num << 1, t[num].lz, t[num].lx, t[num].ln);
make_tag(num << 1 | 1, t[num].lz, t[num].lx, t[num].ln);
t[num].lz = 0;
t[num].lx = -inf;
t[num].ln = inf;
}
void up_date(int l, int r, int k, int num)
{
if (out(l, r, t[num].l, t[num].r))
return;
if (in(t[num].l, t[num].r, l, r))
{
make_tag(num, k, k, k);
return;
}
push_down(num);
up_date(l, r, k, num << 1);
up_date(l, r, k, (num << 1) + 1);
push_up(num);
}
void ans_query(int num)
{
ans.mx = max(ans.mx, t[num].mx);
ans.mn = min(ans.mn, t[num].mn);
ans.date += t[num].date;
}
void query(int l, int r, int num) // 查询区间的l,r不会变,只有子树的区间会变
{
if (in(t[num].l, t[num].r, l, r))
{
ans_query(num);
return;
}
if (out(t[num].l, t[num].r, l, r))
{
return;
}
int mid = (t[num].l + t[num].r) >> 1;
push_down(num);
if (l <= mid)
query(l, r, num << 1);
if (r > mid)
query(l, r, num << 1 | 1);
return;
}
void find(int l, int r, int num)
{
int mid = (l + r) >> 1;
if (t[num].l == 0 || t[num].r == 0)
return;
cout << t[num].l << " " << t[num].r << " " << t[num].date << " " << t[num].mx << " " << t[num].mn << "\n";
if (l == r)
{
return;
}
find(num << 1, l, mid);
find(num << 1 | 1, mid + 1, r);
return;
}
signed main()
{
cin >> n;
int q;
cin >> q;
for (int i = 1; i <= n; i++)
cin >> p[i];
build(1, n, 1);
// find(1, n, 1);
return 0;
}
关键
{ 在查询和结点更改的时候,查询区间l,r不会改变,in的信息是查询区间l,r包含树的区间 }