板子
板子
处理模数运算的自定义数据类
点击查看代码
const int mod = 1e9+7;
long long ext_gcd(long long a, long long b, long long &x, long long &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
long long d = ext_gcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
// 计算 a 的模逆元
long long inv(long long a, long long mod) {
long long x, y;
ext_gcd(a, mod, x, y);
return (x % mod + mod) % mod;
}
class Z {
public:
long long val;
Z() : val(0) {}
Z(long long v) : val(v % mod) {}
Z(const Z &other) : val(other.val) {}
Z& operator=(const Z &other) {
val = other.val;
return *this;
}
Z operator+(const Z &other) const {
return Z(val + other.val);
}
Z operator-(const Z &other) const {
return Z(val - other.val + mod);
}
Z operator*(const Z &other) const {
return Z(val * other.val);
}
Z operator/(const Z &other) const {
return Z(val * inv(other.val, mod));
}
Z& operator+=(const Z &other) {
val = (val + other.val) % mod;
return *this;
}
Z& operator-=(const Z &other) {
val = (val - other.val + mod) % mod;
return *this;
}
Z& operator*=(const Z &other) {
val = (val * other.val) % mod;
return *this;
}
Z& operator/=(const Z &other) {
val = (val * inv(other.val, mod)) % mod;
return *this;
}
friend std::ostream& operator<<(std::ostream &os, const Z &z) {
os << z.val;
return os;
}
};
线段树板子:
https://ac.nowcoder.com/acm/contest/105011/B
圆,已知三点求圆心,求投影点,求半径,求与某条直线的关系。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll __int128
#define PII pair<int,int>
#define PDI pair<double,int>
#define Pll pair<ll, ll>
#define PSI pair<string, int>
#define PCC pair<char, char>
#define endl '\n'
#define mk make_pair
#define IOS ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
#define Pi acos(-1.0)
#define ull unsigned long long
int ls(int p){ return (p << 1) ;}
int rs(int p){ return (p << 1 | 1) ;}
inline int lowbit(int x) { return x & (-x) ;}
const int mod = 100000007;
const int N = 5e5 + 10;
const int Inf = LLONG_MAX;
#define double long double
#define eps (1e-8)
#define equals(a, b) (fabs((a) - (b)) < EPS)
const double pi=acos(-1.0);
int sgn(double x){
if(fabs(x)<eps) return 0;
else return x<0?-1:1;
}
int dcmp(double x,double y){
if(fabs(x-y)<eps) return 0;
else return x<y?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double x,double y):x(x),y(y){}
Point operator+(Point B){
return Point(x+B.x,y+B.y);
}
Point operator-(Point B){
return Point(x-B.x,y-B.y);
}
Point operator*(double k){
return Point(x*k,y*k);
}
Point operator/(double k){
return Point(x/k,y/k);
}
bool operator==(Point B){
return sgn(x-B.x)==0 and sgn(y-B.y)==0;
}
};
struct Line{
Point p1,p2;
Line(){}
Line(Point p1,Point p2):p1(p1),p2(p2){}
Line(Point p,double angle){
p1=p;
if(sgn(angle-pi/2)==0){
p2=(p1+Point(0,1));
}else{
p2=(p1+Point(1,tan(angle)));
}
}
Line(double a,double b,double c){
if(sgn(a)==0){
p1=Point(0,-c/b);
p2=Point(1,-c/b);
}else if(sgn(b)==0){
p1=Point(-c/a,0);
p2=Point(-c/b,0);
}else{
p1=Point(0,-c/b);
p2=Point(1,(-c-a)/b);
}
}
};
typedef Point Vector;
struct Circle{
Point c;
double r;
Circle(){}
Circle(Point c,double r):c(c),r(r){}
Circle(double x,double y,double _r){c=Point(x,y);r=_r;}
};
double Cross(Vector A,Vector B){
return A.x*B.y-A.y*B.x;
}
double Distance(Point A,Point B){
return hypot(A.x-B.x,A.y-B.y);
}
double Dis_point_line(Point p,Line v){
return fabs(Cross(p-v.p1,v.p2-v.p1))/Distance(v.p1,v.p2);
}
int Line_circle(Line v,Circle C){
double dst=Dis_point_line(C.c,v);
if(sgn(dst-C.r)<0)return 0;
if(sgn(dst-C.r)==0) return 1;
return 2;
}
Point circle_center(const Point a,const Point b,const Point c){
Point center;
double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;
double a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;
double d=a1*b2-a2*b1;
center.x=a.x+(c1*b2-c2*b1)/d;
center.y=a.y+(a1*c2-a2*c1)/d;
return center;
}
double Dot(Vector A,Vector B){
return A.x*B.x+A.y*B.y;
}
double Len2(Vector A){
return Dot(A,A);
}
Point pp(Point p,Line v){
double k=Dot(v.p2-v.p1,p-v.p1)/Len2(v.p2-v.p1);
return v.p1+(v.p2-v.p1)*k;
}
void solve(){
Circle c;
vector<Point>a(4);
for(int i=1;i<=3;i++){
cin>>a[i].x>>a[i].y;
}
Point cc=circle_center(a[1],a[2],a[3]);
c.c.x=cc.x;
c.c.y=cc.y;
c.r=sqrt((a[1].x-cc.x)*(a[1].x-cc.x)+(a[1].y-cc.y)*(a[1].y-cc.y));
Point p,v;
cin>>p.x>>p.y>>v.x>>v.y;
Point q;
q.x=p.x+v.x;
q.y=p.y+v.y;
Line z;
z.p1=p;z.p2=q;
Point tyd=pp(c.c,z);
double rr=sqrt((tyd.x-cc.x)*(tyd.x-cc.x)+(tyd.y-cc.y)*(tyd.y-cc.y));
if(rr==c.r){
cout<<"Or"<<endl;
}else if(rr<c.r){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
// int x=Line_circle(z,c);
//
// double d1=sqrt((p.x-cc.x)*(p.x-cc.x)+(p.y-cc.y)*(p.y-cc.y));
//
// if(d1==c.r){
// double k1=(cc.y-p.y)/(cc.x-p.x);
// double k2=(p.y-q.y)/(p.x-q.x);
// if(k1*k2!=-1){
// cout<<"Yes"<<endl;
// }else{
// cout<<"Or"<<endl;
// }
// return;
// }
// double d2=sqrt((q.x-cc.x)*(q.x-cc.x)+(q.y-cc.y)*(q.y-cc.y));
// if(d2==c.r){
// double k1=(cc.y-q.y)/(cc.x-q.x);
// double k2=(p.y-q.y)/(p.x-q.x);
// if(k1*k2!=-1){
// cout<<"Yes"<<endl;
// }else{
// cout<<"Or"<<endl;
// }
// return;
// }
//
//
// if(x==0){
// cout<<"Yes"<<endl;
// }else if(x==1){
// cout<<"Or"<<endl;
// }else{
// cout<<"No"<<endl;
// }
}
signed main(){
IOS
int T = 1;
cin >> T;
while(T--) solve();
}
/*
1
2 0 1 1 0 0 1 1 99999999 -1
3
0 0
0 1
1 0
1 0
1 1
0 0
0 1
1 0
1 1
1 1
0 0
0 1
1 0
2 0
0 1
*/
区间最值线段树
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define pii pair<int, int>
const int mod = 998244353;
const int inf = LLONG_MAX;
const int N = 5e5 + 100;
int n, m;
int a[N];
struct SegmentTree {
int l, r;
int mx; // 区间最大值
int sum; // 区间和
int add_lazy; // 加法懒标记
int mul_lazy; // 乘法懒标记
int set_lazy; // 赋值懒标记 (inf表示未赋值)
} tr[N * 4];
void pushup(int u) {
tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int l, int r, int u) {
tr[u] = {l, r, 0, 0, 0, 1, inf};
if (l == r) {
tr[u].mx = a[l];
tr[u].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, u << 1);
build(mid + 1, r, u << 1 | 1);
pushup(u);
}
// 核心:下传懒标记
void pushdown(int u) {
int len_left = tr[u << 1].r - tr[u << 1].l + 1;
int len_right = tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1;
// 优先级:赋值 > 乘法 > 加法
if (tr[u].set_lazy != inf) {
// 下传赋值标记
int set_val = tr[u].set_lazy;
tr[u << 1].mx = set_val;
tr[u << 1].sum = set_val * len_left;
tr[u << 1].set_lazy = set_val;
tr[u << 1].add_lazy = 0;
tr[u << 1].mul_lazy = 1;
tr[u << 1 | 1].mx = set_val;
tr[u << 1 | 1].sum = set_val * len_right;
tr[u << 1 | 1].set_lazy = set_val;
tr[u << 1 | 1].add_lazy = 0;
tr[u << 1 | 1].mul_lazy = 1;
tr[u].set_lazy = inf;
}
if (tr[u].mul_lazy != 1) {
// 下传乘法标记
int mul_val = tr[u].mul_lazy;
tr[u << 1].mx *= mul_val;
tr[u << 1].sum *= mul_val;
tr[u << 1].mul_lazy *= mul_val;
tr[u << 1].add_lazy *= mul_val;
tr[u << 1 | 1].mx *= mul_val;
tr[u << 1 | 1].sum *= mul_val;
tr[u << 1 | 1].mul_lazy *= mul_val;
tr[u << 1 | 1].add_lazy *= mul_val;
tr[u].mul_lazy = 1;
}
if (tr[u].add_lazy != 0) {
// 下传加法标记
int add_val = tr[u].add_lazy;
tr[u << 1].mx += add_val;
tr[u << 1].sum += add_val * len_left;
tr[u << 1].add_lazy += add_val;
tr[u << 1 | 1].mx += add_val;
tr[u << 1 | 1].sum += add_val * len_right;
tr[u << 1 | 1].add_lazy += add_val;
tr[u].add_lazy = 0;
}
}
// 区间赋值操作
void set_range(int l, int r, int val, int u) {
if (tr[u].l > r || tr[u].r < l) return;
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].mx = val;
tr[u].sum = val * (tr[u].r - tr[u].l + 1);
tr[u].set_lazy = val;
tr[u].add_lazy = 0;
tr[u].mul_lazy = 1;
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) set_range(l, r, val, u << 1);
if (r > mid) set_range(l, r, val, u << 1 | 1);
pushup(u);
}
// 区间加法操作
void add_range(int l, int r, int val, int u) {
if (tr[u].l > r || tr[u].r < l) return;
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].mx += val;
tr[u].sum += val * (tr[u].r - tr[u].l + 1);
tr[u].add_lazy += val;
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) add_range(l, r, val, u << 1);
if (r > mid) add_range(l, r, val, u << 1 | 1);
pushup(u);
}
// 区间乘法操作
void mul_range(int l, int r, int val, int u) {
if (tr[u].l > r || tr[u].r < l) return;
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].mx *= val;
tr[u].sum *= val;
tr[u].mul_lazy *= val;
tr[u].add_lazy *= val;
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) mul_range(l, r, val, u << 1);
if (r > mid) mul_range(l, r, val, u << 1 | 1);
pushup(u);
}
// 区间最大值查询
int query_max(int l, int r, int u) {
if (tr[u].l > r || tr[u].r < l) return -inf;
if (l <= tr[u].l && tr[u].r <= r) return tr[u].mx;
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
int res = -inf;
if (l <= mid) res = max(res, query_max(l, r, u << 1));
if (r > mid) res = max(res, query_max(l, r, u << 1 | 1));
return res;
}
// 区间和查询
int query_sum(int l, int r, int u) {
if (tr[u].l > r || tr[u].r < l) return 0;
if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
int res = 0;
if (l <= mid) res += query_sum(l, r, u << 1);
if (r > mid) res += query_sum(l, r, u << 1 | 1);
return res;
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
build(1, n, 1);
while (m--) {
int op, l, r, x;
cin >> op;
if (op == 1) {
// 区间赋值
cin >> l >> r >> x;
set_range(l, r, x, 1);
} else if (op == 2) {
// 区间加法
cin >> l >> r >> x;
add_range(l, r, x, 1);
} else if (op == 3) {
// 区间乘法
cin >> l >> r >> x;
mul_range(l, r, x, 1);
} else if (op == 4) {
// 区间最大值查询
cin >> l >> r;
cout << query_max(l, r, 1) << endl;
} else if (op == 5) {
// 区间和查询
cin >> l >> r;
cout << query_sum(l, r, 1) << endl;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t; // 多组数据时取消注释
while (t--) {
solve();
}
return 0;
}
求割点 洛谷3388
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 100005;
const int inf = LLONG_MAX;
const double eps = 1e-6;
const int mod = 1e9+7;
/*
low[v] 表示节点 v 通过 DFS遍历 所能到达的 最小的dfn值(即最早被访问的祖先节点)。
具体来说:
dfn[v](深度优先编号)是节点 v 在DFS遍历中的访问顺序(时间戳)。
low[v] 是 v 或其子树中的节点 通过一条反向边(back edge)或横叉边(cross edge)
能到达的最早的 dfn 值。
*/
int n,m;
vector<int>g[N];
//dfn 记录节点v是第几个被DFS访问的(时间戳)
//low 记录v能绕回到的最早的dfn值
//stack 存放当前可能属于同一个强连通分量的节点
//vis[v] 标记v是否在栈中
//color 给同一个强连通分量的节点染相同的颜色
int color[N],vis[N],stk[N],dfn[N],low[N],cnt[N],num[N],cut[N];
//deep节点编号,top栈顶,sum强连通分量数目。
int deep, top, sum, res=0;
int ind[N],outd[N];
void tarjan(int v){
dfn[v]=low[v]=++deep;//初始值相同,首次访问节点时,只能到达自己。
vis[v]=1;//把v压入栈
stk[++top]=v;
for(int i=0;i<g[v].size();i++){
int id=g[v][i];
if(!dfn[id]){//如果没有访问过
tarjan(id);
low[v]=min(low[v],low[id]);// 更新可以到达更早的祖先。
}else{
if(vis[id]){//如果这个点已经被访问且在栈中,说明v可以绕回id。
low[v]=min(low[v],dfn[id]);
}
}
}
if(low[v]==dfn[v]){
color[v]=++sum;
num[sum]++;
vis[v]=0;
while(stk[top]!=v){
color[stk[top]]=sum;//给予同一颜色
vis[stk[top--]]=0;//出栈要修改vis
num[sum]++;
}
top--;
}
}
int tot=0;
void tarjand(int u,int r){
dfn[u]=low[u]=++deep;
int child=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(!dfn[v]){
tarjand(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u] and u!=r){
cut[u]=1;
}
child++;
}else if(v!=r){
low[u]=min(low[u],dfn[v]);
}
}
if(child>=2 and u==r){
cut[r]=1;
}
}
void sovle()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjand(i,i);
}
}
for(int i=1;i<=n;i++){
if(cut[i]){
tot++;
}
}
cout<<tot<<endl;
for(int i=1;i<=n;i++){
// cout<<cut[i]<<" ";
if(cut[i]){
cout<<i<<' ';
}
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin>>ING;
while (ING--)
{
sovle();
}
return 0;
}
缩点

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 100005;
const int inf = LLONG_MAX;
const double eps = 1e-6;
const int mod = 1e9+7;
int n,m;
vector<int>g[N],dag[N];
//dfn 记录节点v是第几个被DFS访问的(时间戳)
//low 记录v能绕回到的最早的dfn值
//stack 存放当前可能属于同一个强连通分量的节点
//vis[v] 标记v是否在栈中
//color 给同一个强连通分量的节点染相同的颜色
int color[N],vis[N],stk[N],dfn[N],low[N],cnt[N],num[N],cut[N],ans[N],a[N],sum_w[N],dp[N];
//deep节点编号,top栈顶,sum强连通分量数目。
int deep, top, sum, res=0;
int ind[N],outd[N];
void tarjan(int v){
dfn[v]=low[v]=++deep;//初始值相同,首次访问节点时,只能到达自己。
vis[v]=1;//把v压入栈
stk[++top]=v;
for(int i=0;i<g[v].size();i++){
int id=g[v][i];
if(!dfn[id]){//如果没有访问过
tarjan(id);
low[v]=min(low[v],low[id]);// 更新可以到达更早的祖先。
}else{
if(vis[id]){//如果这个点已经被访问且在栈中,说明v可以绕回id。
low[v]=min(low[v],dfn[id]);
}
}
}
if(low[v]==dfn[v]){
color[v]=++sum;
sum_w[sum]=a[v];
num[sum]++;
vis[v]=0;
while(stk[top]!=v){
color[stk[top]]=sum;//给予同一颜色
sum_w[sum]+=a[stk[top]];
vis[stk[top--]]=0;//出栈要修改vis
num[sum]++;
}
vis[stk[top--]]=0;
}
}
int tot=0;
void tarjand(int u,int r){
dfn[u]=low[u]=++deep;
int child=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(!dfn[v]){
tarjand(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u] and u!=r){
cut[u]=1;
}
child++;
}else if(v!=r){
low[u]=min(low[u],dfn[v]);
}
}
if(child>=2 and u==r){
cut[r]=1;
}
}
void sovle()
{
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(color, 0, sizeof(color));
memset(vis, 0, sizeof(vis));
memset(ans, 0, sizeof(ans));
deep = top = sum = 0;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
g[x].push_back(y);
// g[y].push_back(x);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int u=1;u<=n;u++){
for(int v:g[u]){
if(color[u]!=color[v]){
dag[color[u]].push_back(color[v]);
}
}
}
vector<int>ind(sum+1,0);
queue<int>q;
for(int u=1;u<=sum;u++){
for(int v:dag[u]){
ind[v]++;
}
}
for(int u=1;u<=sum;u++){
if(ind[u]==0) q.push(u);
dp[u]=sum_w[u];
}
int ans=0;
while(!q.empty()){
int u=q.front();q.pop();
ans=max(ans,dp[u]);
for(int v:dag[u]){
dp[v]=max(dp[v],dp[u]+sum_w[v]);
if(--ind[v]==0){
q.push(v);
}
}
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin>>ING;
while (ING--)
{
sovle();
}
return 0;
}
输出有向无向图环路的办法
点击查看代码
#include <vector>
#include <iostream>
using namespace std;
vector<int> adj[10005]; // 邻接表
bool vis[10005]; // 访问标记
vector<int> path; // 当前DFS路径
vector<int> cycle; // 存储环路径
bool dfs(int u, int parent) {
vis[u] = true;
path.push_back(u);
for (int v : adj[u]) {
if (!vis[v]) {
if (dfs(v, u)) return true; // 递归子节点
} else if (v != parent) { // 发现环
// 从路径中找到v的位置,截取环
auto it = find(path.begin(), path.end(), v);
cycle = vector<int>(it, path.end());
cycle.push_back(v); // 闭环
return true;
}
}
path.pop_back(); // 回溯
return false;
}
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u); // 无向图
}
for (int i = 1; i <= n; i++) {
if (!vis[i] && dfs(i, -1)) break; // 从i开始DFS
}
if (!cycle.empty()) {
cout << "Cycle found: ";
for (int u : cycle) cout << u << " ";
cout << endl;
} else {
cout << "No cycle" << endl;
}
return 0;
}
////////////////////////////////////////////////////////////
#include <vector>
#include <iostream>
using namespace std;
vector<int> adj[10005]; // 邻接表
bool vis[10005]; // 访问标记
bool recStack[10005]; // 递归栈标记
vector<int> path; // 当前DFS路径
vector<int> cycle; // 存储环路径
bool dfs(int u) {
vis[u] = true;
recStack[u] = true; // 加入递归栈
path.push_back(u);
for (int v : adj[u]) {
if (!vis[v]) {
if (dfs(v)) return true; // 递归子节点
} else if (recStack[v]) { // 发现环
// 从路径中找到v的位置,截取环
auto it = find(path.begin(), path.end(), v);
cycle = vector<int>(it, path.end());
cycle.push_back(v); // 闭环
return true;
}
}
recStack[u] = false; // 退出递归栈
path.pop_back(); // 回溯
return false;
}
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v); // 有向图
}
for (int i = 1; i <= n; i++) {
if (!vis[i] && dfs(i)) break; // 从i开始DFS
}
if (!cycle.empty()) {
cout << "Cycle found: ";
for (int u : cycle) cout << u << " ";
cout << endl;
} else {
cout << "No cycle" << endl;
}
return 0;
}
//////////////////////////////////////////////////////////////////////////////
vector<int>adj[100005];
int ind[100005];//储存有向边的终点。
bool has(int n){
queue<int>q;
int cnt=0;
for(int i=1;i<=n;i++){
if(ind[i]==0){
q.push(i);
}
}
while(!q.empty()){
int u=q.front();q.pop();
cnt++;
for(int v:adj[u]){
if((--ind[v])==0){
q.push(v);
}
}
}
return cnt!=n;
}
Kruskal 重构树
Kruskal 重构树是一种基于 Kruskal 算法(用于求解最小生成树,MST)的树结构,主要用于解决 图上两点间路径的最值问题(如最小瓶颈路、最大边权限制等)。它在 离线处理连通性问题 和 处理带权图的查询问题 时非常有用。

用途
(1) 最小瓶颈路(Minimax Path)
问题:查询 u 到 v 的所有路径中,最大边权的最小值。
解法:Kruskal 重构树的 LCA 权值就是答案。
(2) 两点是否在某个权值限制下连通
问题:给定权值 w,问 u 和 v 能否只经过权值 ≤w 的边连通。
解法:在重构树上找到 u 和 v 的权值 ≤w 的祖先,检查是否相同。
(3) 离线处理连通性问题
问题:动态加边,查询两点何时连通。
解法:构建 Kruskal 重构树后,LCA 的权值就是它们首次连通的时刻。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Edge {
int u, v, w;
bool operator<(const Edge &e) const { return w < e.w; }
} edges[N];
int n, m;
int parent[N << 1]; // 原始节点 + 新建虚拟节点
int val[N << 1]; // 节点权值
vector<int> tree[N << 1]; // 重构树
int cnt; // 当前节点数
int find(int x) {
return parent[x] == x ? x : parent[x] = find(parent[x]);
}
void build_kruskal_tree() {
sort(edges + 1, edges + m + 1);
cnt = n;
for (int i = 1; i <= 2 * n; i++) parent[i] = i;
for (int i = 1; i <= m; i++) {
int u = edges[i].u, v = edges[i].v, w = edges[i].w;
int fu = find(u), fv = find(v);
if (fu != fv) {
cnt++;
val[cnt] = w;
parent[fu] = parent[fv] = cnt;
tree[cnt].push_back(fu);
tree[cnt].push_back(fv);
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> edges[i].u >> edges[i].v >> edges[i].w;
}
build_kruskal_tree();
// 现在 cnt 是重构树的根
return 0;
}
P4768 [NOI2018] 归程
CF1416D Graph and Queries
https://codeforces.com/gym/105789/problem/D
解题代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 1e6+5;
const int inf = LLONG_MAX;
const double eps = 1e-6;
const int mod = 1e9+7;
int n,m;
struct edge{
int x,y,val;
bool operator<(const edge&A) const{
return val<A.val;
}
}e[N];
int a[N],fa[N],val[N],idx,siz[N],chr[N][2];
int res[N];
int find(int x){
if(x==fa[x]){
return fa[x];
}
return fa[x]=find(fa[x]);
}
void dfs(int x){
int ls=chr[x][0],rs=chr[x][1];
if(ls==0) return;
res[ls]=res[x]+val[x]*siz[rs];
res[rs]=res[x]+val[x]*siz[ls];
dfs(ls);dfs(rs);
}
void miaojiachun() {
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
fa[i]=i;siz[i]=1;
}
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
e[i]={x,y,max(a[x],a[y])};
}
sort(e+1,e+1+m);
idx=n;
for(int i=1;i<=m;i++){
int x=e[i].x,y=e[i].y,v=e[i].val;
int rx=find(x),ry=find(y);
if(rx==ry){
continue;
}
idx+=1;
fa[rx]=fa[ry]=fa[idx]=idx;//rx和ry现在属于同一个连通块,根节点是idx
//fa[idx]=idx表示idx是当前连通块的根。
chr[idx][0]=rx;
chr[idx][1]=ry;
siz[idx]=siz[rx]+siz[ry];
val[idx]=v;
}
dfs(idx);
for(int i=1;i<=n;i++){
cout<<a[i]+res[i]<<" ";
}
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin>>ING;
while (ING--) {
miaojiachun();
}
return 0;
}

f(u,v)是u到v的所有路径中,路径上最大危险值的最小值(最小瓶颈路径)。
kruskal重构树核心思想:
最小生成树性质:在任意图中,u到v的最小瓶颈路径一定存在于其最小生成树中。
我们建立Kruskal重构树,我们利用并查集来合并连通块,我们先按边权对边进行从小到大排序。这里的边权我们按题目要求,选取路径两端点的最大危险分数。
每次合并创建一个虚拟父节点,权值为当前边的权值,最终形成一颗二叉树,叶子节点是原始路口,非叶子结点是虚拟节点。
重构树的性质:
对于任意两个叶子节点u和v,他们LCA的权值就是f(u,v)。
因为LCA是u和v在重构树中第一次被合并的虚拟节点,该节点的权值就是连接他们的最小瓶颈值。
对于每个节点,我们记录其左右子树的大小。
对于每个虚拟节点x,其权值val[x]会影响所有跨越其左右子树的点对。
左子树的每个节点到右子树的每个结点的f值至少是val[x].
因此,val[x]对左子树的贡献是val[x]右子树的大小。
val[x]对右子树的贡献是val[x]左子树的大小。
并查集板子
点击查看代码
#include <bits/stdc++.h>
using namespace std;
// 你给出的 DSU 定义:
class DSU {
private:
vector<int> parent, rank, size;
int count;
public:
DSU(int n) : count(n) {
parent.resize(n+1);
rank.resize(n+1, 0);
size.resize(n+1, 1);
for (int i = 1; i <= n; ++i) parent[i] = i;
}
int find(int x) {
return parent[x] == x ? x
: parent[x] = find(parent[x]);
}
void unionSet(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry) return;
if (rank[rx] < rank[ry]) {
parent[rx] = ry;
size[ry] += size[rx];
} else if (rank[rx] > rank[ry]) {
parent[ry] = rx;
size[rx] += size[ry];
} else {
parent[ry] = rx;
size[rx] += size[ry];
rank[rx]++;
}
count--;
}
bool connected(int x, int y) {
return find(x) == find(y);
}
int getCount() const {
return count;
}
int getSize(int x) {
return size[find(x)];
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
// 假设输入:n 个点,m 条边
cin >> n >> m;
DSU dsu(n);
// 读入一批无向边,把它们所在的点合并
for (int i = 0; i < m; i++){
int u, v;
cin >> u >> v;
dsu.unionSet(u, v);
}
// 1) 查询连通分量的数量
cout << "连通分量数 = " << dsu.getCount() << "\n";
// 2) 判断两个点是否在同一个集合里
int a, b;
cin >> a >> b;
if (dsu.connected(a, b))
cout << a << " 和 " << b << " 在同一个连通块\n";
else
cout << a << " 和 " << b << " 不在同一个连通块\n";
// 3) 查询某个点所在集合的大小
int x;
cin >> x;
cout << "点 " << x << " 所在集合的大小 = " << dsu.getSize(x) << "\n";
return 0;
}
SPAF模板(负边权最短路)
在不存在负权环的图中,任意节点的最短路径最多包含n-1条边。因此,一条边最多被松弛n-1次(即最多入队n-1次),如果某个节点v的瑞对次数≥n,说明他被反复松弛,意味着存在一条无限缩短的路径(负权环).
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
const int INF = INT_MAX;
vector<int> spfa(vector<vector<pair<int, int>>>& graph, int start, int n) {
vector<int> dist(n + 1, INF); // 存储起点到各点的最短距离
vector<int> cnt(n + 1, 0); // 记录每个节点的入队次数
vector<bool> in_queue(n + 1, false); // 标记节点是否在队列中
queue<int> q;
dist[start] = 0;
q.push(start);
in_queue[start] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
in_queue[u] = false;
for (auto& edge : graph[u]) {
int v = edge.first;
int w = edge.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (!in_queue[v]) {
q.push(v);
in_queue[v] = true;
cnt[v]++;
if (cnt[v] >= n) {
// 检测到负权环,返回空数组
return vector<int>();
}
}
}
}
}
return dist;
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> graph(n + 1); // 邻接表:graph[u] = { (v1, w1), (v2, w2), ... }
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
graph[u].push_back({v, w});
}
int start = 1; // 假设起点是1
vector<int> dist = spfa(graph, start, n);
if (dist.empty()) {
cout << "图中存在负权环,无法计算最短路径!" << endl;
} else {
for (int i = 1; i <= n; ++i) {
if (dist[i] == INF) {
cout << "从 " << start << " 到 " << i << " 不可达" << endl;
} else {
cout << "从 " << start << " 到 " << i << " 的最短距离: " << dist[i] << endl;
}
}
}
return 0;
}
//#pragma GCC optimize(1)
//#pragma GCC optimize(2)
//#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
const int inf=LLONG_MAX;
const int N=2e5+10;
//int n,m;
int n, m;
vector<vector<pii>> g(N);
vector<int> spfa(int str, int n){
vector<int> dist(n + 1, inf);
vector<int> cnt(n + 1, 0);//记录每个节点如队次数。
vector<int> vis(n + 1, 0);//标记每个点是否在队列中。
queue<int> q;
dist[str] = 0;
q.push(str);
vis[str] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (auto& t : g[u]){
int v = t.first;
int w = t.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
cnt[v]++;
if(cnt[v] >= n){
return vector<int>();
}
}
}
}
}
return dist;
}
void solve() {
cin >> n >> m;
for(int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
}
int str = 1;
vector<int> dist = spfa(str, n);
if(dist[n] == inf) {
cout << "impossible" << endl;
}else {
cout << dist[n] << endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
拓扑排序
在算法竞赛中,拓扑排序是一个非常重要的算法,常用于解决以下经典问题或作为关键步骤辅助解题:
· 1. 判断有向图是否有环
- 典型问题:给定有向图,判断是否存在环。
- 解法:直接跑拓扑排序,如果能生成完整的拓扑序列(长度为
n),则无环;否则有环。 - 例题:
- LeetCode 207. Course Schedule
- 判断有向图是否能完成所有任务(Codeforces 510C)
· 2. DAG上的动态规划(DP)
- 典型问题:在DAG上求最长路径、最短路径或计数问题。
- 解法:先拓扑排序,再按拓扑序DP。
- 例题:
- LeetCode 329. Longest Increasing Path in a Matrix(将矩阵转化为DAG)
- DAG上的最长路径(AtCoder DP Contest G)
· 3. 字典序最小拓扑序
- 典型问题:在拓扑排序不唯一时,输出字典序最小的序列。
- 解法:用优先队列(最小堆)代替普通队列。
- 例题:
· 4. 分层图或依赖关系问题
- 典型问题:需要按依赖关系分层处理(如课程表、任务调度)。
- 解法:拓扑排序后按层处理。
- 例题:
- LeetCode 210. Course Schedule II
- 并行任务调度(洛谷 P1113)
· 5. 强连通分量(SCC)分解的前置步骤
- 典型问题:Kosaraju算法中需要逆后序排列。
- 解法:拓扑排序的逆序是Kosaraju算法的关键步骤。
- 例题:
· 6. 关键路径(AOE网络)
- 典型问题:求工程的最早/最晚开始时间。
- 解法:拓扑排序后正向和反向各跑一次DP。
- 例题:
- 关键路径问题模板题(洛谷 P4017)
· 7. 竞赛中的特殊构造题
- 典型问题:需要通过拓扑序构造合法解。
- 解法:利用拓扑序的性质构造答案。
- 例题:
- Codeforces Round #656 (Div. 3) G. Columns Switches(通过拓扑序确定翻转顺序)
· 8. 2-SAT问题
- 典型问题:布尔变量的约束满足问题。
- 解法:用拓扑排序确定变量的赋值顺序。
- 例题:
· 竞赛技巧总结
-
拓扑排序+BFS/DFS:
- 大部分问题直接用Kahn算法(BFS+入度表)即可。
- 字典序最小拓扑序用优先队列优化。
-
拓扑排序+DP:
- DAG上的DP必须按拓扑序计算,确保无后效性。
-
拓扑排序判环:
- 如果最终拓扑序列长度
< n,说明有环。
- 如果最终拓扑序列长度
-
扩展应用:
- 结合SCC、2-SAT等高级算法时,拓扑序常作为中间步骤。
· 模板代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
vector<int> topological_sort(vector<vector<int>>& adj, vector<int>& in_degree) {
int n = adj.size();
vector<int> res;
queue<int> q;
for (int i = 0; i < n; ++i) {
if (in_degree[i] == 0) q.push(i);
}
while (!q.empty()) {
int u = q.front();
q.pop();
res.push_back(u);
for (int v : adj[u]) {
if (--in_degree[v] == 0) {
q.push(v);
}
}
}
return res.size() == n ? res : vector<int>();
}
树状数组单点修改,区间求和。
例题
https://codeforces.com/contest/1915/problem/F
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 2e5 + 100;
const int inf = LLONG_MAX;
const double eps = 1e-6;
const int mod = 1e9+7;
#define lowbit(x) (x&(-x))
struct xx {
int x, tag, num;
bool operator< (const xx& t) const{return x < t.x;}
}c[N * 2];
int n, a[N], b[N], d[N];
inline void update(int x, int f) {//单点修改
for (int i = x; i <= N; i += lowbit(i)) {
d[i] += f;
}
}
inline int query(int x) {//统计区间【1,x】存在的元素个数
int ans = 0;
for (int i = x; i; i -= lowbit(i)) {
ans += d[i];
}
return ans;
}
//int search(int l, int r) {
// int ans = 0;
// for (int i = l - 1; i; i -= lowbit(i)) {
// ans -= d[i];
// }
// for (int i = r; i; i -= lowbit(i)) {
// ans += d[i];
// }
// return ans;
//}
void miaojiachun() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
c[i] = {a[i], 1, i};
c[i + n] = {b[i], -1, i};
}
n <<= 1;
sort(c + 1, c + 1 + n);//升序排列。
int ans = 0, cur = 0;//cur表示入边的先后
memset(d, 0, sizeof d);
for (int i = 1; i <= n; i++) {
if (c[i].tag == 1) {
update(++cur, 1);
a[c[i].num] = cur;
}else {
ans += query(a[c[i].num]);
update(a[c[i].num], -1);
}
}
cout << ans - n/2 << endl;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
cin >> ING;
while (ING--) {
miaojiachun();
}
return 0;
}
马拉车
点击查看代码
string manacher(const string& s) {
// 预处理字符串,插入特殊字符如#,处理奇偶长度回文
string t = "#";
for (char c : s) {
t += c;
t += '#';
}
int n = t.size();
vector<int> p(n, 0); // p[i]表示以i为中心的最长回文半径
int center = 0; // 当前中心位置
int right = 0; // 当前回文串右边界
for (int i = 1; i < n; ++i) {
// 利用对称性快速初始化p[i]
int mirror = 2 * center - i;
if (i < right) {
p[i] = min(right - i, p[mirror]);
}
// 尝试扩展回文半径
int l = i - (1 + p[i]);
int r = i + (1 + p[i]);
while (l >= 0 && r < n && t[l] == t[r]) {
p[i]++;
l--;
r++;
}
// 如果扩展后的回文串右边界超过当前右边界,则更新中心和右边界
if (i + p[i] > right) {
center = i;
right = i + p[i];
}
}
// 找到最长回文子串
int max_len = 0;
int start = 0;
for (int i = 0; i < n; ++i) {
if (p[i] > max_len) {
max_len = p[i];
start = (i - max_len) / 2;
}
}
return s.substr(start, max_len);
}
multiset求数组中位数
点击查看代码
template<typename T>
class Median{
public:
multiset<T> mn,mx;//双multiset实现全部数的储存,在保持两个set元素数量一样的情况下则双端元素为中位数
void equal();//使两个set容器的元素数量相等
void insert(T cur);//插入元素
void delet(T cur);//删除元素
T find();//找中位数
void clear();
};
template<typename T>
void Median<T>::clear() {
mn.clear();
mx.clear();
}
template<typename T>
void Median<T>:: equal(){
int n1=mn.size();
int n2=mx.size();
if(n2==n1) return;
if(n2+1==n1) return;
int ad=0;
if((n2+n1)%2==1) ad++;
while(n1<n2+ad){
T fr=*(mx.begin());
mx.erase(mx.begin());
mn.insert(fr);
n1++;
n2--;
}
while(n2+ad<n1){
auto it=mn.rbegin();
T bc=*(it);
auto itt=it.base();
mn.erase(--itt);
mx.insert(bc);
n2++;
n1--;
}
}
template<typename T>
void Median<T>:: insert(T cur){
equal();
if(mn.size()==0){
mn.insert(cur);
return;
}
int bc=*(mn.rbegin());
if(cur<=bc){
mn.insert(cur);
}
else{
mx.insert(cur);
}
equal();
}
template<typename T>
void Median<T>:: delet(T cur){
auto it=mn.find(cur);
if(it!=mn.end()){
mn.erase(it);
equal();
return;
}
it=mx.find(cur);
if(it!=mx.end()){
mx.erase(it);
equal();
return;
}
}
template<typename T>
T Median<T>:: find(){
equal();
int n1=mn.size();
int n2=mx.size();
if(n2==0) return *(mn.rbegin());
T fr=*(mx.begin());
T bc=*(mn.rbegin());
if(n1==n2) return (bc);
else{
if(n1>n2) return bc;
else return fr;
}
}
二叉索引树
点击查看代码
template<typename T>
struct Fenwick {
const int n;
std::vector<T> a;
Fenwick(int n) : n(n), a(n) {}
// 在位置x增加v
void add(int x, T v) {
for (int i = x + 1; i <= n; i += i & -i)
a[i - 1] += v;
}
// 计算前x个元素的和
T sum(int x) {
T ans = 0;
for (int i = x; i > 0; i -= i & -i)
ans += a[i - 1];
return ans;
}
// 计算区间[l,r)的和
T rangeSum(int l, int r) {
return sum(r) - sum(l);
}
};
主席树求数组中有多少数处于区间范围内
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 1e5 + 7;
const int inf = LLONG_MAX;
const double eps = 1e-6;
const int mod = 1e9+7;
int n, q;
int a[N];
struct lp{
int l, r, sum;
}tree[N << 5];
int b[N], root[N];
int cnt = 0;
int build(int pl, int pr) {
int rt = ++cnt;
tree[rt].sum = 0;
int mid = (pl + pr) >> 1;
if (pl < pr) {
tree[rt].l = build(pl, mid);
tree[rt].r = build(mid + 1, pr);
}
return rt;
}
int update(int pre, int pl, int pr, int x) {
int rt = ++cnt;
tree[rt].l = tree[pre].l;
tree[rt].r = tree[pre].r;
tree[rt].sum = tree[pre].sum + 1;
if (pl == pr) {
return rt;
}
int mid = (pl + pr) >> 1;
if (x <= mid) {
tree[rt].l = update(tree[pre].l, pl, mid, x);
}else {
tree[rt].r = update(tree[pre].r, mid + 1, pr, x);
}
return rt;
}
int query(int u, int v, int pl, int pr, int k) {
if (pl == pr) {
return tree[v].sum - tree[u].sum;
}
// int x = tree[tree[v].l].sum - tree[tree[u].l].sum;
int mid = (pl + pr) >> 1;
if (mid >= k) {
return query(tree[u].l, tree[v].l, pl, mid, k);
}else {
int ans = tree[tree[v].l].sum - tree[tree[u].l].sum;
return ans + query(tree[u].r, tree[v].r, mid + 1, pr, k);
}
}
void miaojiachun() {
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + n + 1);
int size = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b + 1, b + 1 + size, a[i]) - b;
}
int mx1 = *max_element(a + 1, a + n + 1);
root[0] = 0;
for (int i = 1; i <= n; i++) {
root[i] = update(root[i - 1], 1, mx1, a[i]);
}
while(q--) {
int l, r, str, ed;
cin >> l >> r >> str >> ed;
int ql = lower_bound(b + 1, b + mx1 + 1, str) - b;
int qr = upper_bound(b + 1, b + mx1 + 1, ed) - b - 1;
if (ql > qr) {
cout << 0 << endl;
continue;
}
int ans = query(root[l - 1], root[r], 1, mx1, qr) -
(ql > 1 ? query(root[l - 1], root[r], 1, mx1, ql - 1) : 0);
cout << ans << endl;
}
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin>>ING;
while (ING--) {
miaojiachun();
}
return 0;
}
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 1e5 + 7;
const int inf = LLONG_MAX;
const double eps = 1e-6;
const int mod = 1e9+7;
int n, q;
int a[N], b[N], root[N], cnt;
struct Node {
int l, r, sum;
} tree[N << 5];
// 离散化函数
void discretize() {
sort(b + 1, b + n + 1);
int size = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(b + 1, b + size + 1, a[i]) - b;
}
}
// 更新主席树
int update(int pre, int pl, int pr, int x) {
int rt = ++cnt;
tree[rt] = tree[pre];
tree[rt].sum++;
if (pl == pr) return rt;
int mid = (pl + pr) >> 1;
if (x <= mid) {
tree[rt].l = update(tree[pre].l, pl, mid, x);
} else {
tree[rt].r = update(tree[pre].r, mid + 1, pr, x);
}
return rt;
}
// 查询区间内小于等于k的数的个数
int query(int u, int v, int pl, int pr, int k) {
if (pl == pr) {
return tree[v].sum - tree[u].sum;
}
int mid = (pl + pr) >> 1;
if (k <= mid) {
return query(tree[u].l, tree[v].l, pl, mid, k);
} else {
int ans = tree[tree[v].l].sum - tree[tree[u].l].sum;
return ans + query(tree[u].r, tree[v].r, mid + 1, pr, k);
}
}
void solve() {
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
}
// 离散化处理
discretize();
int max_val = *max_element(a + 1, a + n + 1);
// 构建主席树
root[0] = 0;
for (int i = 1; i <= n; i++) {
root[i] = update(root[i-1], 1, max_val, a[i]);
}
while (q--) {
int l, r, str, ed;
cin >> l >> r >> str >> ed;
// 将查询值也离散化
int ql = lower_bound(b + 1, b + max_val + 1, str) - b;
int qr = upper_bound(b + 1, b + max_val + 1, ed) - b - 1;
if (ql > qr) {
cout << 0 << endl;
continue;
}
int ans = query(root[l-1], root[r], 1, max_val, qr) -
(ql > 1 ? query(root[l-1], root[r], 1, max_val, ql-1) : 0);
cout << ans << endl;
}
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}
旋转卡壳
旋转卡壳算法(Rotating Calipers)的应用场景
旋转卡壳是一种在计算几何中用于处理凸多边形或凸包问题的高效算法,它通过"旋转"一对平行卡尺来扫描凸包,解决多种几何优化问题。
主要应用问题
-
计算凸包直径(最远点对)
问题:找到凸包上距离最远的两个点
解法:用旋转卡壳寻找对踵点对(antipodal points)
复杂度:O(n)(凸包已构建的情况下) -
计算凸多边形宽度
问题:找到凸多边形的最小宽度(平行线间的最小距离)
解法:旋转卡尺寻找最小距离的平行切线对 -
计算最小面积/周长外接矩形
问题:找到能包围凸多边形的最小面积或最小周长的矩形
解法:旋转卡尺确定矩形的边与凸包的切线关系 -
多边形间最大距离
问题:计算两个凸多边形之间的最大距离
解法:在两个凸包的合并凸包上应用旋转卡壳 -
凸多边形合并
问题:合并两个凸多边形为一个新的凸包
解法:配合旋转卡壳技术高效合并 -
线段与凸多边形相交判定
问题:判断线段是否与凸多边形相交
解法:利用旋转卡壳快速排除不可能情况 -
最大空圆问题
问题:在给定点集中找到最大半径的空圆(不包含任何输入点)
解法:基于凸包和旋转卡壳技术
算法竞赛中的典型应用
最远点对问题(凸包直径)
给定平面点集,求两点间最大距离
先求凸包,再用旋转卡壳找直径
最小包围矩形问题
求包围所有点的最小面积/周长矩形
旋转卡壳确定矩形方向
碰撞检测
判断两个凸多边形是否可能相交
使用旋转卡壳计算分离轴
洛谷P1452
计算凸包直径(最远点对)
点击查看代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
struct Point {
ll x, y;
Point(ll x = 0, ll y = 0) : x(x), y(y) {}
// 向量减法
Point operator-(const Point& p) const {
return Point(x - p.x, y - p.y);
}
// 向量叉积
ll cross(const Point& p) const {
return x * p.y - y * p.x;
}
// 向量点积
ll dot(const Point& p) const {
return x * p.x + y * p.y;
}
// 距离平方
ll dist2() const {
return x * x + y * y;
}
};
// 计算凸包,使用Andrew算法
vector<Point> convexHull(vector<Point>& points) {
int n = points.size();
if (n <= 1) return points;
sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
});
vector<Point> hull;
for (int i = 0; i < 2; i++) {
int start = hull.size();
for (Point& p : points) {
while (hull.size() >= start + 2 &&
(hull.back() - hull[hull.size()-2]).cross(p - hull.back()) <= 0) {
hull.pop_back();
}
hull.push_back(p);
}
hull.pop_back();
reverse(points.begin(), points.end());
}
return hull;
}
// 旋转卡壳法求凸包直径平方
ll rotatingCalipers(const vector<Point>& hull) {
int n = hull.size();
if (n == 1) return 0;
if (n == 2) return (hull[0] - hull[1]).dist2();
ll max_dist2 = 0;
int j = 1; // 对踵点指针
for (int i = 0; i < n; i++) {
int next_i = (i + 1) % n;
// 寻找下一个对踵点
while (true) {
int next_j = (j + 1) % n;
// 比较三角形面积(叉积)来确定是否移动j
ll cross = (hull[next_i] - hull[i]).cross(hull[next_j] - hull[j]);
if (cross <= 0) break;
j = next_j;
}
// 计算当前对踵点对的距离平方
ll dist2 = (hull[i] - hull[j]).dist2();
if (dist2 > max_dist2) {
max_dist2 = dist2;
}
}
return max_dist2;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<Point> points(n);
for (int i = 0; i < n; i++) {
cin >> points[i].x >> points[i].y;
}
vector<Point> hull = convexHull(points);
ll diameter2 = rotatingCalipers(hull);
cout << diameter2 << endl;
return 0;
}
计算凸多边形宽度
点击查看代码
double minWidth(const vector<Point>& hull) {
int n = hull.size();
if (n <= 1) return 0;
if (n == 2) return sqrt((hull[0]-hull[1]).dist2());
double min_width = 1e18;
int j = 1;
for (int i = 0; i < n; i++) {
Point edge = hull[(i+1)%n] - hull[i];
while (abs(edge.cross(hull[(j+1)%n]-hull[j])) >
abs(edge.cross(hull[j]-hull[(j-1+n)%n])))
j = (j+1)%n;
double dist = abs(edge.cross(hull[j]-hull[i])) / sqrt(edge.dist2());
min_width = min(min_width, dist);
}
return min_width;
}
最小面积外接矩形
点击查看代码
struct Line { Point a, b; };
double minAreaBoundingBox(const vector<Point>& hull) {
int n = hull.size();
if (n <= 2) return 0;
double min_area = 1e18;
vector<Line> edges(n);
for (int i = 0; i < n; i++)
edges[i] = {hull[i], hull[(i+1)%n]};
auto caliper = [](const Line& base, const Point& p) {
Point dir = base.b - base.a;
return abs(dir.cross(p - base.a)) / sqrt(dir.dist2());
};
int left = 0, right = 0, top = 0;
for (int i = 0; i < n; i++) {
Point edge = edges[i].b - edges[i].a;
while (edge.dot(edges[(right+1)%n].b - edges[right].a) > 0)
right = (right+1)%n;
while (top < n && edge.cross(edges[(top+1)%n].b - edges[top].a) > 0)
top = (top+1)%n;
while (edge.dot(edges[(left+1)%n].b - edges[left].a) < 0)
left = (left+1)%n;
double height = caliper(edges[i], hull[top]);
Point left_p = hull[left], right_p = hull[right];
double width = sqrt((right_p - left_p).dist2());
min_area = min(min_area, width * height);
}
return min_area;
}
多边形间最大距离
点击查看代码
ll maxDistanceBetweenHulls(const vector<Point>& hull1, const vector<Point>& hull2) {
auto combined = hull1;
combined.insert(combined.end(), hull2.begin(), hull2.end());
auto hull = convexHull(combined);
return maxDiameter2(hull);
}
凸多边形合并
点击查看代码
vector<Point> mergeConvexHulls(const vector<Point>& hull1, const vector<Point>& hull2) {
vector<Point> points = hull1;
points.insert(points.end(), hull2.begin(), hull2.end());
return convexHull(points);
}
线段与凸多边形相交判定
点击查看代码
bool segmentIntersectsHull(Point a, Point b, const vector<Point>& hull) {
int n = hull.size();
Point seg_dir = b - a;
// 检查线段是否完全在凸包一侧
int pos = 0, neg = 0;
for (int i = 0; i < n; i++) {
ll cross = seg_dir.cross(hull[i] - a);
if (cross > 0) pos++;
if (cross < 0) neg++;
if (pos && neg) break;
}
if (!pos || !neg) return false;
// 检查凸包边是否与线段相交
for (int i = 0; i < n; i++) {
Point p1 = hull[i], p2 = hull[(i+1)%n];
Point edge = p2 - p1;
ll c1 = seg_dir.cross(p1 - a);
ll c2 = seg_dir.cross(p2 - a);
if ((c1 < 0 && c2 > 0) || (c1 > 0 && c2 < 0)) {
ll c3 = edge.cross(a - p1);
ll c4 = edge.cross(b - p1);
if ((c3 < 0 && c4 > 0) || (c3 > 0 && c4 < 0))
return true;
}
}
return false;
}
最大空圆问题
点击查看代码
double largestEmptyCircle(const vector<Point>& points, Point center) {
auto hull = convexHull(const_cast<vector<Point>&>(points));
int n = hull.size();
if (n == 0) return 0;
if (n == 1) return sqrt((hull[0]-center).dist2());
double min_dist = 1e18;
for (int i = 0; i < n; i++) {
Point a = hull[i], b = hull[(i+1)%n];
Point ab = b - a, ac = center - a;
// 计算点到线段的距离
if (ab.dot(ac) < 0) {
min_dist = min(min_dist, sqrt(ac.dist2()));
} else if ((a-b).dot(center-b) < 0) {
min_dist = min(min_dist, sqrt((center-b).dist2()));
} else {
double area = abs(ab.cross(ac));
double base = sqrt(ab.dist2());
min_dist = min(min_dist, area / base);
}
}
return min_dist;
}
所有代码都基于相同的Point结构体
需要先计算凸包(使用提供的convexHull函数)
24昆明邀请赛M题:
https://codeforces.com/gym/105386/problem/M



点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 1e5 + 7;
const int inf = LLONG_MAX;
const double eps = 1e-9;
const int mod = 1e9+7;
typedef long long ll;
/*
perator- 两个点相减,得到向量 求两点连线的方向向量
cross 两向量叉积,结果是一个标量 求平行四边形面积的2倍,判断转向(左拐/右拐)
dot 两向量点积,结果是一个标量 求夹角余弦值,判断垂直性
*/
struct Point {
ll x, y;
Point(ll x = 0, ll y = 0) : x(x), y(y) {}
// 向量减法
Point operator-(const Point& p) const {
return Point(x - p.x, y - p.y);
}
// 向量叉积
// 求面积,判断左右关系
/*
叉积(cross product可以判断两个向量的相对位置。
两个向量的叉积 A × B:
大于 0:B 在 A 的逆时针方向(也叫左拐)
小于 0:B 在 A 的顺时针方向(也叫右拐)
等于 0:A 和 B 在同一条直线上(共线)
*/
ll cross(const Point& p) const {
return x * p.y - y * p.x;
}
// 向量点积
// 求夹角相关
ll dot(const Point& p) const {
return x * p.x + y * p.y;
}
// 距离平方
ll dist2() const {
return x * x + y * y;
}
ll dist2(const Point& p) const {
return (x - p.x) * (x - p.x) + (y - p.y) * (y - p.y);
}
// 计算实际距离
long double dis(const Point& p) const {
return sqrtl(dist2(p));
}
};
// 计算凸包,使用Andrew算法
vector<Point> convexHull(vector<Point>& points) {
int n = points.size();
if (n <= 1) return points;
sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
});
vector<Point> hull;
for (int i = 0; i < 2; i++) {
int start = hull.size();
for (Point& p : points) {
while (hull.size() >= start + 2 &&
(hull.back() - hull[hull.size()-2]).cross(p - hull.back()) <= 0) {
hull.pop_back();
}
hull.push_back(p);
}
hull.pop_back();
reverse(points.begin(), points.end());
}
return hull;
}
// 旋转卡壳法求凸包直径平方
ll rotatingCalipers(const vector<Point>& hull) {
int n = hull.size();
if (n == 1) return 0;
if (n == 2) return (hull[0] - hull[1]).dist2();
ll max_dist2 = 0;
int j = 1; // 对踵点指针
for (int i = 0; i < n; i++) {
int next_i = (i + 1) % n;
// 寻找下一个对踵点
while (true) {
int next_j = (j + 1) % n;
// 比较三角形面积(叉积)来确定是否移动j
ll cross = (hull[next_i] - hull[i]).cross(hull[next_j] - hull[j]);
if (cross <= 0) break;
j = next_j;
}
// 计算当前对踵点对的距离平方
ll dist2 = (hull[i] - hull[j]).dist2();
if (dist2 > max_dist2) {
max_dist2 = dist2;
}
}
return max_dist2;
}
void solve() {
int n;
cin >> n;
Point o;
int x, y, r;
cin >> x >> y >> r;
o = {x, y};
vector<Point> a(n);
for (int i = 0; i < n; i++) {
cin >> x >> y;
a[i] = {x, y};
}
int j = 1; // 对踵点指针
int now = 0;
int ans = 0;
for (int i = 0; i < n; i++) {
int next_i = (i + 1) % n;
// 寻找下一个对踵点
while (true) {
int next_j = (j + 1) % n;
// 比较三角形面积(叉积)来确定是否移动j
// 判断切割线是否有效。
// 检查菠萝中心o是否在边a[i] a[next_j]的右侧或者边上。
// (边向量).(中心向量)
// 叉积 <=0 表示中心在边的右侧或者共线
// || 后面,检查切割线到菠萝中心的距离是否小于半径。
//
ll cross = (a[next_j] - a[i]).cross(o - a[i]);
if (cross <= 0 || (__int128_t)cross * cross < (__int128_t)r * r * a[i].dist2(a[next_j])) {
break;
}
// 计算当前三角形a[i][j] a[next_j] 的面积 x 2
now += abs((a[j] - a[i]).cross(a[next_j] - a[i]));
j = next_j;
}
ans = max(ans, now);
now -= abs((a[j] - a[i]).cross(a[next_i] - a[i]));
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
数学

欧拉定理:
-
欧拉定理
定理内容
若 $\gcd(a, m) = 1$,则:
$$ a^{\varphi(m)} \equiv 1 \pmod{m} $$其中:
- $\varphi(m)$ 是欧拉函数,表示小于$m$且与$m$互质的正整数的个数
- $\gcd(a,m)$ 表示$a$和$m$的最大公约数
示例
-
当$a=3$,$m=4$时:
- $\varphi(4) = 2$
- $3^2 = 9 \equiv 1 \pmod{4}$
-
当$a=3$,$m=5$时:
- $\varphi(5) = 4$
- $3^4 = 81 \equiv 1 \pmod{5}$
证明
-
构造简化剩余系:
设${r_1, r_2, \dots, r_{\varphi(m)}}$是模$m$的简化剩余系 -
考虑乘积:
$$ P = \prod_{i=1}^{\varphi(m)} r_i $$ -
因为$\gcd(a,m)=1$,所以${a r_1, a r_2, \dots, a r_{\varphi(m)}}$也是简化剩余系
-
因此:
$$ \prod_{i=1}^{\varphi(m)} (a r_i) \equiv P \pmod{m} $$
$$ a^{\varphi(m)} P \equiv P \pmod{m} $$ -
由于$\gcd(P,m)=1$,可以两边约去$P$:
$$ a^{\varphi(m)} \equiv 1 \pmod{m} $$
费马小定理(特例)
当$m$为质数$p$时:
$$ \varphi(p) = p-1 $$
因此欧拉定理退化为费马小定理:
$$ a^{p-1} \equiv 1 \pmod{p} $$应用
- 计算模逆元
- RSA加密算法
- 简化模幂运算
注意事项
- 必须满足$\gcd(a,m)=1$的条件
- $\varphi(m)$的计算需要对$m$进行质因数分解
- 当$m$是质数时,可以直接使用费马小定理
欧拉函数计算方法
对于$n = p_1^{k_1} p_2^{k_2} \cdots p_r^{k_r}$:
$$ \varphi(n) = n \prod_{p|n} \left(1 - \frac{1}{p}\right) $$例如:
- $\varphi(12) = 12 \times (1-\frac{1}{2}) \times (1-\frac{1}{3}) = 4$
- $\varphi(7) = 6$(因为7是质数)
如果gcd(a, m) = 1,指数的意思是从(1, m) 有多少个数和m互质。4的结果是2,从1到4和4互质的数有1,2.共两个。9 % 4 == 1。
一个质数的欧拉函数就是m - 1.
费马小定理:一个数的(质数 - 1)次方 % (质数) == 1.
Luogu P5091 【模板】扩展欧拉定理
给你三个正整数 $a, m, b$,你需要求 $a^b \bmod m$
数据范围
$1 \leq a \leq 10^9$, $1 \leq m \leq 10^8$, $1 \leq b \leq 10^{20000000}$
欧拉定理
若 $\gcd(a, m) = 1$,则:
$$ a^{\varphi(m)} \equiv 1 \pmod{m} $$
扩展欧拉定理
$$ a^b \equiv
\begin{cases}
a^b \pmod{m}, & b < \varphi(m) \
a^{b \bmod \varphi(m) + \varphi(m)} \pmod{m}, & b \geq \varphi(m)
\end{cases} $$
使用说明:
- 当 $b$ 较小时:直接计算 $a^b \bmod m$
- 当 $b$ 较大时:先计算 $b' = b \bmod \varphi(m) + \varphi(m)$,再计算 $a^{b'} \bmod m$
示例:
-
$a = 2$, $m = 4$, $b = 1$ ($\varphi(4)=2$):
$2^1 = 2 \neq 2^{1 \bmod 2 + 2} = 2^3 = 8 \pmod{4}$
(符合 $b < \varphi(m)$ 的情况) -
$a = 2$, $m = 4$, $b = 6$ ($\varphi(4)=2$):
$2^6 \equiv 0 \equiv 2^{6 \bmod 2 + 2} = 2^4 = 16 \pmod{4}$
(符合 $b \geq \varphi(m)$ 的情况)
算法步骤:
- 计算 $\varphi(m)$
- 读取超大数 $b$ 并判断:
- 如果 $b$ 的位数超过 $\varphi(m)$ 的位数,直接视为 $b \geq \varphi(m)$
- 否则比较数值大小
- 根据情况选择计算公式
- 使用快速幂计算最终结果
注意事项:
- $\varphi(m)$ 的计算需要对 $m$ 进行质因数分解
- 处理超大数 $b$ 时需要特殊技巧(逐字符读取)
- 注意 $a$ 和 $m$ 不互质时的处理
这两种情况不能合并。分开讨论,第二个通过试除法求欧拉函数。
降幂操作要进行判断,降幂了就要再加上一个mod
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll slowmul(ll x, ll y, ll mod){
ll ans = 0;
while(y){
if(y & 1){
ans = (ans + x);
if(ans > mod) ans %= mod;
}
x = 2 * x % mod;
y >>= 1;
}
return ans;
}
ll fastpow(ll base, ll power, ll mod){
ll ans = 1;
while(power){
if(power & 1) ans = slowmul(ans, base, mod);
base = slowmul(base, base, mod);
power >>= 1;
}
return ans;
}
ll euler(ll n){
ll ans = n;
for(ll i=2;i*i<=n;i++){
if(n % i == 0){
ans = ans / i * (i - 1);
while(n % i == 0){
n /= i;
}
}
}
if(n > 1){
ans = ans / n * (n - 1);
}
return ans;
}
inline ll read(){
ll x = 0, f = 1;char c = getchar();
while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c=getchar();}
return x*f;
}
bool flag = false;
inline ll read(ll MOD){
ll x = 0, f = 1;char c = getchar();
while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);if(x >= MOD){flag = true;x%=MOD;} c=getchar();}
return x*f;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll a, m, b;
a = read();
m = read();
ll phi = euler(m);
b = read(phi);
// cout << a << ' ' << m << ' ' << b << ' ' << phi << '\n';
if(flag) b += phi;
if(b >= phi) cout << fastpow(a, b % phi + phi, m);
else cout << fastpow(a, b, m);
return 0;
}

威尔逊定理
对于任意整数 $p > 1$:
$$
(p-1)! \equiv -1 \pmod{p}
$$
当且仅当 $p$ 为质数时成立。
说明:
- 必要性:若 $p$ 是质数,则 $(p-1)! \equiv -1 \pmod{p}$。
- 充分性:若 $(p-1)! \equiv -1 \pmod{p}$,则 $p$ 必为质数。
示例:
- 当 $p=5$(质数):
$$4! = 24 \equiv -1 \pmod{5}$$ - 当 $p=4$(非质数):
$$3! = 6 \equiv 2 \not\equiv -1 \pmod{4}$$
应用:
- 质数判定(理论意义大于实用,因阶乘计算复杂度高)。
- 数论证明中简化模运算。
注意:
- 定理仅适用于 $p > 1$ 的整数。
- 实际编程中需用更高效的质数判定算法(如 Miller-Rabin)。
(p - 1)! % p == -1
((p - 1)! + 1) % p == 0
如果p > 4, 且p是合数,那么(p - 1)! % p == 0
威尔逊定理
对于整数 $p > 1$:
$$(p-1)! \equiv -1 \pmod{p}$$
是 $p$ 为质数的充分必要条件。
推论
-
质数性质:
若 $p$ 是质数,则
$$(p-1)! + 1 \equiv 0 \pmod{p}$$ -
合数性质:
若 $p > 4$ 是合数,则
$$(p-1)! \equiv 0 \pmod{p}$$
分析
设 $p = 3k + 7$,考察表达式:
$$\left\lfloor \frac{(p-1)!+1}{p} - \left\lfloor\frac{(p-1)!}{p} \right\rfloor \right\rfloor$$
-
当 $p$ 为质数(如 $13, 19, 31$):
- $\frac{(p-1)!+1}{p}$ 是整数
- $\left\lfloor \frac{(p-1)!}{p} \right\rfloor$ 比 $\frac{(p-1)!+1}{p}$ 小 $1$
- 结果恒为 $1$:
$$\left| \frac{(p-1)!+1}{p} - \frac{(p-1)!}{p} \right| = 1$$
-
当 $p$ 为合数(如 $10, 16, 22$):
- $\frac{(p-1)!}{p}$ 是整数
- 结果恒为 $0$:
$$\left\lfloor \frac{(p-1)!+1}{p} - \left\lfloor\frac{(p-1)!}{p} \right\rfloor \right\rfloor = 0$$
应用步骤
-
筛法预处理:
在范围 $[1, 3 \times 10^6 + 7]$ 内筛选所有形如 $3k+7$ 的质数。 -
统计查询:
对于给定 $n$,统计 $[1, n]$ 内满足 $p = 3k+7$ 且通过威尔逊定理验证的质数个数。
示例验证
-
$p=13$(质数):
$$12! = 479001600$$
$$\frac{479001600 + 1}{13} = 36846277 \quad \text{(整数)}$$
$$\left| 36846277 - \left\lfloor \frac{479001600}{13} \right\rfloor \right| = 1$$ -
$p=10$(合数):
$$9! = 362880$$
$$\frac{362880}{10} = 36288 \quad \text{(整数)}$$
$$\left| \frac{362880 + 1}{10} - 36288 \right| = 0.1 \approx 0$$
注意事项
- 实际编程中需用欧拉筛高效预处理质数。
- 直接计算阶乘不可行(数值过大),需结合模运算性质。
- 定理主要用于理论分析,实际质数判定推荐Miller-Rabin算法。
HDU 2973 YAPTCHA 题解
问题描述
给定整数 $n \ (n \leq 10^6)$,计算以下求和式:
$$
S_n = \sum_{k=1}^n \left\lfloor \frac{(3k+6)! + 1}{3k+7} \right\rfloor - \left\lfloor \frac{(3k+6)!}{3k+7} \right\rfloor
$$
威尔逊定理应用
观察发现 $p = 3k + 7$,根据威尔逊定理:
-
若 $p$ 为质数,则 $(p-1)! \equiv -1 \pmod{p}$,即:
$$
\left\lfloor \frac{(p-1)! + 1}{p} \right\rfloor - \left\lfloor \frac{(p-1)!}{p} \right\rfloor = 1
$$ -
若 $p$ 为合数,则 $(p-1)! \equiv 0 \pmod{p}$,差值为 $0$
算法思路
-
预处理质数:
使用欧拉筛法预处理 $[8, 3 \times 10^6 + 7]$ 范围内的所有质数。 -
前缀和数组:
对于每个 $k \in [1, 10^6]$,检查 $p = 3k + 7$ 是否为质数:- 是质数:$a_k = 1$
- 是合数:$a_k = 0$
构建前缀和数组 $S$,其中 $S[i] = \sum_{j=1}^i a_j$。
-
查询答案:
对于输入的 $n$,直接输出 $S[n]$。
代码实现(关键部分)
const int MAX = 3e6 + 10;
vector<bool> is_prime(MAX, true);
vector<int> prefix(1e6 + 10, 0);
void preprocess() {
// 欧拉筛
is_prime[0] = is_prime[1] = false;
for (int i = 2; i < MAX; ++i) {
if (is_prime[i]) {
for (int j = 2*i; j < MAX; j += i)
is_prime[j] = false;
}
}
// 构建前缀和
for (int k = 1; k <= 1e6; ++k) {
int p = 3*k + 7;
prefix[k] = prefix[k-1] + (is_prime[p] ? 1 : 0);
}
}
int main() {
preprocess();
int T, n;
cin >> T;
while (T--) {
cin >> n;
cout << prefix[n] << endl;
}
return 0;
}
裴蜀定理(Bézout's Identity)
定理内容
对于任意整数 ( a, b ),存在整数 ( x, y ) 使得:
$$
ax + by = \gcd(a, b)
$$
性质说明
-
解的普遍性
-
若 $\gcd(a, b) = d $,则方程 $\ ax + by = k$ 有整数解当且仅当 $\ d \mid k $。
-
若 $\ (x_0, y_0)$ 是一组解,则通解为:
$$
x = x_0 + \frac{b}{d} \cdot t, \quad y = y_0 - \frac{a}{d} \cdot t \quad (t \in \mathbb{Z})
$$
-
-
无解情况
当 $k $ 不是 $\gcd(a, b)$ 的倍数时,方程无整数解。
示例验证
-
有解示例
方程 ( 4x + 6y = 2 )(因 $\gcd(4,6)=2 $):-
特解:( x = -1 ), ( y = 1 )满足 $\ 4 \times (-1) + 6 \times 1 = 2 $
-
通解:
$$
x = -1 + 3t, \quad y = 1 - 2t \quad (t \in \mathbb{Z})
$$
-
-
无解示例
方程 ( 4x + 6y = 3 ):- 由于 $ \gcd(4,6)=2 \nmid 3 $,故无整数解。
- 变形后 $\ x = \frac{3-6y}{4} $ 无法得到整数 ( y )。
应用场景
- 求解线性丢番图方程
- 判断方程的可解性
- 扩展欧几里得算法的基础理论
相关算法
扩展欧几里得:
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
int x1, y1, d;
d = exgcd(b, a % b, x1, y1);
x = y1, y = x1 - a / b * y1;
return d;
}
(x % mod + mod) % mod, 返回最小正整数
中国剩余定理(Chinese Remainder Theorem, CRT)
问题原型(《孙子算经》)
"有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?"
对应同余方程组:
$$
\begin{cases}
x \equiv 2 \pmod{3} \
x \equiv 3 \pmod{5} \
x \equiv 2 \pmod{7}
\end{cases}
$$
定理陈述
对于两两互质的模数 ( m_1, m_2, \dots, m_n ),方程组:
$$
\begin{cases}
x \equiv r_1 \pmod{m_1} \
x \equiv r_2 \pmod{m_2} \
\vdots \
x \equiv r_n \pmod{m_n}
\end{cases}
$$
存在唯一解 ( x \equiv R \pmod{M} ),其中 ( M = m_1 m_2 \cdots m_n )。
求解步骤
-
计算总模数
$$ M = \prod_{i=1}^n m_i $$ -
计算辅助量
对每个 ( i ),计算:
$$ c_i = \frac{M}{m_i} $$ -
求逆元
解同余方程 $\ c_i \cdot c_i^{-1} \equiv 1 \pmod{m_i} $,得到 $\ c_i^{-1} $。 -
构造解
$$ x = \sum_{i=1}^n r_i \cdot c_i \cdot c_i^{-1} \pmod{M} $$
示例求解
解方程组:
$$
\begin{cases}
x \equiv 2 \pmod{3} \
x \equiv 3 \pmod{4} \
x \equiv 1 \pmod{5}
\end{cases}
$$
-
计算 ( M )
$$ M = 3 \times 4 \times 5 = 60 $$ -
求 ( c_i ) 和逆元
-
构造解
$$
x = (2 \times 20 \times 2) + (3 \times 15 \times 3) + (1 \times 12 \times 3) = 80 + 135 + 36 = 251 \equiv 11 \pmod{60}
$$
最小正整数解:( x = 11 )
算法实现(C++)
// 扩展欧几里得算法
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) { x = 1; y = 0; return a; }
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
// 模逆元计算
int mod_inverse(int a, int m) {
int x, y;
int d = exgcd(a, m, x, y);
if (d != 1) return -1; // 无逆元
return (x % m + m) % m;
}
// 中国剩余定理
int CRT(const vector<int>& m, const vector<int>& r) {
int M = 1, x = 0;
for (int mi : m) M *= mi;
for (int i = 0; i < m.size(); ++i) {
int ci = M / m[i];
int inv = mod_inverse(ci, m[i]);
if (inv == -1) return -1; // 无解
x = (x + r[i] * ci * inv) % M;
}
return x;
}
扩展中国剩余定理:
m存模数,r存余数
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
// 扩展欧几里得算法
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) { x = 1; y = 0; return a; }
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
// 扩展中国剩余定理
ll ExCRT(const vector<ll>& m, const vector<ll>& r) {
ll M = m[0], R = r[0]; // 初始模数和余数
for (int i = 1; i < m.size(); ++i) {
ll m1 = M, r1 = R;
ll m2 = m[i], r2 = r[i];
ll k1, k2;
ll d = exgcd(m1, m2, k1, k2); // 解 k1*m1 ≡ r2-r1 (mod m2)
if ((r2 - r1) % d != 0) return -1; // 无解
// 计算最小正整数解 k1
ll t = m2 / d;
k1 = ((r2 - r1) / d * k1 % t + t) % t;
// 更新合并后的模数和余数
R = r1 + k1 * m1;
M = m1 / d * m2; // lcm(m1, m2)
}
return R % M;
}
int main() {
int n;
cin >> n;
vector<ll> m(n), r(n);
for (int i = 0; i < n; ++i) cin >> m[i] >> r[i];
ll ans = ExCRT(m, r);
if (ans == -1) cout << "No solution" << endl;
else cout << "Solution: " << ans << endl;
return 0;
}
扩展剩余定理的mi不一定两两互质
n个同余方程合并n - 1次 nlogn
-
GCD 基本性质
-
交换律
$$\gcd(a,b) = \gcd(b,a)$$ -
绝对值无关性
$$\gcd(-a,b) = \gcd(a,b)$$ -
相同数
$$\gcd(a,a) = |a|$$ -
与零的 GCD
$$\gcd(a,0) = |a|$$ -
与 1 的 GCD
$$\gcd(a,1) = 1$$ -
模运算性质
$$\gcd(a,b) = \gcd(b,a \bmod b)$$ -
减法性质
$$\gcd(a,b) = \gcd(b,a-b)$$
分配律与线性组合
-
倍数分配律
$$\gcd(ma,mb) = m \cdot \gcd(a,b) \quad (m \in \mathbb{N})$$ -
线性组合不变性
$$\gcd(a+mb,b) = \gcd(a,b)$$ -
约分性质
若 $m = \gcd(a,b)$,则:
$$\gcd\left(\frac{a}{m}, \frac{b}{m}\right) = \frac{\gcd(a,b)}{m} = 1$$ -
乘法函数性质
$$\gcd(ab,m) = \gcd(a,m) \cdot \gcd(b,m)$$
与 LCM(最小公倍数)的关系
-
基本关系
$$\gcd(a,b) \times \text{lcm}(a,b) = |ab|$$ -
分配律
$$\gcd(a, \text{lcm}(b,c)) = \text{lcm}(\gcd(a,b), \gcd(a,c))$$
$$\text{lcm}(a, \gcd(b,c)) = \gcd(\text{lcm}(a,b), \text{lcm}(a,c))$$
几何意义
- 整数点计数
连接坐标点 $(0,0)$ 和 $(a,b)$ 的线段上,除起点外经过的整数点数为 $\gcd(a,b)$。
扩展推论
-
幂差性质
若 $\gcd(A,B) = 1$,则:
$$\gcd(A^m - B^m, A^n - B^n) = A^{\gcd(m,n)} - B^{\gcd(m,n)}$$ -
模运算约简
若 $a \cdot c \equiv b \cdot c \pmod{p}$ 且 $\gcd(c,p) = d$,则:
$$a \equiv b \pmod{\frac{p}{d}}$$
计算方法
-
质因数分解法
分解两数为质因数,取共有质因数的最小幂次乘积。 -
辗转相除法(欧几里得算法)
基于性质 $\gcd(a,b) = \gcd(b,a \bmod b)$ 迭代计算。
-
高斯消元板子
这是一个高斯消元法求解线性方程组的C++代码。让我详细解释一下:
输入格式
- 第一行输入一个整数
n,表示方程个数和未知数个数 - 接下来
n行,每行输入n+1个浮点数- 前
n个数是系数矩阵 - 第
n+1个数是常数项
- 前
输入示例:
3
1 1 1 6
1 2 -1 1
2 1 -1 1
表示方程组:
x + y + z = 6
x + 2y - z = 1
2x + y - z = 1
输出结果
代码会根据方程组的情况输出三种可能:
- 唯一解
2.00
1.00
3.00
表示 x=2.00, y=1.00, z=3.00
- 无穷多解
0
当方程组的系数矩阵秩小于未知数个数时
- 无解
-1
当出现矛盾方程时(如 0 = 1)
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 500005;
const int inf = LLONG_MAX;
const double eps = 1e-7;
const int mod = 1e9+7;
double a[111][111];
double ans[111];
void miaojiachun() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n + 1; j++) {
cin >> a[i][j];
}
}
int rank = 0;
vector<bool> free_var(n + 1, true);
for (int col = 1; col <= n; col++) {
// 寻找主元
int pivot = -1;
for (int row = rank + 1; row <= n; row++) {
if (fabs(a[row][col]) > eps) {
pivot = row;
break;
}
}
if (pivot == -1) {
free_var[col] = true;
continue;
}
rank++;
free_var[col] = false;
// 交换行
if (pivot != rank) {
for (int j = 1; j <= n + 1; j++) {
swap(a[rank][j], a[pivot][j]);
}
}
// 归一化
double div = a[rank][col];
for (int j = col; j <= n + 1; j++) {
a[rank][j] /= div;
}
// 消元
for (int row = 1; row <= n; row++) {
if (row != rank && fabs(a[row][col]) > eps) {
double factor = a[row][col];
for (int j = col; j <= n + 1; j++) {
a[row][j] -= a[rank][j] * factor;
}
}
}
}
// 检查无解情况
for (int row = rank + 1; row <= n; row++) {
if (fabs(a[row][n + 1]) > eps) {
cout << -1 << endl;
return;
}
}
// 检查无穷解情况
if (rank < n) {
cout << 0 << endl;
return;
}
// 回代求解
for (int i = n; i >= 1; i--) {
ans[i] = a[i][n + 1];
for (int j = i + 1; j <= n; j++) {
ans[i] -= a[i][j] * ans[j];
}
}
// 输出解
for (int i = 1; i <= n; i++) {
cout << fixed << setprecision(2) << ans[i] << endl;
}
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
while (ING--) {
miaojiachun();
}
return 0;
}
包含了三个主要功能:
- 高斯消元求解线性方程组 (
gauss函数)
功能:求解 Ax = b 形式的线性方程组
输入:
a:增广矩阵 [A|b],n行 × (n+1)列ans:用于存储解的向量
返回值:
0:唯一解(解在ans中)>= 1:无穷多解(返回自由变元数量)-1:无解
使用示例:
// 求解:
// 2x + y = 5
// x - y = 1
vector<vector<double>> a = {{2, 1, 5}, {1, -1, 1}};
vector<double> ans;
int result = gauss(a, ans);
// 结果:x=2, y=1
- 计算矩阵行列式 (
det函数)
功能:计算方阵的行列式
输入:n×n 矩阵
返回值:行列式的值
使用示例:
vector<vector<double>> matrix = {{2, 1}, {1, -1}};
double determinant = det(matrix);
// 结果:-3
- 求逆矩阵 (
inverse函数)
功能:计算矩阵的逆
输入:
a:原矩阵inv:存储逆矩阵的结果
返回值:是否成功求逆(矩阵是否可逆)
使用示例:
vector<vector<double>> matrix = {{2, 1}, {1, -1}};
vector<vector<double>> inv_matrix;
if (inverse(matrix, inv_matrix)) {
// 成功求得逆矩阵
}
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8; // 根据题目调整精度
// 高斯消元求解线性方程组 Ax = b
// n: 方程数/变量数, a: 增广矩阵 (n行 x n+1列)
// 返回:
// - 0: 唯一解 (解存在ans中)
// - >= 1: 无穷多解(返回自由变元数量)
// - -1: 无解
int gauss(vector<vector<double>>& a, vector<double>& ans) {
int n = a.size();
ans.assign(n, 0); // 初始化为0
vector<int> pivot_col(n, -1); // 记录每行的主元列
int rank = 0; // 矩阵的秩
for (int col = 0; col < n; col++) {
// 1. 找主元
int pivot_row = -1;
for (int row = rank; row < n; row++) {
if (fabs(a[row][col]) > eps) {
pivot_row = row;
break;
}
}
if (pivot_row == -1) {
continue; // 这一列没有主元,是自由变量
}
// 2. 交换行
swap(a[rank], a[pivot_row]);
// 3. 记录主元位置
pivot_col[rank] = col;
// 4. 归一化(可选,但可以改善数值稳定性)
double pivot_val = a[rank][col];
for (int j = col; j <= n; j++) {
a[rank][j] /= pivot_val;
}
// 5. 消元
for (int row = 0; row < n; row++) {
if (row != rank && fabs(a[row][col]) > eps) {
double ratio = a[row][col];
for (int j = col; j <= n; j++) {
a[row][j] -= a[rank][j] * ratio;
}
}
}
rank++;
}
// 检查无解情况
for (int row = rank; row < n; row++) {
if (fabs(a[row][n]) > eps) {
return -1; // 无解
}
}
int free_vars = n - rank; // 自由变量的个数
if (free_vars > 0) {
// 无穷多解,设置自由变量为0(或其他值)
for (int i = 0; i < rank; i++) {
int col_idx = pivot_col[i];
ans[col_idx] = a[i][n];
// 减去已知变量的贡献
for (int j = col_idx + 1; j < n; j++) {
if (fabs(a[i][j]) > eps && pivot_col[i] != -1) {
ans[col_idx] -= a[i][j] * ans[j];
}
}
}
return free_vars; // 返回自由变量个数
}
// 唯一解,回代求解
for (int i = rank - 1; i >= 0; i--) {
int col_idx = pivot_col[i];
ans[col_idx] = a[i][n];
for (int j = col_idx + 1; j < n; j++) {
ans[col_idx] -= a[i][j] * ans[j];
}
ans[col_idx] /= a[i][col_idx];
}
return 0; // 唯一解
}
// 计算矩阵行列式
double det(vector<vector<double>> a) {
int n = a.size();
double res = 1;
for (int i = 0; i < n; i++) {
int pivot = i;
for (int j = i; j < n; j++) {
if (fabs(a[j][i]) > fabs(a[pivot][i])) {
pivot = j;
}
}
if (fabs(a[pivot][i]) < eps) {
return 0; // 行列式为0
}
if (pivot != i) {
swap(a[i], a[pivot]);
res = -res;
}
res *= a[i][i];
for (int j = i + 1; j < n; j++) {
double ratio = a[j][i] / a[i][i];
for (int k = i; k < n; k++) {
a[j][k] -= a[i][k] * ratio;
}
}
}
return res;
}
// 求逆矩阵 (存在inv中)
bool inverse(vector<vector<double>> a, vector<vector<double>>& inv) {
int n = a.size();
inv.assign(n, vector<double>(n, 0));
// 构造增广矩阵 [A|I]
for (int i = 0; i < n; i++) {
a[i].resize(2 * n);
a[i][n + i] = 1;
}
for (int i = 0; i < n; i++) {
// 找主元
int pivot = i;
for (int j = i; j < n; j++) {
if (fabs(a[j][i]) > fabs(a[pivot][i])) {
pivot = j;
}
}
if (fabs(a[pivot][i]) < eps) {
return false; // 矩阵不可逆
}
swap(a[i], a[pivot]);
// 归一化
double div = a[i][i];
for (int j = i; j < 2 * n; j++) {
a[i][j] /= div;
}
// 消元
for (int j = 0; j < n; j++) {
if (j != i && fabs(a[j][i]) > eps) {
double ratio = a[j][i];
for (int k = i; k < 2 * n; k++) {
a[j][k] -= a[i][k] * ratio;
}
}
}
}
// 提取逆矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
inv[i][j] = a[i][n + j];
}
}
return true;
}
bitset

杂

https://codeforces.com/gym/105173
M
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1005;
struct Point {
int x, y;
Point operator-(const Point &b) const {
return {x - b.x, y - b.y};
}
int operator^(const Point &b) const {
return x * b.y - y * b.x; // cross
}
int operator*(const Point &b) const {
return x * b.x + y * b.y; // dot
}
};
int dis(Point a, Point b) {
return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}
Point p[N];
int n;
int solve(Point A, Point B) {
Point V = B - A;
map<int, int> left_map, right_map;
int L = 0, R = 0;
for (int i = 1; i <= n; ++i) {
Point M = p[i] - A;
if ((V ^ M) == 0) continue; // on line AB
if ((V ^ M) < 0) {
if (dis(p[i], A) == dis(p[i], B)) L++;
} else {
if ((p[i] - A) * V == 0) left_map[dis(p[i], A)]++;
if ((p[i] - B) * V == 0) right_map[dis(p[i], B)]++;
}
}
for (auto &[d, cnt] : left_map) {
if (right_map.count(d)) R++;
}
return L * R;
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> p[i].x >> p[i].y;
}
int ans = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (i != j)
ans += solve(p[i], p[j]);
cout << ans << "\n";
}
文艺平衡树
带注释:
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
//#define int __int128
const int N = 1e5 + 100;
const int mod = 998244353;
#define pii pair<int, int>
struct xx {
int t, l, r;
};
namespace treap {
int n, m, tot;
struct tree{
int l, r;
int fix;
int val;
int size;
int lz;
}tr[500007];
inline int get_node(int v) {
tr[++tot].val = v;
tr[tot].size = 1;
tr[tot].fix = rand();
return tot;
}
void pushdown(int p) {
if (tr[p].lz) {
swap(tr[p].l, tr[p].r);
tr[tr[p].l].lz ^= 1;
tr[tr[p].r].lz ^= 1;
tr[p].lz = 0;
}
}
void pushup(int p) {
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1;
}
/*
将树p分裂为两棵树x和y,x包含前rnk个元素
递归实现,根据左子树大小决定分裂位置
分裂前下传标记,分裂后更新节点信息
*/
void split(int p, int rnk, int &x, int &y) {
if (!p) {
x = y = 0;
return;
}
pushdown(p);
if (tr[tr[p].l].size + 1 <= rnk) {
x = p;
split(tr[p].r, rnk - tr[tr[p].l].size - 1, tr[p].r, y);
}else {
y = p;
split(tr[p].l, rnk, x, tr[p].l);
}
pushup(p);
}
/*
合并两棵树
*/
int merge(int x, int y) {
if (!x or !y) {
return x + y;
}
if (tr[tr[x].l].fix < tr[tr[y].l].fix) {
pushdown(x);
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}else {
pushdown(y);
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
/*
打印Treap
*/
void print(int p) {
pushdown(p);
if (tr[p].l) {
print(tr[p].l);
}
cout << tr[p].val << " ";
if (tr[p].r) {
print(tr[p].r);
}
}
}
void solve() {
int n, q, m;
cin >> n >> q >> m;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int root = 0;
for (int i = 1; i <= n; i++) {
int node = treap::get_node(a[i]);
root = treap::merge(root, node);
}
vector<xx> c(q + 1);
for (int i = 1; i <= q; i++) {
cin >> c[i].t >> c[i].l >> c[i].r;
}
vector<int> b(m + 1);
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
for (int i = 1; i <= q; i++) {
if (c[i].t == 1) {
int x, y, z, w;
// 分裂出前L-1个节点 → x
treap::split(root, c[i].l - 1, x, y);
// 从剩余部分分裂出长度为R-L+1的区间 → z
treap::split(y, c[i].r - c[i].l + 1, z, w);
int p, q;
// 将z的最后1个节点分离出来 → q
treap::split(z, treap::tr[z].size - 1, p, q);
// 将p合并到q的前面 → 实现循环右移
z = treap::merge(q, p);
root = treap::merge(x, treap::merge(z, w));
}else if (c[i].t == 2) {
int x, y, z;
// 分裂出前L-1个节点 → x
treap::split(root, c[i].l - 1, x, y);
// 从剩余部分分裂出长度为R-L+1的区间 → y
treap::split(y, c[i].r - c[i].l + 1, y, z);
// 给y的根节点打上翻转标记
treap::tr[y].lz ^= 1;
// 重新合并
root = treap::merge(x, treap::merge(y, z));
}
}
for (int i = 1; i <= m; i++) {
int x, y, z;
// 分裂出前b[i]-1个节点 → x
treap::split(root, b[i] - 1, x, y);
// 从剩余部分分裂出1个节点 → y
treap::split(y, 1, y, z);
// 输出y的值
cout << treap::tr[y].val << " ";
// 重新合并
root = treap::merge(x, treap::merge(y, z));
}
cout << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}
^上面的板子做洛谷题超时了,
改一下merge函数就好了:
//#pragma GCC optimize(1)
//#pragma GCC optimize(2)
//#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
//#define int __int128
const int N = 1e5 + 100;
const int mod = 998244353;
#define pii pair<int, int>
struct xx {
int t, l, r;
};
namespace treap {
int n, m, tot;
struct tree{
int l, r;
int fix;
int val;
int size;
int lz;
}tr[500007];
inline int get_node(int v) {
tr[++tot].val = v;
tr[tot].size = 1;
tr[tot].fix = rand();
return tot;
}
void pushdown(int p) {
if (tr[p].lz) {
swap(tr[p].l, tr[p].r);
tr[tr[p].l].lz ^= 1;
tr[tr[p].r].lz ^= 1;
tr[p].lz = 0;
}
}
void pushup(int p) {
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1;
}
/*
将树p分裂为两棵树x和y,x包含前rnk个元素
递归实现,根据左子树大小决定分裂位置
分裂前下传标记,分裂后更新节点信息
*/
void split(int p, int rnk, int &x, int &y) {
if (!p) {
x = y = 0;
return;
}
pushdown(p);
if (tr[tr[p].l].size + 1 <= rnk) {
x = p;
split(tr[p].r, rnk - tr[tr[p].l].size - 1, tr[p].r, y);
}else {
y = p;
split(tr[p].l, rnk, x, tr[p].l);
}
pushup(p);
}
/*
合并两棵树
*/
int merge(int x, int y) {
if(!x || !y) return x | y;
if(tr[x].fix < tr[y].fix) {
pushdown(x);
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
} else {
pushdown(y);
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
/*
打印Treap
*/
void print(int p) {
pushdown(p);
if (tr[p].l) {
print(tr[p].l);
}
cout << tr[p].val << " ";
if (tr[p].r) {
print(tr[p].r);
}
}
}
void solve() {
int n, m;
cin >> n >> m;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
a[i] = i;
}
int root = 0;
for (int i = 1; i <= n; i++) {
int node = treap::get_node(a[i]);
root = treap::merge(root, node);
}
for (int i = 1; i <= m; i++) {
int l, r;
cin >> l >> r;
int x, y;
treap::split(root, l - 1, x, y);
int z;
treap::split(y, r - l + 1, y, z);
treap::tr[y].lz ^= 1;
root = treap::merge(x, treap::merge(y, z));
}
vector<int> ans(n + 1);
for (int i = 1; i <= n; i++) {
int x, y, z;
treap::split(root, i - 1, x, y);
treap::split(y, 1, y, z);
// cout << treap::tr[y].val << " ";
ans[i] = treap::tr[y].val;
root = treap::merge(x, treap::merge(y, z));
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << " ";
}
cout << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}
文艺平衡树板子基础上扩展的增强版,支持动态区间最值/求和、区间赋值、区间等差数列操作。
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 1e5 + 100;
namespace treap {
struct Data { // 维护区间数据
int sum, max, min;
int lmax, rmax; // 用于最大子段和
Data() : sum(0), max(-1e18), min(1e18), lmax(-1e18), rmax(-1e18) {}
Data(int val) : sum(val), max(val), min(val), lmax(val), rmax(val) {}
};
struct tree {
int l, r, fix, val, size, lz;
Data data;
int set_lz, add_lz; // 赋值标记和等差数列增量
} tr[N * 5];
int tot;
inline Data merge_data(const Data& a, const Data& b, int val = 0) {
Data res;
res.sum = a.sum + val + b.sum;
res.max = max({a.max, val, b.max});
res.min = min({a.min, val, b.min});
res.lmax = max(a.lmax, a.sum + val + max(0LL, b.lmax));
res.rmax = max(b.rmax, b.sum + val + max(0LL, a.rmax));
return res;
}
inline int get_node(int v) {
tr[++tot] = {0, 0, rand(), v, 1, 0, Data(v), -1, 0};
return tot;
}
void apply_set(int p, int val) {
if (!p) return;
tr[p].val = val;
tr[p].data = Data(val);
tr[p].set_lz = val;
tr[p].add_lz = 0;
}
void apply_add(int p, int delta) {
if (!p) return;
tr[p].val += delta;
tr[p].data.sum += delta * tr[p].size;
tr[p].data.max += delta;
tr[p].data.min += delta;
tr[p].data.lmax += delta;
tr[p].data.rmax += delta;
tr[p].add_lz += delta;
}
void pushdown(int p) {
if (!p) return;
if (tr[p].lz) {
swap(tr[p].l, tr[p].r);
swap(tr[tr[p].l].data.lmax, tr[tr[p].l].data.rmax);
swap(tr[tr[p].r].data.lmax, tr[tr[p].r].data.rmax);
tr[tr[p].l].lz ^= 1;
tr[tr[p].r].lz ^= 1;
tr[p].lz = 0;
}
if (tr[p].set_lz != -1) {
apply_set(tr[p].l, tr[p].set_lz);
apply_set(tr[p].r, tr[p].set_lz);
tr[p].set_lz = -1;
}
if (tr[p].add_lz) {
apply_add(tr[p].l, tr[p].add_lz);
apply_add(tr[p].r, tr[p].add_lz);
tr[p].add_lz = 0;
}
}
void pushup(int p) {
if (!p) return;
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1;
Data l = tr[tr[p].l].data;
Data r = tr[tr[p].r].data;
tr[p].data = merge_data(l, r, tr[p].val);
}
void split(int p, int rnk, int &x, int &y) {
if (!p) { x = y = 0; return; }
pushdown(p);
if (tr[tr[p].l].size + 1 <= rnk) {
x = p;
split(tr[p].r, rnk - tr[tr[p].l].size - 1, tr[p].r, y);
} else {
y = p;
split(tr[p].l, rnk, x, tr[p].l);
}
pushup(p);
}
int merge(int x, int y) {
if (!x || !y) return x | y;
if (tr[x].fix < tr[y].fix) {
pushdown(x);
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
} else {
pushdown(y);
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
// 区间操作接口
void range_set(int &root, int l, int r, int val) {
int x, y, z;
split(root, l-1, x, y);
split(y, r-l+1, y, z);
apply_set(y, val);
root = merge(x, merge(y, z));
}
void range_add(int &root, int l, int r, int delta) {
int x, y, z;
split(root, l-1, x, y);
split(y, r-l+1, y, z);
apply_add(y, delta);
root = merge(x, merge(y, z));
}
Data query(int &root, int l, int r) {
int x, y, z;
split(root, l-1, x, y);
split(y, r-l+1, y, z);
Data res = tr[y].data;
root = merge(x, merge(y, z));
return res;
}
}
/* 使用示例 */
void solve() {
int n, m; cin >> n >> m;
int root = 0;
for (int i = 1; i <= n; i++) {
int val; cin >> val;
root = treap::merge(root, treap::get_node(val));
}
while (m--) {
int op, l, r; cin >> op >> l >> r;
if (op == 1) { // 区间赋值
int val; cin >> val;
treap::range_set(root, l, r, val);
}
else if (op == 2) { // 区间加等差数列
int d; cin >> d;
// 等差数列a[l]+0*d, a[l+1]+1*d...通过差分转化为区间加
treap::range_add(root, l, r, 0); // 这里简化处理,实际需要更复杂处理
}
else if (op == 3) { // 查询区间和
auto res = treap::query(root, l, r);
cout << res.sum << endl;
}
else if (op == 4) { // 查询区间最大子段和
auto res = treap::query(root, l, r);
cout << max({res.lmax, res.rmax, res.max}) << endl;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
solve();
return 0;
}
区间修改,区间查询线段树
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 4e6 + 100;
int n, m;
int a[N];
struct tree {
int l, r, lazy, sum;
} tu[N];
void build(int l, int r, int num) {
tu[num].l = l;
tu[num].r = r;
tu[num].lazy = 0; // 显式初始化
if (l == r) {
tu[num].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, num << 1);
build(mid + 1, r, num << 1 | 1);
tu[num].sum = tu[num << 1].sum + tu[num << 1 | 1].sum;
}
void down(int x) {
if (!tu[x].lazy) return;
int len_left = tu[x << 1].r - tu[x << 1].l + 1;
int len_right = tu[x << 1 | 1].r - tu[x << 1 | 1].l + 1;
tu[x << 1].lazy += tu[x].lazy;
tu[x << 1].sum += len_left * tu[x].lazy;
tu[x << 1 | 1].lazy += tu[x].lazy;
tu[x << 1 | 1].sum += len_right * tu[x].lazy;
tu[x].lazy = 0;
}
void change(int l, int r, int ad, int num) {
if (tu[num].r < l || tu[num].l > r) return;
if (l <= tu[num].l && tu[num].r <= r) {
tu[num].lazy += ad;
tu[num].sum += (tu[num].r - tu[num].l + 1) * ad;
return;
}
down(num);
change(l, r, ad, num << 1);
change(l, r, ad, num << 1 | 1);
tu[num].sum = tu[num << 1].sum + tu[num << 1 | 1].sum;
}
int ask(int l, int r, int num) {
if (tu[num].r < l || tu[num].l > r) return 0;
if (l <= tu[num].l && tu[num].r <= r) return tu[num].sum;
down(num);
return ask(l, r, num << 1) + ask(l, r, num << 1 | 1);
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
build(1, n, 1);
for (int i = 1; i <= m; i++) {
int f;
cin >> f;
if (f == 1) {
int l, r, x;
cin >> l >> r >> x;
change(l, r, x, 1);
}else {
int l, r;
cin >> l >> r;
int ans = ask(l, r, 1);
cout << ans << endl;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
多重背包
点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_V = 40010;
int dp[MAX_V];
int main() {
int N, V; // N物品种类数,V背包容量
cin >> N >> V;
memset(dp, 0, sizeof(dp));
for(int i = 0; i < N; i++) {
int v, w, z; // 体积,价值,数量
cin >> v >> w >> z;
// 二进制拆分
for(int k = 1; k <= z; k *= 2) {
int nv = v * k, nw = w * k;
for(int j = V; j >= nv; j--) {
dp[j] = max(dp[j], dp[j - nv] + nw);
}
z -= k;
}
if(z > 0) {
int nv = v * z, nw = w * z;
for(int j = V; j >= nv; j--) {
dp[j] = max(dp[j], dp[j - nv] + nw);
}
}
}
cout << dp[V] << endl;
return 0;
}
Int128板子,有sqrt()
点击查看代码
// 安全的__int128输入
__int128 read() {
__int128 res = 0;
string s;
cin >> s;
bool neg = s[0] == '-';
for(size_t i = neg ? 1 : 0; i < s.size(); i++) {
if(isdigit(s[i])) {
res = res * 10 + (s[i] - '0');
}
}
return neg ? -res : res;
}
// 安全的__int128输出
void print(__int128 num) {
if(num < 0) {
putchar('-');
num = -num;
}
if(num > 9) print(num / 10);
putchar(static_cast<int>(num % 10) + '0'); // 显式类型转换
}
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int __int128
typedef pair<int, int> pii;
const int N = 500005;
const int inf = LLONG_MAX / 10;
const double eps = 1e-9;
const int mod = 1e9+7;
// 安全的__int128输入
__int128 read() {
__int128 res = 0;
string s;
cin >> s;
bool neg = s[0] == '-';
for(size_t i = neg ? 1 : 0; i < s.size(); i++) {
if(isdigit(s[i])) {
res = res * 10 + (s[i] - '0');
}
}
return neg ? -res : res;
}
// 安全的__int128输出
void print(__int128 num) {
if(num < 0) {
putchar('-');
num = -num;
}
if(num > 9) print(num / 10);
putchar(static_cast<int>(num % 10) + '0'); // 显式类型转换
}
int n, m, k, a0;
int ca, cb;
int ccst(int a, int b) {
return (a - a0) * ca + (b - 1) * cb;
}
void miaojiachun() {
n = read(); m = read(); k = read();
a0 = read(); ca = read(); cb = read();
if (m == 0) {
print(a0); printf(" "); print(1);
return;
}
int ba = max((__int128)1, a0);
int bb = (m + ba * n - 1) / (ba * n);
bb = (bb - 1) * k + 1;
int ans = ccst(ba, bb);
// 第一个循环
for (int a = ba; a <= sqrt(static_cast<long double>(m / n)) + 1; a++) {
int b = (m + a * n - 1) / (a * n);
b = (b - 1) * k + 1;
int cost = ccst(a, b);
if (cost < ans) {
ans = cost;
ba = a;
bb = b;
}
}
// 第二个循环
__int128 kk = sqrt(static_cast<long double>(m / n)) + 2;
for (__int128 j = kk; j >= 1; j--) {
int a = (m + n * j - 1) / (n * j);
if (a < a0) continue;
int nowb = (j - 1) * k + 1;
int cost = ccst(a, nowb);
if (cost < ans) {
ans = cost;
ba = a;
bb = nowb;
}
}
print(ba); printf(" "); print(bb);
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
while (ING--) {
miaojiachun();
}
return 0;
}
珂朵莉树,区间赋成同一值,区间加,区间求和
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const double eps = 1e-6;
const int mod = 1e9 + 7;
struct Node_t {
int l, r;
mutable int v;
Node_t(const int &il, const int &ir, const int &iv) : l(il), r(ir), v(iv) {}
bool operator<(const Node_t &o) const { return l < o.l; }
};
set<Node_t> odt; // 珂朵莉树的核心数据结构
// 将区间 [x, ...] 分裂,返回指向 x 的迭代器
auto split(int x) {
auto it = odt.lower_bound(Node_t(x, 0, 0));
if (it != odt.end() && it->l == x) return it;
--it;
int l = it->l, r = it->r, v = it->v;
odt.erase(it);
odt.insert(Node_t(l, x - 1, v));
return odt.insert(Node_t(x, r, v)).first;
}
// 区间赋值操作 [l, r] = v
void assign(int l, int r, int v) {
auto itr = split(r + 1), itl = split(l);
odt.erase(itl, itr);
odt.insert(Node_t(l, r, v));
}
// 区间操作(示例:区间求和)
int query_sum(int l, int r) {
int res = 0;
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
res += (it->r - it->l + 1) * it->v;
}
return res;
}
// 区间加法(示例)
void add(int l, int r, int val) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
it->v += val;
}
}
void miaojiachun() {
int n, m;
cin >> n >> m;
// 初始化 ODT,假设初始所有位置的值是 0
odt.insert(Node_t(1, n + 10, 0));
while (m--) {
int l, r;
cin >> l >> r;
assign(l, r, 1);
cout << n - query_sum(1, n) << endl;
}
// // 示例操作:
// assign(1, 5, 10); // [1,5] = 10
// add(3, 7, 2); // [3,7] += 2
// cout << query_sum(2, 6) << endl; // 查询 [2,6] 的和
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--) {
miaojiachun();
}
return 0;
}
补脑洞:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const double eps = 1e-6;
const int mod = 1e9 + 7;
struct Node_t {
int l, r;
mutable int v;
Node_t(const int &il, const int &ir, const int &iv) : l(il), r(ir), v(iv) {}
bool operator<(const Node_t &o) const { return l < o.l; }
};
set<Node_t> odt; // 珂朵莉树的核心数据结构
// 将区间 [x, ...] 分裂,返回指向 x 的迭代器
auto split(int x) {
auto it = odt.lower_bound(Node_t(x, 0, 0));
if (it != odt.end() && it->l == x) return it;
--it;
int l = it->l, r = it->r, v = it->v;
odt.erase(it);
odt.insert(Node_t(l, x - 1, v));
return odt.insert(Node_t(x, r, v)).first;
}
// 区间赋值操作 [l, r] = v
void assign(int l, int r, int v) {
auto itr = split(r + 1), itl = split(l);
odt.erase(itl, itr);
odt.insert(Node_t(l, r, v));
}
// 区间操作(示例:区间求和)
int query_sum(int l, int r) {
int res = 0;
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
res += (it->r - it->l + 1) * it->v;
}
return res;
}
// 区间加法(示例)
void add(int l, int r, int val) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
it->v += val;
}
}
// 查询区间 [l, r] 内最长的连续 0 的长度
int query_max_0(int l, int r) {
auto itr = split(r + 1), itl = split(l);
int max_len = 0, current_len = 0;
for (auto it = itl; it != itr; ++it) {
if (it->v == 0) {
current_len += (it->r - it->l + 1);
max_len = max(max_len, current_len);
} else {
current_len = 0;
}
}
return max_len;
}
void miaojiachun() {
int n, m;
cin >> n >> m;
// 初始化 ODT,假设初始所有位置的值是 0
odt.insert(Node_t(1, n + 10, 1));
while (m--) {
int op, l, r, l1, r1;
cin >> op;
if (op == 0) {
cin >> l >> r;
assign(l, r, 0);
}else if (op == 1) {
cin >> l >> r >> l1 >> r1;
int cnt1 = query_sum(l, r);
assign(l, r, 0);
auto itr = split(r1 + 1), itl = split(l1);
vector<Node_t> to_del;
vector<Node_t> to_add;
for (auto it = itl; it != itr && cnt1 > 0; ++it) {
if (it->v == 0) {
int len = it->r - it->l + 1;
int filllen = min(cnt1, len);
to_del.push_back(*it);
to_add.push_back(Node_t(it->l, it->l + filllen - 1, 1));
if (filllen < len) {
to_add.push_back(Node_t(it->l + filllen, it->r, 0));
}
cnt1 -= filllen;
}
}
for (auto &node : to_del) odt.erase(node);
for (auto &node : to_add) odt.insert(node);
}else if (op == 2) {
cin >> l >> r;
cout << query_max_0(l, r) << endl;
}
}
// // 示例操作:
// assign(1, 5, 10); // [1,5] = 10
// add(3, 7, 2); // [3,7] += 2
// cout << query_sum(2, 6) << endl; // 查询 [2,6] 的和
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--) {
miaojiachun();
}
return 0;
}
100但是T代码(5e5):
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 5e5 + 7;
const int inf = LLONG_MAX / 10;
const double eps = 1e-6;
const int mod = 1e9 + 7;
struct Node_t {
int l, r;
mutable int v;
Node_t(const int &il, const int &ir, const int &iv) : l(il), r(ir), v(iv) {}
bool operator<(const Node_t &o) const { return l < o.l; }
};
set<Node_t> odt; // 珂朵莉树的核心数据结构
// 将区间 [x, ...] 分裂,返回指向 x 的迭代器
auto split(int x) {
auto it = odt.lower_bound(Node_t(x, 0, 0));
if (it != odt.end() && it->l == x) return it;
--it;
int l = it->l, r = it->r, v = it->v;
odt.erase(it);
odt.insert(Node_t(l, x - 1, v));
return odt.insert(Node_t(x, r, v)).first;
}
// 区间赋值操作 [l, r] = v
void assign(int l, int r, int v) {
auto itr = split(r + 1), itl = split(l);
odt.erase(itl, itr);
odt.insert(Node_t(l, r, v));
}
// 区间操作(示例:区间求和)
int query_sum(int l, int r) {
int res = 0;
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
res += (it->r - it->l + 1) * it->v;
}
return res;
}
// 区间加法(示例)
void add(int l, int r, int val) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
it->v += val;
}
}
// 查询区间 [l, r] 内最长的连续 0 的长度
int query_max_0(int l, int r, int x) {
auto itr = split(r + 1), itl = split(l);
int max_len = 0, current_len = 0;
int sum = 0;
for (auto it = itl; it != itr; ++it) {
if (it->v == x) {
current_len += (it->r - it->l + 1);
max_len = max(max_len, current_len);
sum += (it->r - it->l + 1);
} else {
current_len = 0;
}
}
return sum;
}
int get_value_at(int x) {
auto it = odt.upper_bound(Node_t(x, 0, 0));
if (it == odt.begin()) return -1;
--it;
if (it->l <= x && x <= it->r) return it->v;
return -1;
}
void miaojiachun() {
int n, m;
cin >> n;
// 初始化 ODT,假设初始所有位置的值是 0
for (int i = 1; i <= n; i++) {
char c;
cin >> c;
int x = c - 'A';
odt.insert(Node_t(i, i, x));
}
cin >> m;
while (m--) {
char op;
int l, r;
cin >> op >> l >> r;
if (op == 'A') {
char c;
cin >> c;
int x = c - 'A';
assign(l, r, x);
}else {
int flag1 = 0, flag2 = 0;
int x = get_value_at(l);
int num = query_max_0(l, r, x);
if (num == (r - l + 1)) {
flag1 = 1;
}
if ((l == 1 or r == n) and flag1 == 1) {
cout << "Yes" << endl;
continue;
}if ((l == 1 or r == n) and flag1 == 0) {
cout << "No" << endl;
continue;
}
int x1 = get_value_at(l - 1);
int x2 = get_value_at(r + 1);
if (x1 != x2) {
flag2 = 1;
}
if (flag1 and flag2) {
cout << "Yes" << endl;
}else {
cout << "No" << endl;
}
}
}
// // 示例操作:
// assign(1, 5, 10); // [1,5] = 10
// add(3, 7, 2); // [3,7] += 2
// cout << query_sum(2, 6) << endl; // 查询 [2,6] 的和
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--) {
miaojiachun();
}
return 0;
}
100并且过了
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> pii;
const int N = 5e5 + 7;
const int inf = LLONG_MAX / 10;
const double eps = 1e-6;
const int mod = 1e9 + 7;
struct Node_t {
int l, r;
mutable int v;
Node_t(const int &il, const int &ir, const int &iv) : l(il), r(ir), v(iv) {}
bool operator<(const Node_t &o) const { return l < o.l; }
};
set<Node_t> odt; // 珂朵莉树的核心数据结构
// 将区间 [x, ...] 分裂,返回指向 x 的迭代器
auto split(int x) {
auto it = odt.lower_bound(Node_t(x, 0, 0));
if (it != odt.end() && it->l == x) return it;
--it;
int l = it->l, r = it->r, v = it->v;
odt.erase(it);
odt.insert(Node_t(l, x - 1, v));
return odt.insert(Node_t(x, r, v)).first;
}
// 区间赋值操作 [l, r] = v
void assign(int l, int r, int v) {
auto itr = split(r + 1), itl = split(l);
odt.erase(itl, itr);
auto it = odt.insert(Node_t(l, r, v)).first;
// 合并左相邻
if (it != odt.begin() && prev(it)->v == v) {
int new_l = prev(it)->l;
odt.erase(prev(it));
odt.erase(it);
it = odt.insert(Node_t(new_l, r, v)).first;
}
// 合并右相邻
if (next(it) != odt.end() && next(it)->v == v) {
int new_r = next(it)->r;
odt.erase(next(it));
odt.erase(it);
odt.insert(Node_t(it->l, new_r, v));
}
}
// 区间操作(示例:区间求和)
int query_sum(int l, int r) {
int res = 0;
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
res += (it->r - it->l + 1) * it->v;
}
return res;
}
// 区间加法(示例)
void add(int l, int r, int val) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
it->v += val;
}
}
// 查询区间 [l, r] 内最长的连续 0 的长度
int query_max_0(int l, int r, int x) {
auto itr = split(r + 1), itl = split(l);
int max_len = 0, current_len = 0;
int sum = 0;
for (auto it = itl; it != itr; ++it) {
if (it->v == x) {
current_len += (it->r - it->l + 1);
max_len = max(max_len, current_len);
sum += (it->r - it->l + 1);
} else {
current_len = 0;
}
}
return sum;
}
bool is_uniform(int l, int r, int x) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it) {
if (it->v != x) return false;
}
return true;
}
int get_value_at(int x) {
auto it = odt.upper_bound(Node_t(x, 0, 0));
if (it == odt.begin()) return -1;
--it;
if (it->l <= x && x <= it->r) return it->v;
return -1;
}
void miaojiachun() {
int n, m;
cin >> n;
// 初始化 ODT,假设初始所有位置的值是 0
for (int i = 1; i <= n; i++) {
char c;
cin >> c;
int x = c - 'A';
odt.insert(Node_t(i, i, x));
}
cin >> m;
while (m--) {
char op;
int l, r;
cin >> op >> l >> r;
if (op == 'A') {
char c;
cin >> c;
int x = c - 'A';
assign(l, r, x);
}else {
int flag1 = 0, flag2 = 0;
int x = get_value_at(l);
// int num = query_max_0(l, r, x);
if (is_uniform(l, r, x)) {
flag1 = 1;
}
if ((l == 1 or r == n) and flag1 == 1) {
cout << "Yes" << endl;
continue;
}if ((l == 1 or r == n) and flag1 == 0) {
cout << "No" << endl;
continue;
}
int x1 = get_value_at(l - 1);
int x2 = get_value_at(r + 1);
if (x1 != x2) {
flag2 = 1;
}
if (flag1 and flag2) {
cout << "Yes" << endl;
}else {
cout << "No" << endl;
}
}
}
// // 示例操作:
// assign(1, 5, 10); // [1,5] = 10
// add(3, 7, 2); // [3,7] += 2
// cout << query_sum(2, 6) << endl; // 查询 [2,6] 的和
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--) {
miaojiachun();
}
return 0;
}
模拟退火
竞赛中的经典应用场景
1. 旅行商问题(TSP)及其变种
- 问题:给定 ( n ) 个点,求最短的环路访问所有点。
- SA 解法:
- 邻域操作:随机交换两个城市 / 反转一段路径(2-opt)。
- 目标函数:路径总长度。
- 适用题目:
- ICPC/Codeforces 中的 小规模 TSP(( n \leq 100 ))。
- 变种:顺序依赖的路径优化(如先访问某些关键点)。
示例代码(TSP 的 SA 实现):
点击查看代码
double calc_dist(const vector<Point>& path) {
double sum = 0;
for (int i = 0; i + 1 < path.size(); i++)
sum += dist(path[i], path[i + 1]);
return sum;
}
vector<Point> solve_tsp_sa(vector<Point> cities) {
double T = 1e5, T_min = 1e-8, alpha = 0.99;
vector<Point> current = cities;
vector<Point> best = current;
while (T > T_min) {
vector<Point> new_path = current;
int i = rand() % new_path.size(), j = rand() % new_path.size();
swap(new_path[i], new_path[j]); // 邻域扰动:随机交换两个城市
double delta = calc_dist(new_path) - calc_dist(current);
if (delta < 0 || exp(-delta / T) > (double)rand() / RAND_MAX)
current = new_path;
if (calc_dist(current) < calc_dist(best))
best = current;
T *= alpha;
}
return best;
}
2. 函数最值问题
- 问题:给定复杂函数 ( f(x) ),求其在某区间的 最小值/最大值。
- SA 解法:
- 邻域操作:在当前解附近随机扰动(如 ( x_{\text{new}} = x + \text{random} \times T ))。
- 目标函数:直接计算 ( f(x) )。
- 适用题目:
- 数学题中的 多峰函数优化(如 Codeforces 某些求极值题)。
- 参数拟合(如给定数据点,求最优曲线)。
示例代码(求 ( f(x) = x \sin(x) ) 的最小值):
点击查看代码
double f(double x) { return x * sin(x); }
double solve_sa() {
double T = 1000, T_min = 1e-8, alpha = 0.99;
double x = rand() % 100, x_best = x;
while (T > T_min) {
double x_new = x + (rand() % 2000 - 1000) * T / 1000; // 邻域扰动
double delta = f(x_new) - f(x);
if (delta < 0 || exp(-delta / T) > (double)rand() / RAND_MAX)
x = x_new;
if (f(x) < f(x_best))
x_best = x;
T *= alpha;
}
return x_best;
}
3. 装箱问题(Bin Packing)
- 问题:将 ( n ) 个物品放入容量有限的箱子,求最少箱子数。
- SA 解法:
- 邻域操作:随机交换两个物品 / 移动某个物品到另一个箱子。
- 目标函数:使用的箱子数量(越小越好)。
- 适用题目:
- ICPC 中的 资源分配问题(如 POJ 1017)。
4. 图着色问题(Graph Coloring)
- 问题:用最少的颜色给图的顶点着色,且相邻顶点颜色不同。
- SA 解法:
- 邻域操作:随机改变一个顶点的颜色。
- 目标函数:冲突数(相同颜色相邻的边数)。
- 适用题目:
- 某些 NP-Hard 图论优化题(如 Codeforces 构造题)。
5. 任务调度问题(Scheduling)
- 问题:安排任务在机器上运行,最小化完成时间(Makespan)。
- SA 解法:
- 邻域操作:随机交换两个任务的顺序 / 移动任务到另一台机器。
- 目标函数:所有机器的最大负载。
- 适用题目:
- ICPC 中的 作业调度优化(如 UVa 10246)。
竞赛中的使用技巧
1. 参数调优
| 参数 | 典型取值 | 调整策略 |
|---|---|---|
| 初始温度 ( T ) | ( 10^3 \sim 10^5 ) | 越大越能跳出局部最优,但越慢 |
| 终止温度 ( T_{\text{min}} ) | ( 10^{-8} \sim 10^{-15} ) | 越小越精确,但可能超时 |
| 降温系数 ( \alpha ) | ( 0.95 \sim 0.999 ) | 越接近 1 降温越慢 |
经验法则:
- 如果 WA(Wrong Answer),尝试 提高初始温度 ( T ) 或 降低 ( \alpha )(降温更慢)。
- 如果 TLE(Time Limit Exceeded),尝试 减少迭代次数 或 增大 ( \alpha )(降温更快)。
2. 邻域设计
- 关键:如何生成“合理”的新解?
- TSP:交换两个城市 / 反转一段路径(2-opt)。
- 函数优化:在当前解附近随机扰动(( x \pm \text{random} \times T ))。
- 装箱问题:随机移动一个物品到另一个箱子。
3. 多次运行取最优
由于 SA 是随机算法,可以 运行多次(如 5~10 次) 并取最优解:
double best_ans = INF;
for (int i = 0; i < 10; i++) {
double ans = solve_sa();
best_ans = min(best_ans, ans);
}
哪些题目能用 SA?
Codeforces 经典例题
-
- 题意:将 ( n ) 个向量分成两组,使得两组和的模长差 ( \leq 1.5 \times 10^6 )。
- SA 解法:随机交换两个向量的分组,计算模长差。
-
- 题意:选 ( k ) 个队员和 ( p ) 个观众,最大化团队价值。
- SA 解法:随机调整队员/观众的选择状态。
-
- 题意:平衡括号序列的最小操作次数。
- SA 解法:随机交换两个括号,计算平衡度。
SA vs 其他算法
| 算法 | 适用场景 | 竞赛中的优势 |
|---|---|---|
| 模拟退火 | NP-Hard 优化问题 | 代码短,适合“骗分” |
| 遗传算法 | 大规模组合优化 | 并行性好,但实现复杂 |
| 动态规划 | 具有最优子结构的问题 | 精确解,但无法处理 NP-Hard |
| 贪心算法 | 局部最优 = 全局最优 | 高效,但可能 WA |
总结
- SA 在竞赛中的定位:
- “暴力优化”:当正解难想时,用 SA 随机化尝试。
- “骗分神器”:即使不能 AC,也可能拿到部分分。
- 适用问题特征:
- 解空间大、无多项式解法、允许近似解。
- 核心技巧:
- 调整温度参数 + 设计合理的邻域操作。
建议:在训练时尝试用 SA 解决几道经典题(如 TSP、函数极值),掌握调参手感,比赛中才能灵活应用! 🔥
权值线段树模板
int n;
const int N=5e5+10;
int nums[N<<2],tree[N<<2],temp[N<<2];
void build(int i,int pl,int pr)
{
if (pl==pr)
{
tree[i]=0; //元素出现的次数++
return;
}
int mid=(pl+pr)>>1;
build(i<<1,pl,mid);
build(i<<1|1,mid+1,pr);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
添加一个数字
往权值线段树中添加一个数字,则节点记录的就是这个数字出现的次数,因此递归到指定区间后,次数加1即可。
//添加数字
void update(int i, int pl, int pr, int x)
{
if (pl == pr)
{
sum[i]++; //到达了叶子节点,叶子节点维护的就是这个数字出现的次数
return;
}
int mid = (pl + pr) >> 1;
if (x <= mid) update(i << 1, pl, mid, x);
if (x > mid) update(i << 1 | 1, mid + 1, pr, x);
sum[i] = sum[i << 1] + sum[i << 1 | 1];
}
测试代码如下:结果肯定是没有问题的,每添加一个数字,每一个叶子节点就会更新为这个值出现的次数,根节点表示这个这个值域中的数字出现的次数
build(0,1,10);
for (int i = 1; i <= 10; i++)
{
update(1, 1, 10, i);
for (int i = 1; i <= 30; i++)
{
cout << sum[i] << " ";
}
cout << endl;
}
求某数出现的次数
递归寻找表示此数的区间
pl==pr表示到达叶子节点,返回sum[i]即可
int query(int i, int pl, int pr,int x)
{
//x表示要查询的值
if (pl == pr)
{
return sum[i];
}
int ans = 0;
int mid = (pl + pr) >> 1;
if (x <= mid) ans = query(i << 1, pl, mid, x);
if (x > mid) ans = query(i << 1 | 1, mid + 1, pr, x);
return ans;
}
查询一段区间中数字出现的次数
给出一段区间: [L,R] 为 [1,5] ,在权值线段树中查询 在这个区间里的所有的数出现的总次数,即1,2,3,4,5在权值线段树中从共出现了多少次
递归左右子树,找到完全覆盖的子区间
ans要 +=,否则会在一次查找后把ans原值覆盖。
int query(int i, int pl, int pr, int L, int R)
{
//[L,R]表示要查询的区间
if (L <= pl && pr <= R)
{
return sum[i];
}
int ans = 0;
int mid = (pl + pr) >> 1;
if (L <= mid) ans += query(i << 1, pl, mid, L, R);
if (R > mid) ans += query(i << 1 | 1, mid + 1, pr, L, R);
return ans;
}
查询整个值域中第K小的数
int query2(int i, int pl, int pr, int k)
{
/*
第k小的数:
首先求出左右孩子节点的元素个数 Ln=sum[i<<1] Rn=sum[i<<1|1]
1. 如果k小于等于Ln,说明第k小的元素在左子树中,则递归到左子树
2. 如果k大于Ln,说明第k小的数字在右子树中,则递归到右子树,同时注意如果k=8,左子树元素有5个,则在右子树中相当于寻找第 k-Ln 个,即第3个元素
*/
if (pl == pr)
{
return pl;
}
int ans = 0;
int mid = (pl + pr) >> 1;
int Ln = sum[i << 1]; //左孩子表示的元素个数
int Rn = sum[i << 1 | 1];//右孩子表示的元素个数
if (k <= Ln) ans = query2(i << 1, pl, mid, k); //左子树
else ans = query2(i << 1 | 1, mid + 1, pr, k - Ln); //右子树
return ans;
}
查询整个值域中第K大的数
注意:我们要求的是整个值域中,而不是任意区间。
int query3(int i, int pl, int pr, int k)
{
/*
第k大的数: 相当于逆着求第k小的数
首先求出左右孩子节点的元素个数 Ln=sum[i<<1] Rn=sum[i<<1|1]
1. 如果k小于等于Rn,则第k大的元素在右子树中,递归右子树
2. 如果k大于Rn,则第k大元素在左子树中,同时注意k=4 右子树元素有3个,则在左子树中相当于寻找第 k-Rn 个元素,即寻找第1个元素
*/
if (pl == pr)
{
return pl;
}
int ans = 0;
int mid = (pl + pr) >> 1;
int Ln = sum[i << 1]; //左孩子表示的元素个数
int Rn = sum[i << 1 | 1];//右孩子表示的元素个数
if (k <= Rn) ans = query3(i << 1|1, mid+1, pr, k); //右子树
else ans = query3(i << 1, pl, mid, k - Rn); //左子树
return ans;
}
树状数组
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class FenwickTree {
public:
vector<int> bit; // 树状数组
vector<int> sorted; // 离散化后的有序数组(升序)
// 离散化:将原始数据映射到紧凑的整数范围
vector<int> discretize(const vector<int>& nums) {
vector<int> tmp = nums;
sort(tmp.begin(), tmp.end());
tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
return tmp;
}
// 获取 x 的离散化排名(1-based)
int getRank(int x) const {
return lower_bound(sorted.begin(), sorted.end(), x) - sorted.begin() + 1;
}
// 树状数组核心操作
int lowbit(int x) const {
return x & -x;
}
// 单点更新:将位置 i 的值增加 delta
void add(int i, int delta) {
while (i < bit.size()) {
bit[i] += delta;
i += lowbit(i);
}
}
// 前缀查询:求 [1..i] 的和
int query(int i) const {
int sum = 0;
while (i > 0) {
sum += bit[i];
i -= lowbit(i);
}
return sum;
}
public:
// 构造函数:初始化离散化数组和树状数组
FenwickTree(const vector<int>& nums) {
sorted = discretize(nums);
bit.resize(sorted.size() + 1, 0); // 1-based
}
// 插入一个数 x
void insert(int x) {
int rank = getRank(x);
add(rank, 1);
}
// 删除一个数 x(假设 x 存在)
void erase(int x) {
int rank = getRank(x);
add(rank, -1);
}
// 查询严格小于 x 的数的个数(类似 order_of_key)
int orderOfKey(int x) const {
int rank = getRank(x);
return query(rank - 1);
}
// 查询第 k 小的数(0-based,类似 find_by_order)
int findByOrder(int k) const {
int low = 1, high = sorted.size();
while (low < high) {
int mid = (low + high) / 2;
if (query(mid) >= k + 1) {
high = mid;
} else {
low = mid + 1;
}
}
return sorted[low - 1]; // 转回 0-based
}
// 查询 x 的出现次数
int count(int x) const {
int rank = getRank(x);
if (rank > sorted.size() || sorted[rank - 1] != x) return 0;
return query(rank) - query(rank - 1);
}
// 查询区间 [x, y] 内数的个数
int rangeCount(int x, int y) const {
int rankX = getRank(x);
int rankY = getRank(y + 1); // y+1 转为 upper_bound
return query(rankY - 1) - query(rankX - 1);
}
};
// 测试代码
int main() {
vector<int> nums = {10, 20, 30, 10, 50, 20};
FenwickTree ft(nums);
// 插入数据
for (int x : nums) ft.insert(x);
// 测试 orderOfKey
cout << "Numbers < 25: " << ft.orderOfKey(25) << endl; // 输出 3(10, 10, 20)
cout << "Numbers < 30: " << ft.orderOfKey(30) << endl; // 输出 4(10, 10, 20, 20)
// 测试 findByOrder
cout << "2nd smallest: " << ft.findByOrder(2) << endl; // 输出 20
cout << "4th smallest: " << ft.findByOrder(4) << endl; // 输出 50
// 测试 count
cout << "Count of 10: " << ft.count(10) << endl; // 输出 2
cout << "Count of 99: " << ft.count(99) << endl; // 输出 0
// 测试 rangeCount
cout << "Numbers in [10, 30]: " << ft.rangeCount(10, 30) << endl; // 输出 5(10,10,20,20,30)
// 测试删除
ft.erase(10);
cout << "After erase 10, count: " << ft.count(10) << endl; // 输出 1
return 0;
}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
const int mod = 998244353;
const int N = 5e5 + 100;
class FenwickTree {
public:
vector<int> bit; // 树状数组
vector<int> sorted; // 离散化后的有序数组(升序)
// 离散化:将原始数据映射到紧凑的整数范围
vector<int> discretize(const vector<int>& nums) {
vector<int> tmp = nums;
sort(tmp.begin(), tmp.end());
tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
return tmp;
}
// 获取 x 的离散化排名(1-based)
int getRank(int x) const {
return lower_bound(sorted.begin(), sorted.end(), x) - sorted.begin() + 1;
}
// 树状数组核心操作
int lowbit(int x) const {
return x & -x;
}
// 单点更新:将位置 i 的值增加 delta
void add(int i, int delta) {
while (i < bit.size()) {
bit[i] += delta;
i += lowbit(i);
}
}
// 前缀查询:求 [1..i] 的和
int query(int i) const {
int sum = 0;
while (i > 0) {
sum += bit[i];
i -= lowbit(i);
}
return sum;
}
public:
// 构造函数:初始化离散化数组和树状数组
FenwickTree(const vector<int>& nums) {
sorted = discretize(nums);
bit.resize(sorted.size() + 1, 0); // 1-based
}
// 插入一个数 x
void insert(int x) {
int rank = getRank(x);
add(rank, 1);
}
// 删除一个数 x(假设 x 存在)
void erase(int x) {
int rank = getRank(x);
add(rank, -1);
}
// 查询严格小于 x 的数的个数(类似 order_of_key)
int orderOfKey(int x) const {
int rank = getRank(x);
return query(rank - 1);
}
// 查询第 k 小的数(0-based,类似 find_by_order)
int findByOrder(int k) const {
int low = 1, high = sorted.size();
while (low < high) {
int mid = (low + high) / 2;
if (query(mid) >= k + 1) {
high = mid;
} else {
low = mid + 1;
}
}
return sorted[low - 1]; // 转回 0-based
}
// 查询 x 的出现次数
int count(int x) const {
int rank = getRank(x);
if (rank > sorted.size() || sorted[rank - 1] != x) return 0;
return query(rank) - query(rank - 1);
}
// 查询区间 [x, y] 内数的个数
int rangeCount(int x, int y) const {
int rankX = getRank(x);
int rankY = getRank(y + 1); // y+1 转为 upper_bound
return query(rankY - 1) - query(rankX - 1);
}
};
void solve() {
int n, q;
cin >> n >> q;
vector<int> a(n);
vector<int> b(n);
unordered_map<int, int> u;
vector<int> tmp;
for (int i = 0; i < n; i++) {
cin >> a[i];
b[i] = a[i];
tmp.push_back(b[i]);
}
vector<int> v(q), w(q);
for (int i = 0; i < q; i++) {
cin >> v[i] >> w[i];
v[i]--;
tmp.push_back(b[v[i]] + w[i]);
b[v[i]] += w[i];
}
sort(tmp.begin(), tmp.end());
tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
int m = tmp.size();
for (int i = 0; i < m; i++) {
u[tmp[i]] = i + 1;
}
FenwickTree ft(tmp);
for (int i = 0; i < n; i++) {
ft.add(u[a[i]], 1);
}
for (int i = 0; i < q; i++) {
ft.add(u[a[v[i]]], -1);
ft.add(u[a[v[i]] + w[i]], 1);
a[v[i]] += w[i];
int l = 1, r = m; int ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (ft.query(mid) > (n + 1) / 2) {
r = mid - 1;
}else {
l = mid + 1;
ans = mid;
}
}
cout << ft.query(ans) << endl;
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(NULL);
int _1 = 1;
cin >> _1;
while (_1--) {
solve();
}
return 0;
}
| 操作 | 复杂度 |
|---|---|
insert(p) 插入一个元素 |
O(log n) |
erase(p) 删除一个元素 |
O(log n) |
find_by_order(k) 查询第 k 小 |
O(log n) |
order_of_key(p) 小于 p 的个数 |
O(log n) |
#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
#define int long long
#define ll __int128
#define PII pair<int,int>
#define endl '\n'
#define Pi acos(-1.0)
#define ull unsigned long long
int ls(int p){ return (p << 1) ;}
int rs(int p){ return (p << 1 | 1) ;}
inline int lowbit(int x) { return x & (-x) ;}
const int mod = 998244353;
const int N = 2e5 + 10;
const int INF = LLONG_MAX / 10;
int dx[] = {0 ,1 ,0 ,-1 ,1 ,-1,1,-1};
int dy[] = {1 ,0 ,-1 ,0 ,-1, 1,1,-1};
typedef tree<
pair<int, int>, // 键类型为pair<值, 唯一ID>
null_type,
less<pair<int, int>>, // pair默认按first排序
rb_tree_tag,
tree_order_statistics_node_update
> ordered_multiset;
void solve() {
int n, q;
cin >> n >> q;
ordered_multiset tr;
vector<int> a(n + 1);
for(int i = 1; i <= n; i++){
cin >> a[i];
tr.insert({a[i], i});
}
while(q--){
int p, w;
cin >> p >> w;
tr.erase({a[p],p});
a[p] += w;
tr.insert({a[p],p});
// 排名从0开始
auto it = tr.find_by_order(n - n / 2); //查找排名为n - n / 2 + 1 的数
int x = it -> first;
// cout << x << endl;
int ans = tr.order_of_key({x, 0}); // 查找严格小于x的数有多少
int ans1 = tr.order_of_key({x, INT_MAX}) //查找小于等于x的数
cout << ans << endl;
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr);
int O_O = 1;
cin >> O_O;
while(O_O--) solve();
return 0;
}
组合数
int qmi(int a, int k)
{
int res = 1;
while (k)
{
if (k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
int fact[N], infact[N];
void init() {
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i++) {
fact[i] = fact[i - 1] * i % mod;
infact[i] = infact[i - 1] * qmi(i, mod - 2) % mod;
}
}
int C(int a, int b) {
return fact[a] * infact[a - b] % mod * infact[b] % mod;
}
sum=xor+2×carry
也就是说,加法结果与异或结果之差必须是偶数。
a+b=(a⊕b)+2⋅(a&b)
质数判断
bool isprime(int x)
{
if(x==0||x==1) return 0;
if(x==2||x==3||x==5||x==7) return 1;
if(x%6!=1&&x%6!=5) return 0;
int num = sqrt(x);
for(int i = 5;i<=num;i+=6)
if(x%i==0||x%(i+2)==0)
return 0;
return 1;
}
线性基
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
#define int long long
#define pii pair<int, int>
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
const int N = 60;
const double PI = acos(-1);
bool flag;
int a[N + 1], tmp[N + 1];
void Insert(int x) {
for (int i = N; ~i; i--) {
if (x & (1ll << i)) {
if (!a[i]) {
a[i] = x;
return;
}else {
x ^= a[i];
}
}
}
flag = true; //线性基里至少有一个
}
bool check(int x) {
for (int i = N; ~i; i--) {
if (x & (1ll << i)) {
if (!a[i]) {
return false;
}else {
x ^= a[i];
}
}
}
return true;
}
int qmax(int res = 0) { // 找异或最大值
for (int i = N; ~i; i--) {
res = max(res, res ^ a[i]);
}
return res;
}
int qmin() { // 找异或最小值
if (flag) {
return 0;
}
for (int i = 0; i <= N; i++) {
if (a[i]) {
return a[i];
}
}
}
int query(int k) { // 查找第K小值
int res = 0;
int cnt = 0;
k -= flag;
if (!k) {
return 0;
}
for (int i = 0; i <= N; i++) {
for (int j = i - 1; ~j; j--) {
if (a[i] & (1ll << j)) {
a[i] ^= a[j];
}
}
if (a[i]) {
tmp[cnt++] = a[i];
}
}
if (k >= (1ll << cnt)) {
return -1;
}
for (int i = 0; i < cnt; i++) {
if (k & (1ll << i)) {
res ^= tmp[i];
}
}
return res;
}
int n;
int x;
void solve() {
cin >> n;
for (int i = 1; i < N; i++) {
a[i] = tmp[i] = 0;
}
for (int i = 1; i <= n; i++) {
cin >> x;
Insert(x);
}
cout << qmax() << endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(NULL);
int _1 = 1;
// cin >> _1;
while (_1--) {
solve();
}
return 0;
}
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
#define int long long
#define pii pair<int, int>
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
const int N = 60;
const double PI = acos(-1);
typedef tree<
int, // 键类型为pair<值, 唯一ID>
null_type,
less<int>, // pair默认按first排序
rb_tree_tag,
tree_order_statistics_node_update
> ordered_set;
int qmi(int a, int k)
{
int res = 1;
while (k)
{
if (k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
int fact[N], infact[N];
void init() {
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i++) {
fact[i] = fact[i - 1] * i % mod;
infact[i] = infact[i - 1] * qmi(i, mod - 2) % mod;
}
}
int C(int a, int b) {
return fact[a] * infact[a - b] % mod * infact[b] % mod;
}
bool isprime(int x)
{
if(x==0||x==1) return 0;
if(x==2||x==3||x==5||x==7) return 1;
if(x%6 != 1 && x%6 != 5) return 0;
int num = sqrt(x);
for(int i = 5; i <= num; i += 6)
if(x%i == 0 || x%(i+2) == 0)
return 0;
return 1;
}
// 复杂度log(N)^2
bool flag;
int bas[N + 1], tmp[N + 1];
int stk[N + 1], tl;
void Insert(int x) {
for (int i = N; ~i; i--) {
if (x & (1ll << i)) {
if (!bas[i]) {
bas[i] = x;
stk[++tl] = i;
return;
}else {
x ^= bas[i];
}
}
}
flag = true; //线性基里至少有一个
}
void pop() { // 弹出最后插入的元素
bas[stk[tl]] = 0;
tl--;
}
bool check(int x) {
for (int i = N; ~i; i--) {
if (x & (1ll << i)) {
if (!bas[i]) {
return false;
}else {
x ^= bas[i];
}
}
}
return true;
}
int qmax(int res = 0) { // 找异或最大值
for (int i = N; ~i; i--) {
res = max(res, res ^ bas[i]);
}
return res;
}
int qmin() { // 找异或最小值
if (flag) {
return 0;
}
for (int i = 0; i <= N; i++) {
if (bas[i]) {
return bas[i];
}
}
}
int query(int k) { // 查找第K小值
int res = 0;
int cnt = 0;
k -= flag;
if (!k) {
return 0;
}
for (int i = 0; i <= N; i++) {
for (int j = i - 1; ~j; j--) {
if (bas[i] & (1ll << j)) {
bas[i] ^= bas[j];
}
}
if (bas[i]) {
tmp[cnt++] = bas[i];
}
}
if (k >= (1ll << cnt)) {
return -1;
}
for (int i = 0; i < cnt; i++) {
if (k & (1ll << i)) {
res ^= tmp[i];
}
}
return res;
}
int n;
int a[N + 1];
int ans = 0;
void dfs(int x) {
if (x <= n) {
Insert(a[x]);
}
if (x + 1 >= n) {
ans = max(ans, qmax());
if (x <= n) {
pop();
}
return;
}
dfs(x + 2);
dfs(x + 3);
pop();
}
void solve() {
cin >> n;
for (int i = 1; i < N; i++) {
bas[i] = tmp[i] = 0;
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
ans = 0;
dfs(1);
if (n >= 2) {
dfs(2);
}
cout << ans << endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(NULL);
int _1 = 1;
cin >> _1;
while (_1--) {
solve();
}
return 0;
}
手写哈希
用法和map一样。
#define ull unsigned long long
const int LEN=(1<<23);
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct HashMap {
inline ull F(ull x) {
static const ull s1=rnd(),s2=rnd(),s3=rnd();
x+=s1;
x=(x^(x>>33))*s2;
x=(x^(x>>30))*s3;
return x;
}
bitset<LEN+5>hav;
ull key[LEN+5];
ull value[LEN+5];
int gt(const ull&x) {
int i=F(x)&(LEN-1);
while(hav[i]&&key[i]!=x)i=(i+1)&(LEN-1);
hav[i]=1,key[i]=x;
return i;
}
ull& operator[](const ull&x) {
return value[gt(x)];
}
}f;
倍增
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 二进制倍增(Binary Lifting) 结构
// 支持快速查询 k 层祖先、LCA 等操作
struct BinaryLifting {
int n, LOG;
vector<vector<int>> up; // up[k][v]: v 的 2^k 级祖先
vector<int> depth;
BinaryLifting(int _n) {
n = _n;
LOG = __lg(n) + 1;
up.assign(LOG, vector<int>(n, -1));
depth.assign(n, 0);
}
// 初始化:建立根为 root 的树,edges 为邻接表
void init(const vector<vector<int>>& edges, int root = 0) {
dfs(root, -1, 0, edges);
for (int k = 1; k < LOG; ++k) {
for (int v = 0; v < n; ++v) {
int pp = up[k-1][v];
up[k][v] = (pp < 0 ? -1 : up[k-1][pp]);
}
}
}
// 深度优先,记录父节点与深度
void dfs(int v, int p, int d, const vector<vector<int>>& edges) {
up[0][v] = p;
depth[v] = d;
for (int to : edges[v]) {
if (to == p) continue;
dfs(to, v, d+1, edges);
}
}
// 获取节点 v 的 k 层祖先,若不存在则返回 -1
int kth_ancestor(int v, int k) const {
for (int i = 0; i < LOG && v != -1; ++i) {
if (k & (1 << i)) v = up[i][v];
}
return v;
}
// 计算 u, v 的最近公共祖先 (LCA)
int lca(int u, int v) const {
if (depth[u] < depth[v]) swap(u, v);
// 先把 u 提升到与 v 同一深度
int diff = depth[u] - depth[v];
u = kth_ancestor(u, diff);
if (u == v) return u;
// 同步向上,找 LCA
for (int k = LOG - 1; k >= 0; --k) {
if (up[k][u] != up[k][v]) {
u = up[k][u];
v = up[k][v];
}
}
// 此时它们的父节点即为 LCA
return up[0][u];
}
// 距离 (u, v) 的节点数距离
int distance(int u, int v) const {
int w = lca(u, v);
return depth[u] + depth[v] - 2 * depth[w];
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n; cin >> n;
vector<vector<int>> edges(n);
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
--u; --v;
edges[u].push_back(v);
edges[v].push_back(u);
}
BinaryLifting bl(n);
bl.init(edges, 0); // 以 0 为根
// 示例:查询节点 5 的 3 层祖先
int node = 5, k = 3;
int anc = bl.kth_ancestor(node, k);
cout << "The " << k << "-th ancestor of " << node << " is " << anc << '\n';
// 示例:查询 LCA
int u = 3, v = 7;
cout << "LCA of " << u << " and " << v << " is " << bl.lca(u, v) << '\n';
return 0;
}
FTT
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int MAXN = 1e7 + 10;
inline int read() {
char c = getchar(); int x = 0, f = 1;
while (c < '0' || c > '9') {if (c == '-')f = -1; c = getchar();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return x * f;
}
const double Pi = acos(-1.0);
struct complex {
double x, y;
complex (double xx = 0, double yy = 0) {x = xx, y = yy;}
} a[MAXN], b[MAXN];
complex operator + (complex a, complex b) { return complex(a.x + b.x , a.y + b.y);}
complex operator - (complex a, complex b) { return complex(a.x - b.x , a.y - b.y);}
complex operator * (complex a, complex b) { return complex(a.x * b.x - a.y * b.y , a.x * b.y + a.y * b.x);} //不懂的看复数的运算那部分
int N, M;
int l, r[MAXN];
int limit = 1;
void fast_fast_tle(complex *A, int type) {
for (int i = 0; i < limit; i++)
if (i < r[i]) swap(A[i], A[r[i]]); //求出要迭代的序列
for (int mid = 1; mid < limit; mid <<= 1) { //待合并区间的长度的一半
complex Wn( cos(Pi / mid) , type * sin(Pi / mid) ); //单位根
for (int R = mid << 1, j = 0; j < limit; j += R) { //R是区间的长度,j表示前已经到哪个位置了
complex w(1, 0); //幂
for (int k = 0; k < mid; k++, w = w * Wn) { //枚举左半部分
complex x = A[j + k], y = w * A[j + mid + k]; //蝴蝶效应
A[j + k] = x + y;
A[j + mid + k] = x - y;
}
}
}
}
int main() {
int N = read(), M = read();
for (int i = 0; i <= N; i++) a[i].x = read();
for (int i = 0; i <= M; i++) b[i].x = read();
while (limit <= N + M) limit <<= 1, l++;
for (int i = 0; i < limit; i++)
r[i] = ( r[i >> 1] >> 1 ) | ( (i & 1) << (l - 1) ) ;
// 在原序列中 i 与 i/2 的关系是 : i可以看做是i/2的二进制上的每一位左移一位得来
// 那么在反转后的数组中就需要右移一位,同时特殊处理一下奇数
fast_fast_tle(a, 1);
fast_fast_tle(b, 1);
for (int i = 0; i <= limit; i++) a[i] = a[i] * b[i];
fast_fast_tle(a, -1);
for (int i = 0; i <= N + M; i++)
printf("%d ", (int)(a[i].x / limit + 0.5));
return 0;
}
快读快写
inline void read(int &n){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
n=x*f;
}
inline void print(int n){
if(n<0){
putchar('-');
n*=-1;
}
if(n>9) print(n/10);
putchar(n % 10 + '0');
}
以下是算法竞赛中常用的数学求和公式及其C++实现:
1. 等差数列求和
公式:S = n(a₁ + aₙ)/2
// 等差数列求和
long long arithmeticSum(long long a1, long long an, long long n) {
return n * (a1 + an) / 2;
}
2. 等比数列求和
公式:S = a₁(1 - rⁿ)/(1 - r) (r ≠ 1)
// 等比数列求和
long long geometricSum(long long a1, long long r, long long n) {
if (r == 1) return a1 * n;
return a1 * (1 - pow(r, n)) / (1 - r);
}
// 带模数的版本(防止溢出)
long long geometricSumMod(long long a1, long long r, long long n, long long mod) {
if (r == 1) return (a1 % mod) * (n % mod) % mod;
long long numerator = (1 - powMod(r, n, mod * (r - 1))) % (mod * (r - 1));
numerator = numerator / (r - 1);
return (a1 % mod) * numerator % mod;
}
// 快速幂实现
long long powMod(long long base, long long exp, long long mod) {
long long res = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) res = (res * base) % mod;
base = (base * base) % mod;
exp >>= 1;
}
return res;
}
3. 平方和公式
公式:1² + 2² + ... + n² = n(n+1)(2n+1)/6
long long sumOfSquares(long long n) {
return n * (n + 1) * (2 * n + 1) / 6;
}
4. 立方和公式
公式:1³ + 2³ + ... + n³ = [n(n+1)/2]²
long long sumOfCubes(long long n) {
long long s = n * (n + 1) / 2;
return s * s;
}
5. 调和级数近似
公式:Hₙ ≈ ln(n) + γ + 1/(2n) (γ ≈ 0.5772156649)
#include <cmath>
const double EULER_MASCHERONI = 0.5772156649;
double harmonicApprox(long long n) {
if (n < 1000) { // 小n直接计算更精确
double sum = 0;
for (int i = 1; i <= n; ++i) sum += 1.0 / i;
return sum;
}
return log(n) + EULER_MASCHERONI + 1.0 / (2 * n);
}
6. 前缀和技巧(用于多次查询)
// 预处理前缀和数组
vector<int> preprocessPrefixSum(const vector<int>& nums) {
vector<int> prefix(nums.size() + 1, 0);
for (int i = 0; i < nums.size(); ++i) {
prefix[i + 1] = prefix[i] + nums[i];
}
return prefix;
}
// 查询区间和 [l, r] (0-based)
int querySum(const vector<int>& prefix, int l, int r) {
return prefix[r + 1] - prefix[l];
}
7. 二维前缀和(矩阵求和)
// 预处理二维前缀和
vector<vector<int>> preprocess2DPrefixSum(const vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> prefix(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
prefix[i + 1][j + 1] = matrix[i][j] + prefix[i][j + 1]
+ prefix[i + 1][j] - prefix[i][j];
}
}
return prefix;
}
// 查询子矩阵和 [(x1,y1)到(x2,y2)]
int query2DSum(const vector<vector<int>>& prefix,
int x1, int y1, int x2, int y2) {
return prefix[x2][y2] - prefix[x1][y2]
- prefix[x2][y1] + prefix[x1][y1];
}
8. 快速求连续数字异或和
// 计算1^2^...^n的快速方法
long long xorSumUpToN(long long n) {
switch(n % 4) {
case 0: return n;
case 1: return 1;
case 2: return n + 1;
case 3: return 0;
}
return 0; // 避免编译警告
}
// 计算a^(a+1)^...^b
long long xorRange(long long a, long long b) {
return xorSumUpToN(b) ^ xorSumUpToN(a - 1);
}
9. 组合数求和
// 预处理组合数表(动态规划)
vector<vector<long long>> precomputeCombinations(int max_n, int mod = 1e9+7) {
vector<vector<long long>> C(max_n + 1, vector<long long>(max_n + 1, 0));
for (int n = 0; n <= max_n; ++n) {
C[n][0] = C[n][n] = 1;
for (int k = 1; k < n; ++k) {
C[n][k] = (C[n-1][k-1] + C[n-1][k]) % mod;
}
}
return C;
}
// 二项式系数和:C(n,0) + C(n,1) + ... + C(n,k)
long long sumOfCombinations(const vector<vector<long long>>& C, int n, int k) {
long long sum = 0;
for (int i = 0; i <= k; ++i) {
sum = (sum + C[n][i]) % mod;
}
return sum;
}
10. 欧拉求和公式(数值积分近似)
// 近似求和 ∑f(k) for k=1 to n
double eulerMaclaurinSum(double (*f)(double), int n) {
double sum = (f(1) + f(n)) / 2.0;
for (int k = 2; k < n; ++k) {
sum += f(k);
}
// 可以添加更高阶的修正项
return sum;
}
1. 交错级数求和
问题描述:
计算奇偶交替和:
1−2+3−4+⋯±n
代码实现
long long alternatingSum(long long n) {
if (n % 2 == 0) return -n / 2;
else return (n + 1) / 2;
}
2. 多项式求和
问题描述:
计算多项式求和:
![]()
代码实现
// 使用分离求和法
long long polySum(long long a, long long b, long long c, long long d, long long n) {
return a * sumOfCubes(n)
+ b * sumOfSquares(n)
+ c * n * (n + 1) / 2
+ d * n;
}
3. 数论相关求和
3.1 约数个数和函数
问题描述:
计算1~n所有数的约数个数和:

代码实现
long long divisorSum(long long n) {
long long res = 0;
for (long long i = 1; i <= n; ) {
long long q = n / i;
long long next_i = n / q + 1;
res += q * (next_i - i);
i = next_i;
}
return res;
}
3.2 欧拉函数和
问题描述:
计算欧拉函数前缀和:
$$\sum_{k=1}^n \varphi(k)$$
代码实现
vector<long long> precomputePhiSum(int max_n) {
vector<long long> phi(max_n + 1);
iota(phi.begin(), phi.end(), 0);
for (int p = 2; p <= max_n; ++p) {
if (phi[p] == p) { // p是质数
for (int m = p; m <= max_n; m += p) {
phi[m] -= phi[m] / p;
}
}
}
// 计算前缀和
partial_sum(phi.begin(), phi.end(), phi.begin());
return phi;
}
4. 特殊数列求和
4.1 斐波那契数列和
公式:

代码实现
long long fibSum(long long n, long long mod = 1e9+7) {
if (n == 0) return 0;
// 使用矩阵快速幂计算F(n+2)
auto multiply = [&](vector<vector<long long>> &a, vector<vector<long long>> &b) {
vector<vector<long long>> res(2, vector<long long>(2));
res[0][0] = (a[0][0]*b[0][0] + a[0][1]*b[1][0]) % mod;
res[0][1] = (a[0][0]*b[0][1] + a[0][1]*b[1][1]) % mod;
res[1][0] = (a[1][0]*b[0][0] + a[1][1]*b[1][0]) % mod;
res[1][1] = (a[1][0]*b[0][1] + a[1][1]*b[1][1]) % mod;
return res;
};
vector<vector<long long>> mat = {{1, 1}, {1, 0}};
vector<vector<long long>> res = {{1, 0}, {0, 1}};
long long power = n + 1; // 计算F(n+2)
while (power > 0) {
if (power & 1) res = multiply(res, mat);
mat = multiply(mat, mat);
power >>= 1;
}
return (res[0][0] - 1 + mod) % mod;
}
4.2 平方斐波那契和
公式:

代码实现
long long fibSquareSum(long long n, long long mod = 1e9+7) {
if (n == 0) return 0;
// 计算F(n)和F(n+1)
auto multiply = [&](vector<vector<long long>> &a, vector<vector<long long>> &b) {
vector<vector<long long>> res(2, vector<long long>(2));
res[0][0] = (a[0][0]*b[0][0] + a[0][1]*b[1][0]) % mod;
res[0][1] = (a[0][0]*b[0][1] + a[0][1]*b[1][1]) % mod;
res[1][0] = (a[1][0]*b[0][0] + a[1][1]*b[1][0]) % mod;
res[1][1] = (a[1][0]*b[0][1] + a[1][1]*b[1][1]) % mod;
return res;
};
vector<vector<long long>> mat = {{1, 1}, {1, 0}};
vector<vector<long long>> fib_n = {{1, 0}, {0, 1}};
vector<vector<long long>> fib_n1 = {{1, 0}, {0, 1}};
long long power = n - 1;
while (power > 0) {
if (power & 1) {
fib_n = multiply(fib_n, mat);
}
mat = multiply(mat, mat);
power >>= 1;
}
fib_n1 = multiply(fib_n, mat);
long long fn = fib_n[0][0];
long long fn1 = fib_n1[0][0];
return (fn * fn1) % mod;
}
5. 数位相关求和
问题描述:
计算1~n所有数的数字位之和
代码实现
long long digitSum(long long n) {
if (n < 10) return n * (n + 1) / 2;
long long d = log10(n);
long long p = pow(10, d);
long long msd = n / p;
return msd * d * (p / 10) * 45
+ (msd * (msd - 1) / 2) * p
+ msd * (1 + n % p)
+ digitSum(n % p);
}
6. 组合恒等式求和
曲棍球棒恒等式
公式:

代码实现
// 直接使用组合数性质计算
long long hockeyStickSum(long long n, long long k, long long mod = 1e9+7) {
return combination(n + 1, k + 1, mod);
}
// 组合数计算函数
long long combination(long long n, long long k, long long mod) {
if (k > n) return 0;
if (k * 2 > n) k = n - k;
if (k == 0) return 1;
long long res = n;
for (long long i = 2; i <= k; ++i) {
res = res * (n - i + 1) / i;
}
return res % mod;
}
7. 多重求和公式
问题描述:
优化二重求和计算:
$$\sum_{i=1}^n \sum_{j=1}^m a[i][j]$$
使用前缀和优化
vector<vector<long long>> precompute2DSum(const vector<vector<int>>& matrix) {
int n = matrix.size(), m = matrix[0].size();
vector<vector<long long>> prefix(n + 1, vector<long long>(m + 1, 0));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j]
+ prefix[i][j-1] - prefix[i-1][j-1];
}
}
return prefix;
}
long long doubleSum(const vector<vector<long long>>& prefix,
int i1, int j1, int i2, int j2) {
return prefix[i2][j2] - prefix[i1-1][j2]
- prefix[i2][j1-1] + prefix[i1-1][j1-1];
}
8. 数论分块求和
问题描述:
计算除法取整和:
$$\sum_{i=1}^n \left\lfloor \frac{n}{i} \right\rfloor$$
代码实现
long long floorDivisionSum(long long n) {
long long res = 0;
for (long long i = 1, j; i <= n; i = j + 1) {
j = n / (n / i);
res += (n / i) * (j - i + 1);
}
return res;
}
9. 高维单形体数求和

| n | 三角数 S_n | 四面体数 T_n | 五胞体数 P_n |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | 3 | 4 | 5 |
| 3 | 6 | 10 | 15 |
| 4 | 10 | 20 | 35 |
| 5 | 15 | 35 | 70 |
9.6 模运算优化
四面体数模运算:
$$T_n \equiv n(n+1)(n+2) \cdot \text{inv}(6) \pmod{m}$$
其中 inv(6) 是 6 的模逆元(需保证 $\gcd(6,m)=1$)。
10. 图论中的环计数公式
公式:
若某连通分量的顶点数为 n,边数为 m,则它包含的独立环数量等于:
r = m - n + 1
直观理解: 一棵树 m = n - 1,无环;多出来的边每增加 1,最多增加 1 个独立环。
斜率优化dp



实现的是求上凸包,只需将直线按照斜率从大到小,加入ConvexHullTrix中。
点击查看代码
struct Line {
int k;
int64_t b;
Line() {}
Line(int k, int64_t b) : k(k), b(b) {}
// 求两直线交点横坐标
double intersect(const Line& l) const { return 1.0 * (l.b - b) / (k - l.k); }
bool operator<(const Line& rhs) const {
if (k == rhs.k) return b > rhs.b;
return k < rhs.k;
}
int64_t operator()(int64_t x) const { return k * x + b; }
};
struct ConvexHullTrick {
vector<double> points;
vector<Line> lines;
int size() {
return points.size();
}
void reset() {
points.clear();
lines.clear();
}
void init(const Line& l) {
points.push_back(-inf);
lines.push_back(l);
}
/**
* 如果有平行的情况,在后外部进行处理
*/
void addLine(const Line& l) {
if (points.size() == 0) {
points.push_back(-inf);
lines.push_back(l);
return;
}
/* 注释掉这段用于下凸包
if (l.k == lines.back().k) {
if (l.b > lines.back().b) {
lines.pop_back();
if (!points.empty()) points.pop_back();
} else {
return;
}
}
*/
while (lines.size() >= 2 &&
l.intersect(lines[lines.size() - 2]) <= points.back()) {
points.pop_back();
lines.pop_back();
}
points.push_back(l.intersect(lines.back()));
lines.push_back(l);
}
int64_t query(int x, int id) { return lines[id](x); }
int64_t query(int x) {
int id = upper_bound(points.begin(), points.end(), x) - points.begin() - 1;
return lines[id](x);
}
};

浙公网安备 33010602011771号