ACM模板

目录

  • 素数
    • 素数筛
    • 求1e18以内数因子分解的最小幂次
  • 数论
    • 中国剩余定理
    • 二次剩余定理
  • 图论
    • tarjan
      • 缩点
      • 求割点
  • dp
    • 区间dp
      • 单次合并多堆
  • 数据结构
    • 线段树
      • 单点修改
      • 区间修改
    • 动态开点线段树
    • 主席树
      • 求区间第k大
  • 奇怪的定理
    • n数码
  • 其他
    • 逆元
    • 快读
    • 离散化
    • 随机数
    • 求数字k在0-n里出现的次数
    • 归并求逆序数

素数

素数筛

int prime[maxn+10];
void getprime(){
     
    memset(prime,0,sizeof(prime));
    for(int i=2;i<=maxn;i++){
     
        if(!prime[i])prime[++prime[0]]=i;
        for(int j=1;j<=prime[0]&&prime[j]<=maxn/i;j++){
     
            prime[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}

求1e18以内数因子分解的最小幂次

复杂度大约是 1 e 4 ∗ l o g ( n ) 1e4*log(n) 1e4logn

const int maxn = 1e4;

int prime[maxn+10];
void getprime(){
     				//预处理素数筛
    memset(prime,0,sizeof(prime));
    for(int i=2;i<=maxn;i++){
     
        if(!prime[i])prime[++prime[0]]=i;
        for(int j=1;j<=prime[0]&&prime[j]<=maxn/i;j++){
     
            prime[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}
int find(ll n)          //求是否是某个数的三次方
{
     
    int l=maxn;
    int r=maxn*100;     //x在1e18以内所以最多是1e6的三次方
    while(l<=r)
    {
     
        int mid=l+r>>1;
        ll t=1ll*mid*mid*mid;
        if(t<n)
            l=mid+1;
        if(t>n)
            r=mid-1;
        if(t==n)
            return 1;
    }
    return 0;

}
int slove(ll n)
{
     
    int ans=64;
    for(int i=1;i<=prime[0]&&prime[i]<=n;i++)
    {
     
        if(n%prime[i]==0)
        {
     
            int t=0;
            while(n%prime[i]==0)
            {
     
                n/=prime[i];
                t++;
            }
            ans=min(ans,t);
        }
    }
    int u1=sqrt(n);
    int u2=sqrt(u1);
    if(n==1)
    {
     
        return ans;
    }
    else if(1ll*u2*u2*u2*u2==n)
    {
     
        ans=min(ans,4);
        return ans;
    }
    else if(find(n))
    {
     
        ans=min(ans,3);
        return ans;
    }
    else if(1ll*u1*u1==n)
    {
     
        ans=min(ans,2);
        return ans;
    }
    else
    {
     
        return 1;
    }
}

数论

中国剩余定理

ll mod[maxn], y[maxn];
void ex_gcd(ll a, ll b, ll &d, ll &x, ll &y)    //d是gcd(a,b)
{
     
    if(!b){
     
        d=a,x=1,y=0;
    }else{
     
        ex_gcd(b,a%b,d,y,x);
        y-=x*(a/b);
    }
}

ll China(int len,ll *a,ll *r){
     						//a*为模数 
    ll M=a[1],R=r[1],x,y,d;
    for(int i=2;i<=len;i++){
     
        ex_gcd(M,a[i],d,x,y);
        if((R-r[i])%d!=0) return -1;        //无解
        x=(R-r[i])/d*x%a[i];
        R-=x*M;
        M=M/d*a[i];
        R%=M;
    }
    return (R%M+M)%M;                       //最小正整数解
}

二次剩余定理

求解 x 2 ≡ n ( m o d p ) x^2\equiv n(mod p) x2n(modp),复杂度只有求快速幂的log。
当且仅当模数为奇素数时可用

ll mod = 1e9 + 7;
ll w, n;
struct Complex			//复数
{
     
    ll x, y;
    Complex(ll _x = 0, ll _y = 0)
    {
     
        x = _x;
        y = _y;
    }
};
Complex operator*(Complex a, Complex b)	//复数乘
{
     
    return Complex((a.x * b.x % mod + a.y * b.y % mod * w % mod) % mod, (a.x * b.y + a.y * b.x) % mod);
}
ll qpow(ll x, ll y)
{
     
    ll ans = 1;
    while (y)
    {
     
        if (y & 1)
            ans = ans * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return ans;
}

Complex qpow(Complex a, ll y)
{
     
    Complex ans = Complex(1, 0);
    while (y)
    {
     
        if (y & 1)
            ans = ans * a;
        a = a * a;
        y >>= 1;
    }
    return ans;
}
ll Legendre(ll x,ll mod)
{
     
    return qpow(x, (mod - 1) >> 1);
}

int slove(ll n,ll mod)			//返回值为其中一个解,另一个解为mod-返回值
{
     
    if(n==0)
        return 0;
    if(n==1)
        return 1;
    if(Legendre(n,mod)+1==mod)
        return -1;						//非二次剩余,在mod下无法开方
    ll a, temp;
    while(1)
    {
     
        a = rand() % mod;
        temp=a*a-n;
        w=(temp%mod+mod)%mod;
        if(Legendre(w,mod)+1==mod) break;
    }
    Complex te;
    te.x=a;
    te.y=1;
    Complex ans=qpow(te,(mod+1)>>1);
    return ans.x;
}

图论

tarjan

缩点

const int MAXN=100005;
int n,m;
vector<int> g[MAXN];
int dfn[MAXN],low[MAXN],cnt=0,index=0;
int scc[MAXN],size[MAXN];
bool inStack[MAXN];
stack<int> st;
void tarjan(int u)
{
     
    index++;
    dfn[u]=index;
    low[u]=index;
    st.push(u);
    inStack[u]=true;
    for(int i=0;i<g[u].size();i++)
    {
     
        if(!dfn[g[u][i]])
        {
     
            tarjan(g[u][i]);
            low[u]=min(low[u],low[g[u][i]]);
        }
        else if(inStack[g[u][i]])
            low[u]=min(low[u],dfn[[u][i]]);
    }
    if(low[u]==dfn[u])
    {
     
        cnt++;
        int t;
        do
        {
     
            t=st.top();
            scc[t]=cnt;					//缩点后的新编号
            size[cnt]++;				//缩点后点集数量
            inStack[t]=false;
            st.pop();
        }while(t!=u);
    }
}

缩点后重新构图

//g是原图,G是缩点后的新图
//have_edge 防止重边
int have_edge[maxn][maxn];
for(int i=1;i<=n;i++)
{
     
    for(int j=0;j<g[i].size();j++)
    {
     
        if(scc[i]!=scc[g[i][j]] && !have_edge[scc[i]][scc[g[i][j]]])
        {
     
            have_edge[scc[i]][scc[g[i][j]]]=1;
            G[scc[i]].push_back(scc[g[i][j]]);
        }
    }
}

求割点

void tarjan(int x){
     
	dfn[x]=low[x]=cnt++;
	int son=0;
	for(int i=0;i<g[x].size();i++){
     
		int v=g[x][i];
		if(!dfn[v]){
     
			son++;
			tarjan(v);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x] && dfn[x]!=1) ans.insert(x);
			else if(x==1 && son>1){
     
				ans.insert(x);
			}
		}
		else{
     
			low[x]=min(low[x],dfn[v]);

		}
	}

}

dp

区间dp

单次合并多堆

每次只能合并L-R堆。复杂度 O ( n 4 ) O(n^4) O(n4)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f3f
#define FIN freopen("input.txt","r",stdin);
#define mem(x,y) memset(x,y,sizeof(x));
int sum[205];

int dp[105][105][105];
int n,l,r;
void init()
{
     
    sum[0]=0;
    memset(dp,INF,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
     
        for(int j=i;j<=n;j++)
        {
     
            dp[i][j][j-i+1]=0;
        }
    }
}
int main() {
     
    while(cin>>n>>l>>r)
    {
     
        init();
        for(int i=1;i<=n;i++)
        {
     
            scanf("%d",&sum[i]);
            sum[i]=sum[i]+sum[i-1];
        }
        for(int len=2;len<=n;len++)
        {
     
            for(int i=1;i<=n;i++)
            {
     
                int j=i+len-1;
                if(j>n)
                    break;
                for(int k=len;k>=1;k--)
                {
     
                    if(k==1)
                    {
     
                        for(int s=l;s<=r;s++)
                        {
     
                            dp[i][j][k]=min(dp[i][j][k],dp[i][j][s]+sum[j]-sum[i-1]);
                        }
                        
                    }
                    else
                    {
     
                        for (int x=i;x<j;x++)
                        {
     
                            dp[i][j][k]=min(dp[i][j][k],dp[i][x][k]+dp[x+1][j][k-1]);
                        }
                    }
                }
            }
        }
        if(dp[1][n][1]==INF)
        {
     
            printf("0\n");
        }
        else 
            printf("%d\n",dp[1][n][1]);
    }
}

数据结构

线段树

单点修改

#define lson node<<1
#define rson node<<1|1
ll tree[4*maxn]; // 线段树
ll a[maxn];
void init(){
     
	memset(tree,0,sizeof(tree));
}

// 创建线段树
void build(int node,int l,int r){
     
	if(l == r){
     
        tree[node]=a[l];
		return;
	}
	int mid = (l+r)/2;
	build(lson,l,mid);
	build(rson,mid+1,r);
	tree[node] = tree[lson] + tree[rson];
}

// 单点更新,add为更新值,index为更新点
void update(int node,int l,int r,int index,int add){
     
	if(l == r) {
     
		tree[node] = add; // 更新方式,可以自由改动
		return;
	}
	int mid = (l+r) / 2;
	if(index <= mid){
     
		update(lson,l,mid,index,add);
	}else{
     
		update(rson,mid+1,r,index,add);
	}
	tree[node] = tree[lson] + tree[rson];
}
// 区间查找
ll query(int node,int l,int r,int L,int R){
     
	if(l >= L && r <= R) return tree[node];
	int mid = (l+r) / 2;
	ll sum = 0;
	if(mid >= L) sum += query(lson,l,mid,L,R);
	if(mid < R) sum += query(rson,mid+1,r,L,R);
	return sum;
}

区间修改


#define lson node<<1
#define rson node<<1|1
ll tree[4*maxn]; // 线段树
ll lz[4*maxn]; // 延迟标记
ll a[maxn];
void init(){
     
	memset(tree,0,sizeof(tree));
	memset(lz,0,sizeof(lz));
}

// 创建线段树
void build(int node,int l,int r){
     
	if(l == r){
     
        tree[node]=a[l];
		return;
	}
	int mid = (l+r)/2;
	build(lson,l,mid);
	build(rson,mid+1,r);
	tree[node] = tree[lson] + tree[rson];
}

void push_down(int node,int l,int r){
     
	if(lz[node]){
     					//这里根据实际操作进行修改
		int mid = (l+r) / 2;
		lz[lson] += lz[node];
		lz[rson] += lz[node];
		tree[lson] += 1LL*(mid - l + 1)*lz[node];       
		tree[rson] += 1LL*(r - mid)*lz[node];
		lz[node] = 0;
	}
}

// 区间更新,lr为线段树范围,LR为更新范围,add为更新值
void update(int node,int l,int r,int L,int R,int add){
     
	if(l >= L && r <= R){
     
		lz[node] += 1LL*add;
		tree[node] += 1LL*(r - l + 1)*add; // 更新方式
		return;
	}
	push_down(node,l,r);
	int mid = (l+r) / 2;
	if(mid >= L) update(lson,l,mid,L,R,add);
	if(mid < R) update(rson,mid+1,r,L,R,add);
	tree[node] = tree[lson] + tree[rson];
}

// 区间查找
ll query(int node,int l,int r,int L,int R){
     
	if(l >= L && r <= R) return tree[node];
	push_down(node,l,r);                        
	int mid = (l+r) / 2;
	ll sum = 0;
	if(mid >= L) sum += query(lson,l,mid,L,R);
	if(mid < R) sum += query(rson,mid+1,r,L,R);
	return sum;
}

动态开点线段树


#define mid  (l+r)/2
#define maxn 1000007 //元素个数

ll n,m;
ll rt=1,cnt=1;
ll lson[maxn*40],rson[maxn*40];
ll Lazy[maxn*40];//区间增加的lazy标记
ll sum[maxn*40];//线段树求和最多分成4个子区间
void init()
{
     
    for(int i=0;i<=cnt;i++)
    {
     
        sum[i]=0;
        Lazy[i]=0;
    }
    rt=cnt=1;
}
ll Get_Son(long long &pos)
{
     
    if(pos==0) pos=++cnt;
    return pos;
}

void PushUp(long long pos)
{
     
    sum[pos]=sum[lson[pos]]+sum[rson[pos]];
}

void PushDown(long long pos,long long l,long long r)//区间查询用
{
     
    //l,r为左子树,右子树的数字区间

    // if(Lazy[pos]==0) return;
    // if(r-l<=1) return;
    // if(pos<<1!=0)
    // {
     
    //     pos<<1=++cnt;
    //     sum[pos<<1]+=(mid-l+1)*Lazy[pos];
    //     Lazy[pos<<1]+=Lazy[pos];
    // }
    // if(rson[pos]!=0)
    // {
     
    //     rson[pos]=++cnt;
    //     sum[rson[pos]]+=(r-mid+1)*Lazy[pos];
    //     Lazy[rson[pos]]+=Lazy[pos];
    // }
    sum[Get_Son(lson[pos])]+=(mid-l+1)*Lazy[pos];
    sum[Get_Son(rson[pos])]+=(r-mid)*Lazy[pos];
    Lazy[lson[pos]]+=Lazy[pos];
    Lazy[rson[pos]]+=Lazy[pos];
    Lazy[pos]=0;
}

void Update(long long &pos,long long l,long long r,long long L,long long R,long long C)
{
     
    //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号
    if(pos==0) pos=++cnt;
    if(Lazy[pos]!=0) PushDown(pos,l,r);//下推标记
    
    if(L<=l && R>=r)//节点区间在操作区间之内,直接返回
    {
     
        sum[pos]+=(r-l+1)*C;//这个点需要加上区间长度*C
        Lazy[pos]+=C;//用Lazy标记,表示本区间的Sum正确,子区间的Sum仍需要根据Lazy调整
        return;
    }

    if(L<=mid) Update(lson[pos],l,mid,L,R,C);
    if(R>mid) Update(rson[pos],mid+1,r,L,R,C);
    PushUp(pos);
}

ll Query(long long pos,long long l,long long r,long long ql,long long qr)
{
     
    //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号
    if(pos==0) return 0;
    if(Lazy[pos]) PushDown(pos,l,r);//下推标记,否则sum可能不正确

    if(ql<=l && qr>=r)//节点区间在操作区间之内,直接返回
    {
     
        return sum[pos];
    }
    
    //统计答案
    long long ans=0;
    if(ql<=mid) ans+=Query(lson[pos],l,mid,ql,qr);
    if(qr>mid) ans+=Query(rson[pos],mid+1,r,ql,qr);
    PushUp(pos);
    return ans;
}

主席树

求区间第k大

#include
using namespace std;
typedef long long ll;

const int maxn = 2e5 + 20;
int lson[maxn*30],rson[maxn*30],tree[maxn*30];
int a[maxn],ha[maxn],T[maxn];
int n,m,len,tot;

void init_hash()
{
     
    sort(ha + 1, ha + 1 + n);
    len = unique(ha + 1, ha + 1 + n) - ha - 1;
    for (int i = 1; i <= n;i++)
    {
     
        a[i] = lower_bound(ha + 1, ha + 1 + len,a[i]) - ha;
    }
}
void init()
{
     
    tot=0;
    init_hash();
}

int build(int l,int r)
{
     
    int rt = tot++;
    tree[rt] = 0;
    if (l!=r)
    {
     
        int mid = (l + r) >> 1;
        lson[rt] = build(l, mid);
        rson[rt] = build(mid + 1, r);
    }
    return rt;
}

int update(int rt,int pos,int val)
{
     
    int newroot = tot++;
    int temp = newroot;
    tree[newroot] = tree[rt] + val;        //更新区间和
    int l = 1, r = len;
    while (l<r)                                 //这里迭代更新比递归快一点
    {
     
        int mid = (l + r) >> 1;
        if (pos<=mid)
        {
          
            lson[newroot] = tot++;
            rson[newroot] = rson[rt];
            newroot = lson[newroot];
            rt = lson[rt];
            r = mid;
        }
        else
        {
     
            rson[newroot] = tot++;
            lson[newroot] = lson[rt];
            newroot = rson[newroot];
            rt = rson[rt];
            l = mid + 1;
        }
        tree[newroot] = tree[rt] + val;
    }
    return temp;
}

int query(int st,int end,int k)
{
     
    int l = 1, r = len;
    while(l<r){
     
        int mid = (l + r) >> 1;
        if (tree[lson[end]]-tree[lson[st]]>=k)
        {
     
            r = mid;
            st = lson[st];
            end = lson[end]; 
        }
        else
        {
     
            l = mid + 1;
            k -= tree[lson[end]] - tree[lson[st]];
            st = rson[st];
            end = rson[end];
        }
    }
    return l;
}

int main(){
     
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
     
        scanf("%d", &a[i]);
        ha[i] = a[i];
    }
    init();
    T[0] = build(1, len);
    for (int i = 1; i <= n;i++)
    {
     
        T[i] = update(T[i - 1], a[i], 1);
    }
    for (int i = 1; i <= m;i++)
    {
     
        int l, r,k;
        scanf("%d%d%d", &l, &r, &k);
        int ans = ha[query(T[l - 1], T[r], k)];
        printf("%d\n", ans);
    }
}

奇怪的定理

n数码

n*m的数码问题有一个结论:
1.假如m是奇数,那么上下交换会改变(m-1)也就是偶数次逆序数,左右交换(空格和左右交换)不改变逆序数,因此逆序数的奇偶性不变,此时两状态的奇偶性一致即有解

2.假如m是偶数,上下交换会改变奇数次逆序数,左右交换不改变逆序数,因此逆序数奇偶性会因上下交换而改变,此时逆序数之差和两个局面下空格所在行数之差
的奇偶性相同即有解。

但是,这里所指的逆序数是不计算0的,或者是把0变为16再计算!!!!
也就是除空格外,剩下数的逆序数之差才是我们要求的东西

其他

逆元

ll inverse(ll a)
{
     
  return a == 1 ? 1 : 1LL * (mod - mod / a) * inverse(mod % a) % mod;
}

快读

inline bool read(T &ret) {
     
    char c; int sgn;
    if(c=getchar(),c==EOF) return 0; //EOF
    while(c!=' '&&(c<'0'||c>'9')) c=getchar();
    sgn=(c==' ')? 1:1;
    ret=(c==' ')?0:(c-'0');
    while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
    ret*=sgn;
    return 1;
}

离散化

随机数

mt19937_64 gen(time(0));
int main()
{
     
	srand(time(0));
	int a=gen()%10;			//貌似c++14才能用
	
}

求数字k在0-n里出现的次数

int digitCounts(int k, int n) {
     
        // write your code here
        int count = 0 , x;
        if (k == 0 && n == 0) count = 1;
        for (int i = 1;x = n / i;i *= 10) {
     
            int high = x / 10;
            if (k == 0) {
     
                if (high) high--;
                else {
     
                    count++;
                    break;
                }
            }
            count += high * i;
            int current = x % 10;
            if (current > k) count += i;
            else if (current == k) count += n - x * i + 1;
        }
        return count;
    }

归并求逆序数

复杂度大约是 O ( n l o g n ) O(nlogn) O(nlogn)
使用方式就是,把数存进a数组,然后调用mergesort( l, r),sum就是所求值

int a[maxn],temp[maxn];
long long sum=0;
void merge(int l,int r,int m){
     
    int i = l, j = m + 1,k = l;
    while(i<=m&&j<=r){
     
        if(a[i] > a[j]){
     
            sum += m - i + 1;
            temp[k++] = a[j++];
        }else{
     
            temp[k++] = a[i++];
        }
    }
    while(i<=m)
        temp[k++] = a[i++];
    while(j<=r)
        temp[k++] = a[j++];
    for(i = l;i<=r;i++)
        a[i] = temp[i];
}
void mergesort(int l,int r){
     
    if(l<r){
     
        int m = (l + r) / 2;
        mergesort(l,m);
        mergesort(m+1,r);
        merge(l,r,m);
    }
}

你可能感兴趣的:(ACM模板)