5.21前校个人赛模板整理

争取以后板子可以以引用自己博客为主(

基础

排序

快排
无swap版

void QuickSort(int arr, int left, int right)
{
	if(left >= right)
		return;
	int i = left, j = right, pivot = arr[left];
	while(i < j)
	{
		while(i<j && arr[j]>=pivot) j--;
		arr[i] = arr[j];//可以看作每次都是和pivot交换,所以交换后的位置可以先不赋值
		while(i<j && arr[i]<=pivot) i++;
		arr[j] = arr[i];
	}
	arr[i] = pivot;//最后一次交换后要赋值,当然不交换时也成立。
	QuickSort(arr, left, i-1);
	QuickSort(arr, i+1, right);
}

重载运算符

//外置函数时不需要友元函数
struct  node{
    string name;
    int price;
    friend bool operator < (node a, node b){
        return a.price>b.price;
    }
};

搜索

双向搜索(字面意思)

迭代加深
防BFS炸空间

代码框架
IDDFS(u,d)
    if d>limit
        return
    else
        for each edge (u,v)
            IDDFS(v,d+1)
  return

IDA*
A* + 迭代加深
例题:埃及分数
迭代保证解的最优性和状态的空间可储存。
A*集中体现在剪枝上。
搜索优化
基本技巧:记忆化搜索,最优性剪枝,可行性剪枝
剪枝思路:极端法(最好情况下仍不满足),调整法(通过对子树的比较剪掉重复子树和明显不是最有“前途”的子树),数学法(主要是不等式,数论,图论)

高精度

//作者:小黑AWM+MashPlant
//注:可以直接把BigInt和一样用cin cout都行,就是高精乘为了速度才用了FFT降低了精度,有需要可以自行更改。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const double PI = acos(-1.0);
struct Complex{
    double x,y;
    Complex(double _x = 0.0,double _y = 0.0){
        x = _x;
        y = _y;
    }
    Complex operator-(const Complex &b)const{
        return Complex(x - b.x,y - b.y);
    }
    Complex operator+(const Complex &b)const{
        return Complex(x + b.x,y + b.y);
    }
    Complex operator*(const Complex &b)const{
        return Complex(x*b.x - y*b.y,x*b.y + y*b.x);
    }
};
void change(Complex y[],int len){
    int i,j,k;
    for(int i = 1,j = len/2;i<len-1;i++){
        if(i < j)    swap(y[i],y[j]);
        k = len/2;
        while(j >= k){
            j = j - k;
            k = k/2;
        }
        if(j < k)    j+=k;
    }
}
void fft(Complex y[],int len,int on){
    change(y,len);
    for(int h = 2;h <= len;h<<=1){
        Complex wn(cos(on*2*PI/h),sin(on*2*PI/h));
        for(int j = 0;j < len;j += h){
            Complex w(1,0);
            for(int k = j;k < j + h/2;k++){
                Complex u = y[k];
                Complex t = w*y[k + h/2];
                y[k] = u + t;
                y[k + h/2] = u - t;
                w = w*wn;
            }
        }
    }
    if(on == -1){
        for(int i = 0;i < len;i++){
            y[i].x /= len;
        }
    }
}
class BigInt
{
#define Value(x, nega) ((nega) ? -(x) : (x))
#define At(vec, index) ((index) < vec.size() ? vec[(index)] : 0)
    static int absComp(const BigInt &lhs, const BigInt &rhs)
    {
        if (lhs.size() != rhs.size())
            return lhs.size() < rhs.size() ? -1 : 1;
        for (int i = lhs.size() - 1; i >= 0; --i)
            if (lhs[i] != rhs[i])
                return lhs[i] < rhs[i] ? -1 : 1;
        return 0;
    }
    using Long = long long;
    const static int Exp = 9;
    const static Long Mod = 1000000000;
    mutable std::vector<Long> val;
    mutable bool nega = false;
    void trim() const
    {
        while (val.size() && val.back() == 0)
            val.pop_back();
        if (val.empty())
            nega = false;
    }
    int size() const { return val.size(); }
    Long &operator[](int index) const { return val[index]; }
    Long &back() const { return val.back(); }
    BigInt(int size, bool nega) : val(size), nega(nega) {}
    BigInt(const std::vector<Long> &val, bool nega) : val(val), nega(nega) {}

public:
    friend std::ostream &operator<<(std::ostream &os, const BigInt &n)
    {
        if (n.size())
        {
            if (n.nega)
                putchar('-');
            for (int i = n.size() - 1; i >= 0; --i)
            {
                if (i == n.size() - 1)
                    printf("%lld", n[i]);
                else
                    printf("%0*lld", n.Exp, n[i]);
            }
        }
        else
            putchar('0');
        return os;
    }
    friend BigInt operator+(const BigInt &lhs, const BigInt &rhs)
    {
        BigInt ret(lhs);
        return ret += rhs;
    }
    friend BigInt operator-(const BigInt &lhs, const BigInt &rhs)
    {
        BigInt ret(lhs);
        return ret -= rhs;
    }
    BigInt(Long x = 0)
    {
        if (x < 0)
            x = -x, nega = true;
        while (x >= Mod)
            val.push_back(x % Mod), x /= Mod;
        if (x)
            val.push_back(x);
    }
    BigInt(const char *s)
    {
        int bound = 0, pos;
        if (s[0] == '-')
            nega = true, bound = 1;
        Long cur = 0, pow = 1;
        for (pos = strlen(s) - 1; pos >= Exp + bound - 1; pos -= Exp, val.push_back(cur), cur = 0, pow = 1)
            for (int i = pos; i > pos - Exp; --i)
                cur += (s[i] - '0') * pow, pow *= 10;
        for (cur = 0, pow = 1; pos >= bound; --pos)
            cur += (s[pos] - '0') * pow, pow *= 10;
        if (cur)
            val.push_back(cur);
    }
    BigInt &operator=(const char *s){
        BigInt n(s);
        *this = n;
        return n;
    }
    BigInt &operator=(const Long x){
        BigInt n(x);
        *this = n;
        return n;
    }
    friend std::istream &operator>>(std::istream &is, BigInt &n){
        string s;
        is >> s;
        n=(char*)s.data();
        return is;
    }
    BigInt &operator+=(const BigInt &rhs)
    {
        const int cap = std::max(size(), rhs.size()) + 1;
        val.resize(cap);
        int carry = 0;
        for (int i = 0; i < cap - 1; ++i)
        {
            val[i] = Value(val[i], nega) + Value(At(rhs, i), rhs.nega) + carry, carry = 0;
            if (val[i] >= Mod)
                val[i] -= Mod, carry = 1;
            else if (val[i] < 0)
                val[i] += Mod, carry = -1;
        }
        if ((val.back() = carry) == -1) //assert(val.back() == 1 or 0 or -1)
        {
            nega = true, val.pop_back();
            bool tailZero = true;
            for (int i = 0; i < cap - 1; ++i)
            {
                if (tailZero && val[i])
                    val[i] = Mod - val[i], tailZero = false;
                else
                    val[i] = Mod - 1 - val[i];
            }
        }
        trim();
        return *this;
    }
    friend BigInt operator-(const BigInt &rhs)
    {
        BigInt ret(rhs);
        ret.nega ^= 1;
        return ret;
    }
    BigInt &operator-=(const BigInt &rhs)
    {
        rhs.nega ^= 1;
        *this += rhs;
        rhs.nega ^= 1;
        return *this;
    }
    friend BigInt operator*(const BigInt &lhs, const BigInt &rhs)
    {
        int len=1;
        BigInt ll=lhs,rr=rhs;
        ll.nega = lhs.nega ^ rhs.nega;
        while(len<2*lhs.size()||len<2*rhs.size())len<<=1;
        ll.val.resize(len),rr.val.resize(len);
        Complex x1[len],x2[len];
        for(int i=0;i<len;i++){
            Complex nx(ll[i],0.0),ny(rr[i],0.0);
            x1[i]=nx;
            x2[i]=ny;
        }
        fft(x1,len,1);
        fft(x2,len,1);
        for(int i = 0 ; i < len; i++)
            x1[i] = x1[i] * x2[i];
        fft( x1 , len , -1 );
        for(int i = 0 ; i < len; i++)
            ll[i] = int( x1[i].x + 0.5 );
        for(int i = 0 ; i < len; i++){
            ll[i+1]+=ll[i]/Mod;
            ll[i]%=Mod;
        }
        ll.trim();
        return ll;
    }
    friend BigInt operator*(const BigInt &lhs, const Long &x){
        BigInt ret=lhs;
        bool negat = ( x < 0 );
        Long xx = (negat) ? -x : x;
        ret.nega ^= negat;
        ret.val.push_back(0);
        ret.val.push_back(0);
        for(int i = 0; i < ret.size(); i++)
            ret[i]*=xx;
        for(int i = 0; i < ret.size(); i++){
            ret[i+1]+=ret[i]/Mod;
            ret[i] %= Mod;
        }
        ret.trim();
        return ret;
    }
    BigInt &operator*=(const BigInt &rhs) { return *this = *this * rhs; }
    BigInt &operator*=(const Long &x) { return *this = *this * x; }
    friend BigInt operator/(const BigInt &lhs, const BigInt &rhs)
    {
        static std::vector<BigInt> powTwo{BigInt(1)};
        static std::vector<BigInt> estimate;
        estimate.clear();
        if (absComp(lhs, rhs) < 0)
            return BigInt();
        BigInt cur = rhs;
        int cmp;
        while ((cmp = absComp(cur, lhs)) <= 0)
        {
            estimate.push_back(cur), cur += cur;
            if (estimate.size() >= powTwo.size())
                powTwo.push_back(powTwo.back() + powTwo.back());
        }
        if (cmp == 0)
            return BigInt(powTwo.back().val, lhs.nega ^ rhs.nega);
        BigInt ret = powTwo[estimate.size() - 1];
        cur = estimate[estimate.size() - 1];
        for (int i = estimate.size() - 1; i >= 0 && cmp != 0; --i)
            if ((cmp = absComp(cur + estimate[i], lhs)) <= 0)
                cur += estimate[i], ret += powTwo[i];
        ret.nega = lhs.nega ^ rhs.nega;
        return ret;
    }
    friend BigInt operator/(const BigInt &num,const Long &x){
        bool negat = ( x < 0 );
        Long xx = (negat) ? -x : x;
        BigInt ret;
        Long k = 0;
        ret.val.resize( num.size() );
        ret.nega = (num.nega ^ negat);
        for(int i = num.size() - 1 ;i >= 0; i--){
            ret[i] = ( k * Mod + num[i]) / xx;
            k = ( k * Mod + num[i]) % xx;
        }
        ret.trim();
        return ret;
    }
    bool operator==(const BigInt &rhs) const
    {
        return nega == rhs.nega && val == rhs.val;
    }
    bool operator!=(const BigInt &rhs) const { return nega != rhs.nega || val != rhs.val; }
    bool operator>=(const BigInt &rhs) const { return !(*this < rhs); }
    bool operator>(const BigInt &rhs) const { return !(*this <= rhs); }
    bool operator<=(const BigInt &rhs) const
    {
        if (nega && !rhs.nega)
            return true;
        if (!nega && rhs.nega)
            return false;
        int cmp = absComp(*this, rhs);
        return nega ? cmp >= 0 : cmp <= 0;
    }
    bool operator<(const BigInt &rhs) const
    {
        if (nega && !rhs.nega)
            return true;
        if (!nega && rhs.nega)
            return false;
        return (absComp(*this, rhs) < 0) ^ nega;
    }
    void swap(const BigInt &rhs) const
    {
        std::swap(val, rhs.val);
        std::swap(nega, rhs.nega);
    }
};
BigInt ba,bb;
int main(){
    cin>>ba>>bb;
    cout << ba + bb << '\n';//和
    cout << ba - bb << '\n';//差
    cout << ba * bb << '\n';//积
    BigInt d;
    cout << (d = ba / bb) << '\n';//商
    cout << ba - d * bb << '\n';//余
    return 0;
}

背包问题

oi-wiki
可行性背包&&最大价值背包

01背包从后向前

完全背包从前向后

多重背包(二进制优化或单调队列优化)

二维背包(f数组改成二维)

分组背包(每组择一,循环顺序:组别,空间,组中元素01背包)

有依赖的背包(树上分组背包)

泛化背包(函数引用,数学优化)

求方案数(01和完全背包近似,多重可以用队列维护)

字符串

字符串(p)review
Hash

KMP
匹配失败用nxt跳,构造nxt函数也同理

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+100;
char s1[maxn],s2[maxn];
int pre[maxn];
int l1,l2;
inline void pre_kmp(){
	for(int i=l2;i>=1;i--)s2[i]=s2[i-1];
	for(int i=l1;i>=1;i--)s1[i]=s1[i-1];
	int j=0;pre[1]=0;
	for(int i=2;i<=l2;i++){//preÖÁÉÙÏòÇ°ÒÆÒ»Î» 
		while(j&&s2[i]!=s2[j+1])j=pre[j];
		if(s2[i]==s2[j+1])j++;
		pre[i]=j;
	}
}
int main(){
	cin>>s1,l1=strlen(s1);
	cin>>s2,l2=strlen(s2);
	pre_kmp();
	int j=0;
	for(int i=1;i<=l1;i++){
		while(j&&s1[i]!=s2[j+1])j=pre[j];
		if(s1[i]==s2[j+1])j++;
		if(j==l2){
			cout<<i-j+1<<endl;
			j=pre[j];
		}
	}
	for(int i=1;i<=l2;i++)
		cout<<pre[i]<<" ";
	return 0;
}

AC自动机
有 N 个由小写字母组成的模式串以及一个文本串 T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串 T 中出现的次数最多。
T<=1e6,N<=50,len(模式串)<=70

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxson=26;
const int maxn=200;
const int maxm=1e6+100;
struct node{
	int end;int fail;
	int son[maxson];
}a[maxn*maxn];
int cnt=1;
int n;
struct stri{
	char s[maxn];
	int ls,cnt,id;
}st[maxn];
char t[maxm];
int lt;
inline void ins(int id){
	int pos=0;
	for(int i=1;i<=st[id].ls;i++){
		if(!a[pos].son[st[id].s[i]])a[pos].son[st[id].s[i]]=cnt++;
		pos=a[pos].son[st[id].s[i]];
	}
	a[pos].end=id;
}
inline void get_fail(){
	queue<int>q;
	for(int i=0;i<maxson;i++)
		if(a[0].son[i])q.push(a[0].son[i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<maxson;i++){
			if(a[u].son[i]){
				a[a[u].son[i]].fail=a[a[u].fail].son[i];//uµÄfailµÄson£¬ÔÚbfsǰ¼¸²ãÒѾ­´¦ÀíºÃÁË 1.son²»Îª¿Õ£¬½Ô´ó»¶Ï²£»2sonΪ¿Õ£¬Ç°¼¸²ãÒÑͨ¹ýest[i].lseÍê³ÉʧÅ仨ÍËÁ´£¬´Ë´¦Á´+1
				q.push(a[u].son[i]);  
			}
			else a[u].son[i]=a[a[u].fail].son[i];//×Ó½ÚµãΪ¿ÕÖ±½ÓʧÅ䣬ʡȥif 
		}
	}
}
bool cmp(stri x,stri y){
	if(x.cnt>y.cnt)return 1;
	else if(x.cnt==y.cnt&&x.id<y.id)return 1;
	return 0;
}
inline void recover(int x){
	for(int i=1;i<=st[x].ls;i++)
		st[x].s[i]+='a';
}
int main(){
	cin>>n;
	while(n){
		memset(a,0,sizeof(a));
		memset(st,0,sizeof(st));cnt=1;
		for(int i=1;i<=n;i++){
			scanf("%s",st[i].s+1);
			st[i].ls=strlen(st[i].s+1),st[i].id=i;
			for(int j=1;j<=st[i].ls;j++) st[i].s[j]-='a';
			ins(i);
		}
		get_fail();
		scanf("%s",t+1);
		lt=strlen(t+1);
		for(int i=1;i<=lt;i++) t[i]-='a';
		int pos=0,j;
		for(int i=1;i<=lt;i++){
			pos=a[pos].son[t[i]],j=pos;
			while(j)st[a[j].end].cnt++,j=a[j].fail;
		}
		sort(st+1,st+n+1,cmp);
		int x=1;
		recover(x);
		cout<<st[x].cnt<<endl;
		cout<<st[x].s+1<<endl;
		while(x+1<=n&&st[x+1].cnt==st[x].cnt){
			x++;recover(x);cout<<st[x].s+1<<endl;
		}
		cin>>n;
	}
	return 0;
}

manacher算法
任意i,有len[i]>=min(mx-i,len[pos2-i])
判奇不判偶,注意偶回文串的处理

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1.1e7+1000;
char s[maxn<<1];int n;
int len[maxn<<1];
int main(){
    int kase=0;
    while(1){
        scanf("%s",s+1);n=strlen(s+1);
        if(s[1]=='E'&&s[2]=='N'&&s[3]=='D')break;
        for(int i=n<<1;i>=2;i-=2)s[i]=s[i>>1],s[i-1]='*';s[0]='*';
        n=(n<<1)+1,s[n]='*',s[n+1]=0;
        int pos=0,mr=0,ans=0;
        for(int i=1;i<=n;i++){
            if(mr>1)len[i]=min(mr-i,len[(pos<<1)-i]);
            else len[i]=1;
            while(s[i-len[i]]==s[i+len[i]])len[i]++;
            if(i+len[i]>mr){
                mr=i+len[i];
                pos=i;
            }
            ans=max(ans,len[i]-1);
        }
        printf("Case %d: %d\n",++kase,ans);
    }
    return 0;
}

没有后缀数组(SA)

数学

高斯消元
这里介绍的是高斯-约旦消元法。

相对于传统的高斯消元,约旦消元法的精度更好、代码更简单,没有回带的过程。

约旦消元法大致思路如下:

1.选择一个尚未被选过的未知数作为主元,选择一个包含这个主元的方程。

2.将这个方程主元的系数化为1。

3.通过加减消元,消掉其它方程中的这个未知数。

4.重复以上步骤,直到把每一行都变成只有一项有系数。

我们用矩阵表示每一项系数以及结果

代码如下:

#include<bits/stdc++.h>
#define re register
#define il inline
#define debug printf("Now is %d\n",__LINE__);
using namespace std;
#define maxn 105
#define D double
D a[maxn][maxn];
int n;
int main()
{
	scanf("%d",&n);
	for(re int i=1;i<=n;++i)
	{
		for(re int j=1;j<=n+1;++j)
		{
			scanf("%lf",&a[i][j]);
		}
	}
	for(re int i=1;i<=n;++i)//枚举列(项) 
	{
		re int max=i;
		for(re int j=i+1;j<=n;++j)//选出该列最大系数 
		{
			if(fabs(a[j][i])>fabs(a[max][i]))
            //fabs是取浮点数的绝对值的函数
			{
				max=j;
			}
		}
		for(re int j=1;j<=n+1;++j)//交换
		{
			swap(a[i][j],a[max][j]);
		}
		if(!a[i][i])//最大值等于0则说明该列都为0,肯定无解 
		{
			puts("No Solution");
			return 0;
		}
		for(re int j=1;j<=n;++j)//每一项都减去一个数(即加减消元)
		{
			if(j!=i)
			{
				re D temp=a[j][i]/a[i][i];
				for(re int k=i+1;k<=n+1;++k)
				{
					a[j][k]-=a[i][k]*temp;
                    //a[j][k]-=a[j][i]*a[i][k]/a[i][i];
				}
			}
		}
	}
    //上述操作结束后,矩阵会变成这样
    /*
    k1*a=e1
    k2*b=e2
    k3*c=e3
    k4*d=e4
    */
    //所以输出的结果要记得除以该项系数,消去常数
	for(re int i=1;i<=n;++i)
	{
		printf("%.2lf\n",a[i][n+1]/a[i][i]);
	}
	return 0;
}

矩阵乘法(幂)(模拟即可)
phi
线性筛

void init(int n) {
    phi[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!st[i]) prime[cnt++] = i, phi[i] = i - 1;  // i是质数,phi[i] = i - 1
        for (int j = 0; prime[j] <= n / i; ++j) {
            st[prime[j] * i] = 1;
            if (i % prime[j] == 0) {
                phi[i * prime[j]] = phi[i] * prime[j];  // i % pj == 0, phi[i * pj] = phi[i] * pj
                break;
            }
            phi[i * prime[j]] = phi[i] * (prime[j] - 1);  // i % pj != 0, phi[i * pj] = phi[i] * (pj - 1)
        }
    }
    return;
}

单独判phi(质因数分解)

质数分解
Pollard_Rho算法

ll Pollard_Rho(ll N)
{
    if (N == 4) // 特判4
        return 2;
    if (is_prime(N)) // 特判质数
        return N;
    while (1)
    {
        ll c = randint(1, N - 1); // 生成随机的c
        auto f = [=](ll x) { return ((lll)x * x + c) % N; }; // lll表示__int128,防溢出
        ll t = f(0), r = f(f(0));
        while (t != r)
        {
            ll d = gcd(abs(t - r), N);
            if (d > 1)
                return d;
            t = f(t), r = f(f(r));
        }
    }
}

大素数判别
miller-rabin算法
本质是对费马小定理的逆用,大部分卡迈克尔数不能被检验

bool Miller_Rabin(LL n)
{
    if(n==2||n==3||n==5||n==7||n==11||n==13||n==17||n==41) return true;
    if(n==1||n%2==0||n%3==0||n%5==0||n%7==0||n%11==0||n%41==0||n%17==0) return false;
    if (n%13==0) return false;
    int div2=0;
    LL tn=n-1;
    while( !(tn&1) )
    {
        div2++;
        tn>>=1;//脱平方根 
    }
    for(int tt=0;tt<TIMES;tt++)
    {
        LL x=GetRandom(n-1); //随机得到[1,n-1]
        if(x==1) continue;
        x=Quk_Mul(x,tn,n);
        LL pre=x;//平凡平方根,最小根可以为p-1,其余必须为1
        for(int j=0;j<div2;j++)
        {
            x = Mod_Mul(x, x, n);
            if(x==1&&pre!=1&&pre!=n-1) return false;
            pre=x;
        }
        if(x!=1) return false;
    }
    return true;
}

博弈论
公平组合游戏三原则:
定理 1:没有后继状态的状态是必败状态。
定理 2:一个状态是必胜状态当且仅当存在至少一个必败状态为它的后继状态。
定理 3:一个状态是必败状态当且仅当它的所有后继状态均为必胜状态。

基础解法:
用一数组记录博弈状态,由三原则可以写出记忆化搜索的状态转移方程。
其复杂度为O(N+M)

Nim游戏:
n堆物品,每堆有ai个,两个玩家轮流取走任意一堆的任意个物品,但不能不取。

取走最后一个物品的人获胜。

Nim和:
通过绘画博弈图,我们可以在Θ(∏ni=1ai)的时间里求出该局面是否先手必赢。
然而这个时间复杂度我们是不能接受,也难以从算法层面优化的。
这时引入Nim和(Nim−sum)
Nim−sum=a1⨁a2⨁⋯⨁an
可以证明
Nim−sum=0时为必胜状态,Nim−sum≠0时为必败状态

Nim定理证明:
为建立Nim和与必败必胜状态的关系,需要仿证Nim和的三定理:
定理一:没有后继状态的状态是必败态
定理二:任意a1⨁a2⨁⋯⨁an≠0的状态都可以通过一次操作后,使得a1⨁a2⨁⋯⨁an=0
定理三:任意a1⨁a2⨁⋯⨁an=0它的后继不存在a1⨁a2⨁⋯⨁an=0的状态

SG函数
sg[i]为除后继状态的最小值。
sg[i]分等于0和大于0两种状态,本质是对必胜必败的数字描述

数据结构

树状数组
本质是log的求和式
用法一,a[i]=原值,求和式为和函数,单点修改区间求和
用法二,a[i]=差分,求和式为原数列,区间增减单点查询
用法三,对差分方式优化,可以做到区间增减区间查询

//区间修改区间查询
add1(x,delta),add1(y+1,-delta);
add2(x,delta*x),add2(y+1,-delta*(y+1);
ans=y*ask1(y)-ask2(y)-((x-1)*ask1(x-1)-ask2(x-1));

可持久化线段树
于数据结构而言,可持久化总是可以保留每一个历史版本,并且支持操作的不可变特性。
而可持久化线段树的本质就是多根线段树,每个根都代表一个历史版本。
当我们在修改关键值时,只有相关区间的节点内的值会受到影响,而树本身结构并未发生变化,那么在每次修改时,不再是在原来的版本上下功夫,而是创建一系列新的节点,从而达到可持久化的目的。
brave_cattle

#include<bits/stdc++.h>
using namespace std;
const int N=200000+5;

int n, m, size, cnt = 0;
int w[N];//用于记录原数组
int s[N];//用于记录去重排序后数组
int rk[N];//用于记录原数组的元素是第几小
int root[N];//用于记录每个根节点的编号

struct President_tree{//不要管什么总统树什么树的变量名啦
    int ls, rs, sum;//ls,rs分别记录一个节点的左右儿子编号,sum记录经过该节点的次数
}t[N*20];

int gi(){//读入优化
    int ans = 0 , f = 1; char i = getchar();
    while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
    while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
    return ans * f;
}

void build(int &node,int l,int r){//建一颗空树(虽然不建也没什么关系)
    node = ++cnt;
    if(l == r) return;
    int mid = (l+r>>1);
    build(t[node].ls,l,mid);
    build(t[node].rs,mid+1,r);
}

void updata(int &node,int last,int l,int r,int s){
    node = ++cnt; t[node]=t[last]; ++t[node].sum;//通过上一次修改的值来修改
    if(l == r) return;
    int mid = (l+r>>1);
    if(s <= mid) updata(t[node].ls,t[last].ls,l,mid,s);//如果最后要插到叶子节点的位置在mid左边,就往左插
    else updata(t[node].rs,t[last].rs,mid+1,r,s);//同理
}

int query(int node,int last,int l,int r,int k){
    if(l == r) return s[l];//查询到了叶子节点时,就找到了是在去重排序后序列中第L位置的值
    int sum = t[t[node].ls].sum-t[t[last].ls].sum , mid = (l+r>>1);//类似于splay的查询第k大
    if(k <= sum) return query(t[node].ls , t[last].ls , l , mid , k);
    else return query(t[node].rs , t[last].rs , mid+1 , r , k-sum);
}

int main(){
    int x, y, k; n = gi(); m = gi();
    for(int i=1;i<=n;i++) w[i] = gi();
    memcpy(s,w,sizeof(s));
    sort(s+1,s+n+1);//排序
    size = unique(s+1,s+n+1)-s-1;//unique可以将数组去重,因为unique返回的是一个地址,而数组以1开始,所以排序后的数组大小为这个值
    build(root[0],1,size);
    for(int i=1;i<=n;i++) rk[i] = lower_bound(s+1,s+size+1,w[i])-s;//处理每个数字是第几小,要在已经去重后的s数组中查找
    for(int i=1;i<=n;i++)
	updata(root[i],root[i-1],1,size,rk[i]);//将每次修改后的线段树存下来,以根节点来找整棵树
    for(int i=1;i<=m;i++){
	x = gi(); y = gi(); k = gi();
	printf("%d\n",query(root[y],root[x-1],1,size,k));
    }
    return 0;
}

树链剖分
LCA

struct Edge{
	int v,nxt;
}edge[maxn];
int head[maxn],tot=0;
inline void read(int &x){
	x=0;char tmp=getchar();
	while(tmp<'0'||tmp>'9')tmp=getchar();
	while(tmp>='0'&&tmp<='9')x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
inline void add_edge(int x,int y){
	edge[tot].v=y,edge[tot].nxt=head[x],head[x]=tot++;
}
int dep[maxn],sz[maxn],son[maxn],fa[maxn];
int top[maxn],id[maxn],dfs_clock=0;
void dfs1(int u,int depth){
	dep[u]=depth,sz[u]=1,son[u]=-1;
	for(int i=head[u];i!=-1;i=edge[i].nxt){
		int v=edge[i].v;
                if(dep[v])continue;
		fa[v]=u;
		dfs1(v,depth+1);
		sz[u]+=sz[v];
		if(son[u]==-1||sz[v]>sz[son[u]])son[u]=v;
	}
}
void dfs2(int u,int tp){
	top[u]=tp,id[u]=++dfs_clock;
	if(son[u]==-1)return;
	dfs2(son[u],tp);
	for(int i=head[u];i!=-1;i=edge[i].nxt){
		int v=edge[i].v;
		if(v!=son[u]&&v!=fa)
			dfs2(v,v);
	}
}
int lca(int x,int y){//也可以swap不过这个常数小点
	if(y>n)return 0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])y=fa[top[y]];
		else x=fa[top[x]];
	}
	if(dep[x]<dep[y])return x;
	else return y;
}

链/子树上操作

struct Edge{
	int v,nxt;
}edge[maxn];
int head[maxn],tot=0;
inline void read(int &x){
	x=0;char tmp=getchar();
	while(tmp<'0'||tmp>'9')tmp=getchar();
	while(tmp>='0'&&tmp<='9')x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
inline void add_edge(int x,int y){
	edge[tot].v=y,edge[tot].nxt=head[x],head[x]=tot++;
}
int dep[maxn],sz[maxn],son[maxn],fa[maxn];
int top[maxn],id[maxn],dfs_clock=0;
void dfs1(int u,int depth){
	dep[u]=depth,sz[u]=1,son[u]=-1;
	for(int i=head[u];i!=-1;i=edge[i].nxt){
		int v=edge[i].v;
                if(dep[v])continue;
		fa[v]=u;
		dfs1(v,depth+1);
		sz[u]+=sz[v];
		if(son[u]==-1||sz[v]>sz[son[u]])son[u]=v;
	}
}
void dfs2(int u,int tp){
	top[u]=tp,id[u]=++dfs_clock;
	if(son[u]==-1)return;
	dfs2(son[u],tp);
	for(int i=head[u];i!=-1;i=edge[i].nxt){
		int v=edge[i].v;
		if(v!=son[u]&&v!=fa)
			dfs2(v,v);
	}
}
//线段树维护

treap
见树套树板子

splay(无)

树套树
1、查询k在[l,r]的排名;对treap原来的求排名函数稍作修改为求比k小的数的个数。这样线段树分段查询再合并加1,就是k在l-r的排名。
时间复杂度:(log2n)[线段树,平衡树]

2、查询在[l,r]的区间中排名为k的元素。这个操作真没有什么高深的做法,二分值大小吧,再用操作一暴力。听说隔壁“线段树/树状数组套主席树”可以只带两个log,平衡树表示无奈。
时间复杂度:(log3n)[二分,线段树,平衡树]

3、将位置为pos的元素值修改为k。这个也很简单(暴力),线段树枚举pos所在的log个区间,每次从平衡树中删去原值,插入新值。

4、查找k在[l,r]中的前驱(要求严格小于)。线段树划分区间,再用平衡树暴力求各区间的前驱,最后合并前驱。

5、查询k在[l,r]中的后继(要求严格大于)。对操作四依葫芦画瓢。

这里将treap封装成了结构体,方便线段树的调用。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
const int inf=2147483647;
const int maxn=5e4+1000;
int n,m;
int p[maxn];
struct node{
	int val,key,cnt,size;
	int ch[2];
};
node t[maxn*40];
int tot;
struct Treap{
	int root;
	void update(int x){
		t[x].size=t[ls].size+t[rs].size+t[x].cnt;
	}
	void rotate(int &x,int d){
		int son=t[x].ch[d];
		t[x].ch[d]=t[son].ch[d^1];
		t[son].ch[d^1]=x;update(x);update(x=son);
	}
	void insert(int &x,int val){
		if(!x){
			x=++tot;
			t[x].cnt=t[x].size=1;
			t[x].key=rand(),t[x].val=val;
			return ;
		}
		t[x].size++;
		if(t[x].val==val){t[x].cnt++;return;}
		int d=val>t[x].val;insert(t[x].ch[d],val);
		if(t[x].key>t[t[x].ch[d]].key) rotate(x,d);
	}
	void delet(int &x,int val){
		if(!x) return ;
		if(t[x].val==val){
			if(t[x].cnt>1){t[x].cnt--,t[x].size--;return ;}
			bool d=t[ls].key>t[rs].key;
			if(ls==0||rs==0) x=ls+rs;
			else rotate(x,d),delet(x,val);
		}
		else t[x].size--,delet(t[x].ch[t[x].val<val],val);
	}
	int rank(int x,int val){
		if(!x)return 0;
		if(t[x].val==val) return t[ls].size;
		if(t[x].val>val) return rank(ls,val);
		else return t[ls].size+t[x].cnt+rank(rs,val);
	}
	int kth(int x,int k){
		while(1){
			if(k<=t[ls].size)x=ls;
			else if(k>t[ls].size+t[x].cnt) k-=t[ls].size+t[x].cnt,x=rs;
			else return t[x].val;
		}
	}
	int pre(int x,int val){
		if(!x) return -inf;
		if(t[x].val>=val) return pre(ls,val);
		else return max(t[x].val,pre(rs,val));
	}
	int nxt(int x,int val){
		if(!x) return inf;
		if(t[x].val<=val) return nxt(rs,val);
		else return min(t[x].val,nxt(ls,val));
	}
}a[maxn<<2];
inline void read(int &x){
	x=0;char tmp=getchar();
	while(tmp<'0'||tmp>'9') tmp=getchar();
	while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
inline void read(int &x,int &y){read(x),read(y);}
inline void read(int &x,int &y,int &z){read(x),read(y),read(z);}

void build(int x,int l,int r){
	for(int i=l;i<=r;i++)
		a[x].insert(a[x].root,p[i]);
	if(l==r) return ;
	build(x<<1,l,l+r>>1);
	build(x<<1|1,(l+r>>1)+1,r);
}
int queryrank(int x,int l,int r,int ql,int qr,int num){
	if(l>qr||r<ql) return 0;
	if(ql<=l&&r<=qr)
		return a[x].rank(a[x].root,num);
	int ret=0;
	ret+=queryrank(x<<1,l,l+r>>1,ql,qr,num);
	ret+=queryrank(x<<1|1,(l+r>>1)+1,r,ql,qr,num);
	return ret;
}
int queryval(int ql,int qr,int k){
	int l=0,r=1e8,mid,ans=-1;
	while(l<=r){
		mid=l+r>>1;
		if(queryrank(1,1,n,ql,qr,mid)+1<=k)ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}
void modify(int x,int l,int r,int pos,int k){
	if(pos<l||r<pos) return ;
	a[x].delet(a[x].root,p[pos]);
	a[x].insert(a[x].root,k);
	if(l==r) return ;
	modify(x<<1,l,l+r>>1,pos,k);
	modify(x<<1|1,(l+r>>1)+1,r,pos,k);
}
int querypre(int x,int l,int r,int ql,int qr,int k){
	if(l>qr||r<ql) return -inf;
	if(ql<=l&&r<=qr) return a[x].pre(a[x].root,k);
	int ret=querypre(x<<1,l,l+r>>1,ql,qr,k);
	ret=max(ret,querypre(x<<1|1,(l+r>>1)+1,r,ql,qr,k));
	return ret;
}
int querynxt(int x,int l,int r,int ql,int qr,int k){
	if(l>qr||r<ql) return inf;
	if(ql<=l&&r<=qr) return a[x].nxt(a[x].root,k);
	int ret=querynxt(x<<1,l,l+r>>1,ql,qr,k);
	ret=min(ret,querynxt(x<<1|1,(l+r>>1)+1,r,ql,qr,k));
	return ret;
}
int main(){
	srand(19260817);
	cin>>n>>m;
	for(int i=1;i<=n;i++)read(p[i]);
	build(1,1,n);
	int op;
	for(int i=1;i<=m;i++){
		int l,r,k,pos;
		read(op);
		if(op==1){
			read(l,r,k);
			printf("%d\n",queryrank(1,1,n,l,r,k)+1);
		}
		else if(op==2){
			read(l,r,k);
			printf("%d\n",queryval(l,r,k));
		}
		else if(op==3){
			read(pos,k);
			modify(1,1,n,pos,k);p[pos]=k;
		}
		else if(op==4){
			read(l,r,k);
			printf("%d\n",querypre(1,1,n,l,r,k));
		}
		else{
			read(l,r,k);
			printf("%d\n",querynxt(1,1,n,l,r,k));
		}
	}
	return 0;
}

图论

二分图算法

二分图定义:

节点由两个集合组成,且两个集合内部没有边的图。

应用时,

先考察是否存在一种方案,将节点划分成满足以上性质的两个集合;再考虑此二分图的性质以及如何求得性质。

简单性质:

任意边连接的两点属于两个不同的集合。(由定义可得)
二分图不存在奇数环。(充分必要条件)

判定条件:

dfs遍历,判断是否存在奇环;

证明:奇环不存在<=>图为二分图
  <=假设,奇环存在
    设奇环中某一点属于集合a而不属于集合b
    由简单性质1可知,令该点绕环一周后该点属于b而不属于a
    矛盾,假设错误
    必要性得证
  =>易证,当一张图中有多个连通块时,我们仅需考虑每个连通块的情况。
    对于某一联通块任意中两点,设有两条奇偶性不同的通路连接。
    可以证明这两条通路中所有不同的边可以构成一个或n个环。(否则,这两条路的终点起点就一定不相同)
    因为奇环不存在,故环的边数和为偶数。
    则两条通路的奇偶性相同,矛盾。进而可得两点间任意通路的奇偶性均相同。
    以连通块中某一点为起点遍历整张图,标记各点到起点的距离,并以距离的奇偶性作为二分图集合的划分依据。
    由上证明结论可得,任意边的两点均属不同集合。
    二分图构造完成。
    充分性得证

由证明过程不难看出,判断二分图的方法也可以用于构造一种点集的分类方案

应用:

二分图最大匹配:
匹配:

在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。

最大匹配:

一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。通常对于一个图,它的最大匹配不唯一。

交替路:

从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。

增广路:

从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。
特别的,一条连接两个未匹配点的边也可以称为增广路。

匈牙利算法:

考察增广路,发现其有一个重要性质,首尾均为非匹配点,且非匹配边的数量比匹配边多一
这时,若我们使增广路中所有匹配边变为非匹配边,非匹配边变为匹配边,则此时匹配边的数量+1且匹配点的个数+2。

增广路定理:

一个匹配是最大匹配的充要条件是不存在增广路,这个充要条件适用于任意图。
下证明定理的充要性:

必要性:
  若一个匹配是最大匹配且存在增广路,
  则可利用增广路性质使匹配数增加,故不存在。
  命题得证
充分性:
  已知任意两个未匹配点之间不存在增广路,

再用网络流证明,(网络流大坑1/1)

于是我们只要对每个a组的点或b组的点进行一次增广路的寻找,我们就完成了最大匹配的构造。
这也就是匈牙利算法的核心,剩下的是一些代码实现上的优化,主流上有用bfs和dfs两种方式实现查找。
[dfs]
cnt表示最大匹配数,key是记录所有最大匹配的公共点个数

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000;
struct node{int x,y;};
int n,m,k;
bool g[maxn][maxn];
int a[maxn],b[maxn];
bool find(int x){
	for(int i=1;i<=n;i++){
		if(g[x][i]&&!b[i]){
			b[i]=1;
			if(!a[i]||find(a[i])){
				a[i]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main(){
//	ios::sync_with_stdio(false);
	int T=1;
	while(cin>>n>>m>>k){
		memset(g,0,sizeof(g));
		memset(a,0,sizeof(a));
		for(int i=1;i<=k;i++){
			int x,y;cin>>x>>y;
			g[x][y]=1;
		}
		int cnt=0,key=0;
		for(int i=1;i<=n;i++){
			memset(b,0,sizeof(b));
			if(find(i))cnt++;
		}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(g[i][j]){
					g[i][j]=0;
					int sum=0;
					memset(a,0,sizeof(a));
					for(int l=1;l<=n;l++){
						memset(b,0,sizeof(b));
						if(find(l))sum++;
					}
					if(sum<cnt)key++;
					g[i][j]=1;
				}
		 printf("Board %d have %d important blanks for %d chessmen.\n",T++,key,cnt);
	}
	return 0;
}

[bfs]
代码比dfs复杂,在稀疏图上大约比dfs快一倍左右,有空来补一下bfs的代码。。。

扩展定理:

最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择
 最大匹配数 = 最小点覆盖数(这是 Konig 定理)
最大独立数:选取最多的点,使任意所选两点均不相连
 最大独立数 = 顶点数-最大匹配数
最小路径覆盖数:对于一个 DAG(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径。路径长可以为 0(即单个点)。
 最小路径覆盖数 = 顶点数 - 最大匹配数

强连通分量
主流的写法有两种:Kosaraju算法和Tarjan 算法

求完强连通后可以缩点,缩点后图会变成DAG。

Tarjan算法
tarjan老博客
Kosaraju算法
该算法依靠两次简单的 DFS 实现:

第一次 DFS,选取任意顶点作为起点,遍历所有未访问过的顶点,并在回溯之前给顶点编号,也就是后序遍历。

第二次 DFS,对于反向后的图,以标号最大的顶点作为起点开始 DFS。这样遍历到的顶点集合就是一个强连通分量。对于所有未访问过的结点,选取标号最大的,重复上述过程。

两次 DFS 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为\(\Theta(N+M)\)

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int maxn=1e5+1000;
vector<int>g[maxn],g2[maxn];
vector<int>s;
int vis[maxn],clr[maxn];
int n,m,cnt,dfs_clock,tt;
inline void read(int &x){
	char tmp=getchar();x=0;
	while(tmp<'0'||tmp>'9')tmp=getchar();
	while(tmp>='0'&&tmp<='9')x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
void dfs1(int u){
	vis[u]=1;
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(!vis[v])dfs1(v);
	}
	s.push_back(u);
}
void dfs2(int u){
	clr[u]=cnt;
	tt++;
	for(int i=0;i<g2[u].size();i++){
		int v=g2[u][i];
		if(!clr[v])dfs2(v);
	}
}
signed main(){
	int T;cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++)
			g[i].clear(),g2[i].clear();
		for(int i=1;i<=m;i++){
			int x,y;read(x),read(y);
			g[x].push_back(y);
			g2[y].push_back(x);
		}
		memset(vis,0,sizeof(vis));
		memset(clr,0,sizeof(clr));
		dfs_clock=0,s.clear();
		for(int i=1;i<=n;i++)
			if(!vis[i])dfs1(i);
		cnt=0;int ans=0;
		for(int i=n-1;i>=0;i--)
			if(!clr[s[i]]){
				cnt++;
				tt=0;
				dfs2(s[i]);
				ans+=(tt-1)*tt>>1;
			}
		cout<<ans<<endl;
	}
	return 0;
}

割点和桥
预备知识
割顶:Cut Vertex,对于无向图G,如果删除某个顶点u后,连通分量数目增加,则u便是图G的关节点(Articulation Vertex)或割顶。
桥:Bridge,如果删除某条边,G就非连通了,这条边就称之为桥。

概念1 时间戳
某一点的时间戳即是该点在DFS中被访问的顺序,如果v的时间戳小于u的时间戳则v是u的祖先 (因为祖先先访问嘛)。
用全局变量dfs_clock表示“当前时刻”,定义pre[u]表示u的顺序(或称为到达u点时的时刻),伪代码如下:

memset(pre,0,sizeof(pre));
dfs_clock=0;
pre[u]=++dfs_clock;

概念2 树边反向边
树边:在dfs森林中被遍历的边;
反向边:第一次处理后,子孙指向祖先的边。可认为是除去树边外的所有边。
dfs森林
dfs的森林

概念3 割顶特性
定理1->容易看出,割顶至少要与两点相连,所以若为根节点,需要有两个及以上的孩子;若不为根节点,则需要至少一个孩子结点
用low[u]表示u及其子孙所能到达的最早祖先;
定理2->容易想到的是若有一个点,它的子孙所能到达的最早的点,大于等于自己,则此点是割顶(动手画一画,想一想是为什么)。用数组表示为

low[v]>=pre[u]

之后可以通过dfs求low[u]的值,顺道完成求割顶和桥的操作。
代码如下

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
vector<int>g[100010];
int dfs_clock;
int cnt_bridge,cnt_cut;
int low[100010],is_cut[100100];
int pre[100010],post[100010];
void pre_visit(int u)
{
    pre[u]=++dfs_clock;
}
void post_visit(int u)
{
    post[u]=++dfs_clock;
}
int dfs(int u,int fa)//u在DFS数中的父亲结点是fa
{
    int lowu=pre[u]=++dfs_clock;
    int child=0;//子结点数目
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(!pre[v])//访问过v
        {
            child++;
            int lowv=dfs(v,u);
            lowu=min(lowu,lowv);//通过后代的lowv更新lowu值
            if(lowv>=pre[u]) is_cut[u]=1;//如果v能到达的最早的祖先小于等于它的祖先u ,则u是割点
            if(low[v]>pre[u]) cnt_bridge++;//如果儿子v只能到达u,则(u,v)是一座桥,删去后连通分量+1
        }
        else if(pre[v]<pre[u]&&v!=fa)//通过反向边更新lowu的值
        {
            lowu=min(lowu,pre[v]);
        }
    }
    if(fa<0&&child==1) is_cut[u]=0;//只有一个子结点的根结点不是割顶
    low[u]=lowu;//更新low[u]的值;
    return lowu;//返回值,对应父亲结点的lowv
}
int main()
{
    int n,m;
    cin>>n>>m;
        memset(is_cut,0,sizeof(is_cut));
        memset(pre,0,sizeof(pre));
        for(int i=1;i<=n;i++)
            g[i].clear();
        cnt_bridge=0;
        cnt_cut=0,dfs_clock=0;//每次的初始化
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            g[x].push_back(y);
            g[y].push_back(x);
        }
        for(int i=1;i<=n;i++)
            if(!pre[i])
                dfs(i,-1);
        for(int i=1;i<=n;i++)
            if(is_cut[i])cnt_cut++;
        printf("%d\n",cnt_cut);
        for(int i=1;i<=n;i++)
            if(is_cut[i]) printf("%d ",i);//输出割顶
    return 0;
}

双连通分量

#include <stack>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1000;
struct Edge
{
    int u,v;
    Edge(int uu,int vv)
    {
        u = uu;
        v = vv;
    }
};//定义边的结构
stack<Edge> s;//定义一个栈来储存同一个双连通分量

struct edge
{
    int v,next;
}edges[100000];

int n,m;
int tot,head[maxn];
int pre[maxn];
int dfs_clock;
int bcc_cnt;
int belong[maxn];
vector<int> bcc[maxn];
void add(int u,int v)
{
    edges[tot].v = v;
    edges[tot].next = head[u];
    head[u] = tot++;
}

int tarjan(int u,int fa)
{
    int lowu = pre[u] = ++dfs_clock;
    int child = 0;
    for(int i=head[u];i!=-1;i=edges[i].next)
    {
        int v = edges[i].v;
        Edge e = (Edge){u,v};
        if(!pre[v])
        {
            s.push(e);//把两边压入栈中 
            child++;//记录子结点的数量
            int lowv = tarjan(v,u);//通过孩子v更新low值 
            lowu = min(lowu,lowv);
            if(lowv >= pre[u])//如果v及其子孙无法从u以外的其他地方通过,则双连通分量增加 
            {
                bcc_cnt++;//双连通分量++ 
                bcc[bcc_cnt].clear();//数组清零 
                for(;;) //效果等同于while(1) 
                {
                    Edge x = s.top(); s.pop();//将栈中的结点逐一弹出,并将它们划分为同一双连通分量 
                    if(belong[x.u] != bcc_cnt) {bcc[bcc_cnt].push_back(x.u); belong[x.u] = bcc_cnt;}
                    if(belong[x.v] != bcc_cnt) {bcc[bcc_cnt].push_back(x.v); belong[x.v] = bcc_cnt;}
                    if(x.u == u && x.v == v) break;
                }
            }
        }
        else if(pre[v] < pre[u] && v != fa)//如果子结点的先被访问,则产生一条反向边 
        {
            s.push(e);//把两边压入栈中 
            lowu = min(lowu,pre[v]);
        }
    } 
    return lowu;
}

int cmp(vector<int> x,vector<int>y)
{
    int k=0;
    while(x[k]==y[k])k++;
    return x[k]<y[k];
}//开启了vector二维排序的新模式 
void init()
{
    memset(pre,0,sizeof(pre));
    memset(head,-1,sizeof(head));
    memset(belong,0,sizeof(belong));
    tot = 0; dfs_clock = 0; bcc_cnt = 0;
}//神奇的初始化 
int main()
{
    int u,v;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0) break;
        init();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
            add(v,u);//边表的加边处理 
        }
        for(int i=1;i<=n;i++)
            if(!pre[i])tarjan(i,-1);
        cout<<bcc_cnt<<endl;
        for(int i=1;i<=bcc_cnt;i++)
            sort(bcc[i].begin(),bcc[i].end());//把每个集合的内部进行排序 
        sort(bcc+1,bcc+bcc_cnt+1,cmp);//把所有集合按字典序排序 
        for(int i=1;i<=bcc_cnt;i++)//输出次序 
        {
            printf("%d",bcc[i][0]);
            for(int j=1;j<bcc[i].size();j++)
                printf(" %d",bcc[i][j]);
            printf("\n");
        }
        cout<<endl;
    }
    return 0;
}

杂项

STL基本

string

插入insert

s.insert(1,"sdfsdf");
s.insert(1,s2);
s.insert(5,3,"ds");

求子串substr

s2=s.substr(2,4);
s2=s.substr(2);

删除字串erase

s.erase(2,4);
s.erase(5);//删除5及以后的字符

查找字符串find

pos=s.find("key");//s中第一次出现匹配子串的位置
pos2=s.find("key",10);//s中从第10位开始第一次出现匹配子串的位置
//匹配结果为空时,返回尾地址后一个地址

排序sort

sort(s.begin(),s.end());

下一个全排列

next_permutation(s.begin(),s.end());

字符数组匹配strcmp

strcmp(c,"hello world!");//相同返回0,不同返回1;

迭代器iterator

循环

for(iter=s.begin();iter!=s.end();iter++);

map

map按第一关键字排序。
查找,计数(只会返回0或1),删除只能依据第一关键字。
用insert插入时,插入的是一个pair

二分查找

val是否出现在闭区间中,返回0或1

binary_search(first,last,val)

有序数列中第一个“大于等于”val的元素的位置(返回值为地址)

lower_bound(first,last,val);

有序数列中第一个“大于”val的元素的位置(返回值为地址)

upper_bound(first,last,val);

卡常

swap

a^=b^=a^=b;//without function
<=
inline void swap(int &x,int &b){
  a^=b^=a^=b;
}
<=
swap(a,b);//stl

究极快读

namespace fast_IO {//without negative number
    char getchar() {
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    int read() {
        int num = 0;
        char c;
        while ((c = getchar()) != '-' && c != '+' && (c < '0' || c > '9') && ~c);
        num = c ^ 48;
        while ((c = getchar()) >= '0' && c <= '9')
            num = (num << 1) + (num << 3) + (c ^ 48);
        return num;
    }
}
namespace fast_IO {
    char buf[N], *s, *t;
    char getchar() {
        return (s == t) && (t = (s = buf) + fread(buf, 1, N, stdin)), s == t ? -1 : *s++;
    }
    int read() {
        int num = 0;
        char c;
        bool tag = false;
        while ((c = getchar()) != '-' && c != '+' && (c < '0' || c > '9') && ~c);
        if (c == '-')tag = true;
        else num = c ^ 48;
        while ((c = getchar()) >= '0' && c <= '9')
            num = (num << 1) + (num << 3) + (c ^ 48);
        if (tag)return -num;
        return num;
    }
}

GCD

inline int gcd(int a,int b){if(!b)return a;while(b^=a^=b^=a%=b);return a;

氧气加速

#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
posted @ 2022-05-21 11:30  xyc1719  阅读(31)  评论(0)    收藏  举报