板子

NTT固定模数

点击查看代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int MOD = 998244353;
const int G = 3; // 原根

// 快速幂
long long modpow(long long a, long long b, long long mod = MOD) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

// 逆元
long long modinv(long long a, long long mod = MOD) {
    return modpow(a, mod - 2, mod);
}

// NTT 核心函数
void ntt(vector<long long> &a, bool inv) {
    int n = a.size();
    // 位逆序置换
    for (int i = 1, j = 0; i < n; i++) {
        int bit = n >> 1;
        for (; j & bit; bit >>= 1) j ^= bit;
        j ^= bit;
        if (i < j) swap(a[i], a[j]);
    }
    // 迭代合并
    for (int len = 2; len <= n; len <<= 1) {
        long long wlen = modpow(G, (MOD - 1) / len, MOD);
        if (inv) wlen = modinv(wlen, MOD);
        for (int i = 0; i < n; i += len) {
            long long w = 1;
            for (int j = 0; j < len / 2; j++) {
                long long u = a[i + j], v = a[i + j + len / 2] * w % MOD;
                a[i + j] = (u + v) % MOD;
                a[i + j + len / 2] = (u - v + MOD) % MOD;
                w = w * wlen % MOD;
            }
        }
    }
    // 逆变换需要除以 n
    if (inv) {
        long long inv_n = modinv(n, MOD);
        for (long long &x : a) x = x * inv_n % MOD;
    }
}

// 卷积函数
vector<long long> convolution(vector<long long> a, vector<long long> b) {
    int n = 1;
    while (n < a.size() + b.size()) n <<= 1; // 扩展为2的幂
    a.resize(n); b.resize(n);
    ntt(a, false); ntt(b, false);
    for (int i = 0; i < n; i++) a[i] = a[i] * b[i] % MOD;
    ntt(a, true);
    return a;
}

// 预处理阶乘和逆元(组合数学常用)
vector<long long> fact, inv_fact;
void precompute_factorials(int maxn) {
    fact.resize(maxn + 1);
    inv_fact.resize(maxn + 1);
    fact[0] = 1;
    for (int i = 1; i <= maxn; i++) 
        fact[i] = fact[i - 1] * i % MOD;
    inv_fact[maxn] = modinv(fact[maxn], MOD);
    for (int i = maxn - 1; i >= 0; i--) 
        inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD;
}

// 使用示例
int main() {
    // 示例:计算多项式 (1+2x) * (1+x^2)
    vector<long long> a = {1, 2};    // 1 + 2x
    vector<long long> b = {1, 0, 1}; // 1 + x^2
    
    auto c = convolution(a, b);
    // c = [1, 2, 1, 2] -> 1 + 2x + x^2 + 2x^3
    
    for (int i = 0; i < c.size(); i++) {
        if (c[i] != 0) cout << c[i] << "x^" << i << " + ";
    }
    cout << endl;
    
    return 0;
}

NTT不固定模数

例子:https://atcoder.jp/contests/abc422/tasks/abc422_g

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf = LLONG_MAX;
#define pii pair<int, int> 
const int N = 1e5 + 100;
// const int mod = 998244353;
#define lll __int128

// 通用 NTT 类模板
template<int MOD, int G>
struct NTT {
    using ll = long long;
    
    ll modpow(ll a, ll b, ll mod = MOD) {
        ll res = 1;
        while (b) {
            if (b & 1) res = res * a % mod;
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }

    ll modinv(ll a, ll mod = MOD) {
        return modpow(a, mod - 2, mod);
    }

    void ntt(vector<ll> &a, bool inv) {
        int n = a.size();
        for (int i = 1, j = 0; i < n; i++) {
            int bit = n >> 1;
            for (; j & bit; bit >>= 1) j ^= bit;
            j ^= bit;
            if (i < j) swap(a[i], a[j]);
        }
        for (int len = 2; len <= n; len <<= 1) {
            ll wlen = modpow(G, (MOD - 1) / len, MOD);
            if (inv) wlen = modinv(wlen, MOD);
            for (int i = 0; i < n; i += len) {
                ll w = 1;
                for (int j = 0; j < len / 2; j++) {
                    ll u = a[i + j], v = a[i + j + len / 2] * w % MOD;
                    a[i + j] = (u + v) % MOD;
                    a[i + j + len / 2] = (u - v + MOD) % MOD;
                    w = w * wlen % MOD;
                }
            }
        }
        if (inv) {
            ll inv_n = modinv(n, MOD);
            for (ll &x : a) x = x * inv_n % MOD;
        }
    }

    vector<ll> convolution(vector<ll> a, vector<ll> b) {
        int n = 1;
        while (n < a.size() + b.size()) n <<= 1;
        a.resize(n); b.resize(n);
        ntt(a, false); ntt(b, false);
        for (int i = 0; i < n; i++) a[i] = a[i] * b[i] % MOD;
        ntt(a, true);
        return a;
    }
};

// 常用模数预设
using NTT_998244353 = NTT<998244353, 3>;
using NTT_1004535809 = NTT<1004535809, 3>;
using NTT_469762049 = NTT<469762049, 3>;

// 三模数 NTT(用于任意模数)
struct ArbitraryModNTT {
    using ll = long long;
    int MOD;
    
    ArbitraryModNTT(int mod) : MOD(mod) {}
    
    // 中国剩余定理合并
    ll crt(const vector<ll>& rem, const vector<ll>& mods) {
        ll M = 1;
        for (ll m : mods) M *= m;
        
        ll res = 0;
        for (int i = 0; i < rem.size(); i++) {
            ll Mi = M / mods[i];
            ll inv_Mi = modinv(Mi, mods[i]);
            res = (res + rem[i] * Mi % M * inv_Mi % M) % M;
        }
        return res % MOD;
    }
    
    ll modpow(ll a, ll b, ll mod) {
        ll res = 1;
        while (b) {
            if (b & 1) res = res * a % mod;
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }

    ll modinv(ll a, ll mod) {
        return modpow(a, mod - 2, mod);
    }
    
    vector<ll> convolution(vector<ll> a, vector<ll> b) {
        if (MOD == 998244353) {
            return NTT_998244353().convolution(a, b);
        }
        if (MOD == 1004535809) {
            return NTT_1004535809().convolution(a, b);
        }
        if (MOD == 469762049) {
            return NTT_469762049().convolution(a, b);
        }
        
        // 对于其他模数,使用三模数 NTT + CRT
        auto res1 = NTT_998244353().convolution(a, b);
        auto res2 = NTT_1004535809().convolution(a, b);
        auto res3 = NTT_469762049().convolution(a, b);
        
        vector<ll> result(res1.size());
        vector<ll> mods = {998244353, 1004535809, 469762049};
        
        for (int i = 0; i < result.size(); i++) {
            vector<ll> rems = {res1[i], res2[i], res3[i]};
            result[i] = crt(rems, mods) % MOD;
        }
        
        return result;
    }
};

void solve() {
    int n, a, b, c, mod;
    mod = 998244353;
    cin >> n >> a >> b >> c;
    
    ArbitraryModNTT ntt_solver(mod);
    
    // 预处理阶乘和逆元
    vector<long long> fact(n + 1), infact(n + 1);
    fact[0] = 1;
    for (int i = 1; i <= n; i++) {
        fact[i] = fact[i - 1] * i % mod;
    }

    auto modinv = [&](long long a, long long m) {
        long long res = 1, b = m - 2;
        while (b) {
            if (b & 1) res = res * a % m;
            a = a * a % m;
            b >>= 1;
        }
        return res;
    };
    
    infact[n] = modinv(fact[n], mod);
    for (int i = n - 1; i >= 0; i--) {
        infact[i] = infact[i + 1] * (i + 1) % mod;
    }

    // 问题1:球不可区分
    vector<long long> a1(n + 1, 0), b1(n + 1, 0), c1(n + 1, 0);
    for (int i = 0; i <= n; i += a) a1[i] = 1;
    for (int i = 0; i <= n; i += b) b1[i] = 1;
    for (int i = 0; i <= n; i += c) c1[i] = 1;

    auto ab1 = ntt_solver.convolution(a1, b1);
    ab1.resize(n + 1);
    auto abc1 = ntt_solver.convolution(ab1, c1);
    long long ans1 = abc1[n] % mod;

    // 问题2:球可区分
    vector<long long> a2(n + 1, 0), b2(n + 1, 0), c2(n + 1, 0);
    for (int i = 0; i <= n; i += a) a2[i] = infact[i];
    for (int i = 0; i <= n; i += b) b2[i] = infact[i];
    for (int i = 0; i <= n; i += c) c2[i] = infact[i];

    auto ab2 = ntt_solver.convolution(a2, b2);
    ab2.resize(n + 1);
    auto abc2 = ntt_solver.convolution(ab2, c2);
    long long ans2 = abc2[n] * fact[n] % mod;

    cout << ans1 << endl;
    cout << ans2 << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    // cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

处理模数运算的自定义数据类型

点击查看代码
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

点击查看代码
struct segtree
{
	int ct, rt;
	vector<int> ls, rs;
	vector<int> tg, mx;
	segtree(int _n) : ct(0), rt(0)
	{
		ls = rs = vector<int>(2 * _n + 3, 0);
		mx = tg = vector<int>(2 * _n + 3, 0);
	}
	void down(int x)
	{
		if (tg[x])
		{
			mx[ls[x]] += tg[x], tg[ls[x]] += tg[x];
			mx[rs[x]] += tg[x], tg[rs[x]] += tg[x];
			tg[x] = 0;
		}
	}
	void up(int x)
	{
		mx[x] = min(mx[ls[x]], mx[rs[x]]);
	}
	void add(int x, int l, int r, int ql, int qr, int v)
	{
		if (ql <= l && r <= qr)
			return tg[x] += v, mx[x] += v, void();
		down(x);
		int mid = (l + r) / 2;
		if (ql <= mid)
			add(ls[x], l, mid, ql, qr, v);
		if (mid + 1 <= qr)
			add(rs[x], mid + 1, r, ql, qr, v);
		up(x);
	}
	int ask1(int x, int l, int r, int ql, int qr)
	{
		if (ql <= l && r <= qr)
			return mx[x];
		down(x);
		int ret = LLONG_MAX;
		int mid = (l + r) / 2;
		if (ql <= mid)
			ret = min(ret, ask1(ls[x], l, mid, ql, qr));
		if (mid + 1 <= qr)
			ret = min(ret, ask1(rs[x], mid + 1, r, ql, qr));
		return ret;
	}
	int ask2(int x, int l, int r)
	{
		if (l == r)
			return l;
		down(x);
		int mid = (l + r) / 2;
		if (mx[ls[x]] == 0)
			return ask2(ls[x], l, mid);
		else
			return ask2(rs[x], mid + 1, r);
	}
	void cst(int &x, int l, int r)
	{
		x = ++ct;
		if (l == r)
			return;
		int mid = (l + r) / 2;
		cst(ls[x], l, mid);
		cst(rs[x], mid + 1, r);
		up(x);
	}
};

圆,已知三点求圆心,求投影点,求半径,求与某条直线的关系。

点击查看代码
#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
*/

求割点 洛谷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;
#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;
const int XX=999999999;
class DSU {
private:
	vector<int> parent;  // 存储每个节点的父节点
	vector<int> rank;    // 存储树的秩(近似树的高度)
	vector<int> size;    // 存储集合的大小(可选)
	int count;           // 连通分量的数量
	
public:
	// 构造函数,初始化n个元素
	DSU(int n) : count(n) {
		parent.resize(n+1);
		rank.resize(n+1, 0);
		size.resize(n+1, 1);  // 初始每个集合大小为1
		for (int i = 1; i <= n; ++i) {
			parent[i] = i;  // 初始时每个元素的父节点是自己
		}
	}
	
	// 查找根节点,带路径压缩
	int find(int x) {
		if (parent[x] != x) {
			parent[x] = find(parent[x]);  // 路径压缩
		}
		return parent[x];
	}
	
	// 合并两个集合,按秩合并
	void unionSet(int x, int y) {
		int rootX = find(x);
		int rootY = find(y);
		
		if (rootX == rootY) return;  // 已经在同一集合
		
		// 按秩合并
		if (rank[rootX] < rank[rootY]) {
			parent[rootX] = rootY;
			size[rootY] += size[rootX];  // 更新集合大小
		} else if (rank[rootX] > rank[rootY]) {
			parent[rootY] = rootX;
			size[rootX] += size[rootY];  // 更新集合大小
		} else {
			parent[rootY] = rootX;
			size[rootX] += size[rootY];  // 更新集合大小
			rank[rootX]++;              // 秩增加
		}
		count--;  // 连通分量减少
	}
	
	// 检查两个元素是否在同一集合
	bool connected(int x, int y) {
		return find(x) == find(y);
	}
	
	// 获取连通分量的数量
	int getCount() const {
		return count;
	}
	
	// 获取元素所在集合的大小(可选功能)
	int getSize(int x) {
		int root = find(x);
		return size[root];
	}
};
void miaojiachun() {
	int n;
	cin>>n;
	DSU dsu(n);
	vector<int>p(n+1),d(n+1),c(n+1),cc(n+1);
	for(int i=1;i<=n;i++){
		cin>>p[i];
	}
	for(int i=1;i<=n;i++){
		cin>>d[i];
	}
	for(int i=1;i<=n;i++){
		int now=i;
		while(p[now]!=now and !c[now]){
			c[now]=1;
			dsu.unionSet(now,p[now]);
			now=p[now];
		}
	}
	
	int ans=0;
	for(int i=1;i<=n;i++){
		if(!cc[dsu.find(d[i])]){
			cc[dsu.find(d[i])]=1;
			ans+=dsu.getSize(d[i]);
		}
		cout<<ans<<" ";
	}
	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;
}

Bellman-Ford算法的限制边数版本(负边权)

点击查看代码
//#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;
struct xx{
	int x,y,z;
};
void solve() {
	
	int n, m, k;
	cin >> n >> m >>k;
	vector<xx> a(m + 1);
	for(int i=1;i<=m;i++){
		cin >> a[i].x >> a[i].y >> a[i].z;
	}
	
	vector<int> dist(n + 1, inf);
	dist[1] = 0;
	for (int i = 0; i < k; i++){
		vector<int> temp = dist;
		for(int j = 1; j <= m; j++ ){
//			const auto& v = a[j];
			if(dist[a[j].x] != inf and temp[a[j].y] > dist[a[j].x] + a[j].z){
				temp[a[j].y] = dist[a[j].x] + a[j].z;
			}
		}
		dist = temp;
	}
	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;
}

Bellman-Ford算法普通版本

点击查看代码
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

const int INF = INT_MAX;

vector<int> bellman_ford(vector<vector<pair<int, int>>>& graph, int start, int n) {
    vector<int> dist(n + 1, INF);  // 存储起点到各点的最短距离
    dist[start] = 0;

    // 进行 n-1 次松弛操作
    for (int i = 0; i < n - 1; ++i) {
        bool updated = false;  // 标记本轮是否更新
        for (int u = 1; u <= n; ++u) {
            if (dist[u] == INF) continue;  // 跳过未访问的节点
            for (auto& edge : graph[u]) {
                int v = edge.first;
                int w = edge.second;
                if (dist[v] > dist[u] + w) {
                    dist[v] = dist[u] + w;
                    updated = true;
                }
            }
        }
        if (!updated) break;  // 提前终止(无更新时)
    }

    // 检测负权环(第 n 次松弛)
    bool has_negative_cycle = false;
    for (int u = 1; u <= n; ++u) {
        if (dist[u] == INF) continue;
        for (auto& edge : graph[u]) {
            int v = edge.first;
            int w = edge.second;
            if (dist[v] > dist[u] + w) {
                has_negative_cycle = true;
                break;
            }
        }
        if (has_negative_cycle) break;
    }

    if (has_negative_cycle) {
        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});
        // 如果是无向图,需添加反向边:
        // graph[v].push_back({u, w});
    }

    int start = 1;  // 假设起点是1
    vector<int> dist = bellman_ford(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;
}

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. 判断有向图是否有环


· 2. DAG上的动态规划(DP)


· 3. 字典序最小拓扑序


· 4. 分层图或依赖关系问题


· 5. 强连通分量(SCC)分解的前置步骤


· 6. 关键路径(AOE网络)

  • 典型问题:求工程的最早/最晚开始时间。
  • 解法:拓扑排序后正向和反向各跑一次DP。
  • 例题

· 7. 竞赛中的特殊构造题


· 8. 2-SAT问题


· 竞赛技巧总结

  1. 拓扑排序+BFS/DFS

    • 大部分问题直接用Kahn算法(BFS+入度表)即可。
    • 字典序最小拓扑序用优先队列优化。
  2. 拓扑排序+DP

    • DAG上的DP必须按拓扑序计算,确保无后效性。
  3. 拓扑排序判环

    • 如果最终拓扑序列长度 < n,说明有环。
  4. 扩展应用

    • 结合SCC、2-SAT等高级算法时,拓扑序常作为中间步骤。

· 模板代码(C++)

#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>();
}

prim算法求最小生成树

点击查看代码
//#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>> e(N);
void solve() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int x, y, z;
		cin >> x >> y >> z;
		e[x].push_back({y, z});
		e[y].push_back({x, z});
	}
	
	vector<int> dist(n + 1, inf);
	vector<bool> vis(n + 1, false);
	
	priority_queue<pii, vector<pii>, greater<pii>> q;
	
	dist[1] = 0;
//	vis[1] = 1;
	
	q.push({dist[1], 1});
	
	int res = 0;
	int edd = 0;
	
	while(q.size() and edd < n) {
		auto [w, u] = q.top();//找到离起点最近的节点
		q.pop();
		if(vis[u]) continue;//已经处理则跳过。
		vis[u] = true;
		res += w;//累加边权到总边权。
		edd += 1;//已加入节点数加 1
		for (auto& edge : e[u]) {
			int v = edge.first;
			int wit = edge.second;
			if (vis[v]) continue; 
			if (dist[v] > wit) {//如果找到更小的边权。
				dist[v] = wit;//更新最小边权。
				q.push({dist[v], v});//加入队列。
			}
		}
	}
//	
//	for (int i = 1; i <= n; i++) { 
//		cout << dist[i] << " ";
//	}
//	cout << endl;
	
	if(edd == n){
		cout << res << endl;
	}else {
		cout << "impossible" <<endl;	
	}
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	
	int t = 1;
//	cin >> t;
	
	while (t--) {
		solve();
	}
	return 0;
}

树状数组单点修改,区间求和。

例题
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)的应用场景
旋转卡壳是一种在计算几何中用于处理凸多边形或凸包问题的高效算法,它通过"旋转"一对平行卡尺来扫描凸包,解决多种几何优化问题。

主要应用问题

  1. 计算凸包直径(最远点对)
    问题:找到凸包上距离最远的两个点
    解法:用旋转卡壳寻找对踵点对(antipodal points)
    复杂度:O(n)(凸包已构建的情况下)

  2. 计算凸多边形宽度
    问题:找到凸多边形的最小宽度(平行线间的最小距离)
    解法:旋转卡尺寻找最小距离的平行切线对

  3. 计算最小面积/周长外接矩形
    问题:找到能包围凸多边形的最小面积或最小周长的矩形
    解法:旋转卡尺确定矩形的边与凸包的切线关系

  4. 多边形间最大距离
    问题:计算两个凸多边形之间的最大距离
    解法:在两个凸包的合并凸包上应用旋转卡壳

  5. 凸多边形合并
    问题:合并两个凸多边形为一个新的凸包
    解法:配合旋转卡壳技术高效合并

  6. 线段与凸多边形相交判定
    问题:判断线段是否与凸多边形相交
    解法:利用旋转卡壳快速排除不可能情况

  7. 最大空圆问题
    问题:在给定点集中找到最大半径的空圆(不包含任何输入点)
    解法:基于凸包和旋转卡壳技术

算法竞赛中的典型应用

最远点对问题(凸包直径)

给定平面点集,求两点间最大距离

先求凸包,再用旋转卡壳找直径

最小包围矩形问题

求包围所有点的最小面积/周长矩形

旋转卡壳确定矩形方向

碰撞检测

判断两个凸多边形是否可能相交

使用旋转卡壳计算分离轴

洛谷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;
}

数学

高斯消元板子

点击查看代码
#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;
}
点击查看代码
#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.resize(n);
    
    vector<int> col(n);  // 记录列交换情况
    
    for (int i = 0; i < n; i++) {
        // 1. 找主元
        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) {
            continue;  // 自由变量
        }
        
        // 2. 交换行
        swap(a[i], a[pivot]);
        
        // 3. 消元
        for (int j = 0; j < n; j++) {
            if (j != i && fabs(a[j][i]) > eps) {
                double ratio = a[j][i] / a[i][i];
                for (int k = i; k <= n; k++) {
                    a[j][k] -= a[i][k] * ratio;
                }
            }
        }
    }
    
    // 检查解的情况
    int free_var = 0;
    for (int i = 0; i < n; i++) {
        bool all_zero = true;
        for (int j = 0; j < n; j++) {
            if (fabs(a[i][j]) > eps) {
                all_zero = false;
                break;
            }
        }
        
        if (all_zero) {
            if (fabs(a[i][n]) > eps) {
                return -1;  // 无解
            }
            free_var++;
        }
    }
    
    if (free_var) return 1;  // 无穷多解
    
    // 回代求解
    for (int i = 0; i < n; i++) {
        ans[i] = a[i][n] / a[i][i];
    }
    
    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";
}

区间最值线段树

posted @ 2025-03-17 23:44  miao-jc  阅读(33)  评论(0)    收藏  举报