笔记

康托展开和逆康托展开

康托展开和逆康托展开(转) - Sky丨Star - 博客园 (cnblogs.com)

康托展开表示的就是是当前排列组合在n个不同元素的全排列中的名次

逆康托展开则是由名次得出该名次的排列组合

公式:康托展开值X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!

X表示该排列组合前面有X个排列组合,所以该排列组合是第X+1

a[i]表示当前未用到的元素中该元素排第几个

//阶乘
static const int Fac[]={1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
//康托展开
int cantor(int *a,int n){
    int x=0;
    for(int i=0;i<n;++i){
        int cnt=0;
        for(int j=i+1;j<n;++j)
            if(a[j]<a[i])cnt++;
        x+=Fac[n-i-1]*cnt;
    }
    return x;
}
//逆康托展开
void decantor(int x,int n){//x为康托展开值,n为元素个数
    vector<int>v;//存放当前可选数
    vector<int>a;//所求排列组合
    for(int i=1;i<=n;++i)v.push_back(i);
    for(int i=n;i>=1;--i){
        int l=x%Fac[i-1];
        int r=x/Fac[i-1];
        x=l;
        a.push_back(v[r]);
        v.erase(v.begin()+r);
    }
}

 


__int128的使用方法

__int128 就是占用128字节的整数存储类型,范围是 -2^127~2^127-1

_int128只能实现四则运算,不能用cin,cout,scanf,printf输入输出,用快读和快写的函数

__int128 read()
{
    //直接在函数里面实现读字符串操作更简洁
    __int128 res=0;//初始结果赋值0
    char scan[1005];
    scanf("%s",scan);
    for(int i=0;i<strlen(scan);i++)
        res*=10,res+=scan[i]-'0';//实现进位
    return res;//返回__int128类型
}

void print(__int128 num)
{//递归调用,实现从高位向低位输出
    if(num>9) 
        print(num/10);
    putchar(num%10+'0');
}

 


Lucas

int ksm(int x,int y){
    int res=1;
    while(y){
        if(y&1)res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
int c(int a,int b){
    if(b>a)return 0;
    int res=1;
    for(int i=1,j=a;i<=b;++i,--j){
        res=res*j%mod;
        res=res*ksm(i,mod-2)%mod;
    }
    return res;
}
int Lucas(int a,int b){
    if(a<mod&&b<mod)return c(a,b);
    return c(a%mod,b%mod)*Lucas(a/mod,b/mod)%mod;
}

 

阶乘逆元求组合数

 

int fact[N],infact[N];
int ksm(int a,int k,int p){
    int res=1;
    while(k){
        if(k&1)res=res*a%p;
        k>>=1;
        a=a*a%p;
    }
    return res;
}
void init(){
    fact[0] = infact[0] = 1;
    for (int i = 1 ; i < N; ++i) {
        fact[i] = fact[i - 1] * i % mod;
        infact[i] = infact[i - 1] * ksm(i, mod - 2, mod) % mod;
    }
    //C(a,b)=fact[a]*infact[a-b]%mod*infact[b]%mod;
}

 


struct E{
    int x,y;
    E(){}
    E(int _x,int _y){x=_x,y=_y;}
    E operator-(E b){return E(x-b.x,y-b.y);}
    double len(){return ::hypot(x,y);}//sqrt(x*x+y*y);
};

 


博弈论

Nim游戏

  • 有n堆石子,两人轮流取石子,取不到的败

结论:每堆石子数异或和为非0则先手胜,否则先手败

  • 有n个台阶,每个台阶有若干石子,两人轮流扔石子,石子被扔到下一台阶(到地面为止),无法扔的败

结论:所有第奇数个台阶的异或和为非0则先手胜,否则先手败

  • 有n堆石子和一个集合S,两人轮流取石子,只能取si个石子,取不到的败

结论:所有堆数的SG异或和为非0则先手胜,否则先手败

//f初始为-1,sg(0)=0
vector<int>s,f(N);
int sg(int x){
    if(f[x]!=-1)return f[x];
    unordered_set<int>se;
    for(int i=0;i<k;++i){
        if(s[i]<=x)se.insert(sg(x-s[i]));
    }
    for(int i=0;;++i)if(!se.count(i))return f[x]=i;
}

有n堆石子,两人轮流取一堆石子,换取两堆石子数都不超过被取堆的石子数的石子,不能取的败

结论:所有堆数的SG异或和为非0则先手胜利,否则先手败

//f初始为-1
vector<int>f(105);
int sg(int x){
    if(f[x]!=-1)return f[x];
    unordered_set<int>se;
    for(int i=0;i<x;++i){
        for(int j=0;j<=i;++j)
        se.insert(sg(i)^sg(j));
    }
    for(int i=0;;++i)if(!se.count(i))return f[x]=i;
}

 


欧拉函数

int cnt,primes[N],phi[N];
bool st[N];
int n;
void get_phi(int n){
    phi[1]=1;
    for(int i=2;i<=n;++i){
        if(!st[i])phi[i]=i-1,primes[cnt++]=i;
        for(int j=0;primes[j]*i<=n;++j){
            int t=i*primes[j];
            st[t]=true;
            if(i%primes[j]==0){
                phi[t]=phi[i]*primes[j];
                break;
            }
            else phi[t]=phi[i]*(primes[j]-1);
        }
    }
}

 欧拉反演

 


线性基

详细→线性基详解-CSDN博客

 构造线性基

//插入x
void add(ll x)
{
    for(int i=60;i>=0;i--)
    {
        if(x&(1ll<<i))//注意,如果i大于31,前面的1的后面一定要加ll
        {
            if(d[i])x^=d[i];
            else
            {
                d[i]=x;
                break;//插入成功就退出
            }
        }
    }
}

 

如何求在一个序列中,取若干个数,使得它们的异或和最大

ll ans()
{
    ll anss=0;
    for(int i=60;i>=0;i--)//记得从线性基的最高位开始
    if((anss^d[i])>anss)anss^=d[i];
    return anss;
 }   

用线性基内的元素能异或出的最小值

 从一个序列中取任意个元素进行异或,求能异或出的所有数字中第k小的那个

void work()//处理线性基
{
    for(int i=1;i<=60;i++)
    for(int j=1;j<=i;j++)
    if(d[i]&(1ll<<(j-1)))d[i]^=d[j-1];
}
ll k_th(ll k)
{
    if(k==1&&tot<n)return 0;//特判一下,假如k=1,并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
    if(tot<n)k--;//类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
    work();
    ll ans=0;
    for(int i=0;i<=60;i++)
    if(d[i]!=0)
    {
        if(k%2==1)ans^=d[i];
        k/=2;
    }
}

 


8种球盒问题

https://zhuanlan.zhihu.com/p/429815465?utm_id=0

(0,1,0)

板子
int fact[N],infact[N];
int ksm(int a,int k,int p){
    int res=1;
    while(k){
        if(k&1)res=res*a%p;
        k>>=1;
        a=a*a%p;
    }
    return res;
}
void init(){
    fact[0] = infact[0] = 1;
    for (int i = 1 ; i < N; ++i) {
        fact[i] = fact[i - 1] * i % mod;
        infact[i] = infact[i - 1] * ksm(i, mod - 2, mod) % mod;
    }
    //C(a,b)=fact[a]*infact[a-b]%mod*infact[b]%mod;
}
int C(int a,int b){
    return fact[a]*infact[a-b]%mod*infact[b]%mod;
}
void solve() {
    int n,m;
    cin>>n>>m;
    if(m>n){
        cout<<0;
        return ;
    }
    init();
    int ans=0;
    for(int i=0;i<=m;++i){
        if(i%2){
            ans=(ans-C(m,i)*ksm(m-i,n,mod)%mod+mod)%mod;
        }else{
            ans=(ans+C(m,i)*ksm(m-i,n,mod)%mod)%mod;
        }
    }
    cout<<ans*infact[m]%mod;
}

 


扩展欧几里得

ax+by=c的求解

板子
int exgcd(int a, int b, int &x, int &y) {
    if(!b) {
        x = 1, y = 0;
        return a;
    }
    int ans = exgcd(b, a%b, y, x);
    y -= a / b * x;
    return ans;
}

int P(int a, int b, int c) {///最小正整数解
    int x, y, z;
    z = exgcd(a, b, x, y);
    if(c%z)
        return -1;//不成立
//    return x;//不需要最小正整数的话直接返回x
    x *= c / z;
    b = abs(b / z);
    return (x%b + b) % b;
}

 


匈牙利二分图最大匹配

vector<int> st(n + 1), to(n + 1);
    auto dfs = [&](int u, auto dfs)->bool {
        for (auto v:ve[u]) {
            if (st[v]) continue;
            st[v] = 1;
            if (!to[v] || dfs(to[v], dfs)) {
                to[v] = u;
                return true;
            }
        }
        return false;
    };
    for (int i = 1; i <= n; ++i) {
        std::fill(st.begin(), st.end(), 0);
        if (dfs(i, dfs)) ans ++;
    }

tarjan求割边割点

无向图求割边

int dfn[N], low[N], ct;
vector<vector<int> > ve;

void tarjan(int u)
{
    low[u] = dfn[u] = ++ct;
    for (auto v : ve[u])
    {
        if (!dfn[v])
        {
            fa[v] = u;
            tarjan(v);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u]) {
                add_ge_bian(u, v);
                //求割边
            }

        }
        else if (v != fa[u])
            low[u] = min(low[u], dfn[v]);
    }
}
void work() {
    //为连通图
    tarjan(1);
}

无向图求割点

int dfn[N], low[N], ct;
vector<vector<int> > ve;

void tarjan(int u, bool isroot)
{
    int tot = 0;
    low[u] = dfn[u] = ++ct;
    for (auto v : ve[u])
    {
        if (!dfn[v])
        {
            tarjan(v, 0);
            low[u] = min(low[u], low[v]);
            tot += low[v] >= dfn[u];
        }
        else
            low[u] = min(low[u], dfn[v]);
    }
    if ((isroot && tot >= 2) || (!isroot && tot >= 1)) {
        //割点
        add_ge_dian(u);
    }
}

void work() {
    //为连通图
    tarjan(1, 1);
}

边双连通分量

定义:不存在割边极大双连通子图

性质:边双连通分量中任意一条边包含在至少一个简单环

vector<int> dfn(n + 5), low(n + 5), vis(n + 5), bel(n + 5);
    // bel表示每个点属于的边双连通分量,bel[u] = vel[v]表示u和v在一个边双中
    int bccnt = 0, idx = 0;
    stack<int> st;
    auto dfs = [&](int u, int fa, auto dfs) -> void {
        dfn[u] = low[u] = ++idx;
        st.push(u);
        vis[u] = 1;
        for (auto v: ve[u]) {
            if (v == fa) continue;
            if (!vis[v]) {
                dfs(v, u, dfs);
                low[u] = min(low[u], low[v]);
            } else low[u] = min(low[u], dfn[v]);
        }
        if (dfn[u] == low[u]) {
            int p;
            ++ bccnt;
            do {
                p = st.top();
                st.pop();
                bel[p] = bccnt;
            } while (p != u);
        }
    };
    for (int i = 1; i <= n; ++i) {
        if (!vis[i]) {
            dfs(i, -1, dfs);
        }
    }

点双连通分量

定义:不存在割点的极大双连通子图

性质:点双连通分量中任意两点同时包含在至少一个简单环

 

vector<vector<int> > ve(N), ans(N); //ans中存放每个点双,有bcc个
int dfn[N], low[N], bcc, top, idx, n;
int s[N];   //栈

inline void tarjan(int u, int fa) {
    int son = 0;
    low[u] = dfn[u] = ++idx;
    s[++top] = u;
    for (auto v: ve[u]) {
        if(!dfn[v]) {
            son++;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u]) {
                bcc++;
                while(s[top + 1] != v) ans[bcc].push_back(s[top--]);//将子树出栈
                ans[bcc].push_back(u);//把割点/树根也丢到点双里
            }
        } else if(v != fa) low[u] = min(low[u], dfn[v]);
    }
    if(fa == 0 && son == 0) ans[++bcc].push_back(u);//特判独立点
}

void work() {
    for (int i = 1; i <= n; ++i) {
        if (dfn[i]) continue;
        top = 0;
        tarjan(i, 0);
    }
}

KMP

//找出第一个匹配下标  
int kmp(string text, string pattern) {
        int n = text.size(), m = pattern.size();
        if (m == 0) {
            return 0;
        }
        vector<int> next(m);
        for (int i = 1, j = 0; i < m; i++) {
            while (j > 0 && pattern[i] != pattern[j]) {
                j = next[j - 1];
            }
            if (pattern[i] == pattern[j]) {
                j++;
            }
            next[i] = j;
        }
        for (int i = 0, j = 0; i < n; i++) {
            while (j > 0 && text[i] != pattern[j]) {
                j = next[j - 1];
            }
            if (text[i] == pattern[j]) {
                j++;
            }
            if (j == m) {
                return i - m + 1;
            }
        }
        return -1;
    }

int ne[N];

void get_Next(string s)        //这个函数对字符串s进行预处理得到next数组
{
    int j = 0;
    ne[0] = 0;    //初始化
    for (int i = 1; i < s.size(); i++) {    //i指针指向的是后缀末尾,j指针指向的是前缀末尾
        while (j > 0 && s[i] != s[j]) j = ne[j - 1];    //前后缀不相同,去找j前一位的最长相等前后缀
        if (s[i] == s[j]) j++;    //前后缀相同,j指针后移
        ne[i] = j;    //更新next数组
    }
}
int strSTR(string s, string t)	//这个函数是从s中找到t,如果存在返回t出现的位置,如果不存在返回-1
{
    if(t.size()==0)	return 0;
    get_Next(t);
    int j = 0;
    for(int i = 0; i < s.size(); i++){
        while(j>0&&s[i]!= t[j])	j = ne[j-1];
        if(s[i]==t[j])	j++;
        if(j==t.size())	{
            // 找到匹配后的处理
            return i - t.size() + 1;
        }
    }
    return -1;
}

字符串哈希


const int N = 2e6 + 5; // 最大字符串的个数
const int M = 2e6 + 10; // 题目中字符串的最大长度
const ull base = 131; // 131,13331不容易哈希碰撞

// p[i]:表示p的i次方
// h[i]:表示s[1~i]的哈希值,如h[2]表示字符串s前两个字符组成字符串的哈希值
ull p[N], h[N];

//int n;

// 预处理hash函数的前缀和,时间复杂度O(n)
void init(string s) {
    //  下标从1开始
    int n = s.size() - 1;
    // p^0=1,空串哈希值为0
    p[0] = 1, h[0] = 0;
    for (int i = 1; i <= n; i++) {
        p[i] = p[i - 1] * base;
        h[i] = h[i - 1] * base + s[i]; // 前缀和计算公式
    }
}

// 计算s[l~r](子串)的hash值,时间复杂度O(1)
ull get(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1]; // 区间和计算字串的hash值
}

// 判断s中的两个子串是否相同
bool substr(int l1, int r1, int l2, int r2) {
    return get(l1, r1) == get(l2, r2);
}

//  求ss的哈希值(下标从0开始)
ull gethash(string ss) {
    ull ret = 0;
    for (int i = 0; i < ss.size(); ++i)
        ret = ret * base + (ull) ss[i];
    return ret;
}

ac自动机

最原始的问题情境就是“给定n个模式串和1个文本串,求有多少种模式串在文本串里出现过。”

同一个模式串视为一种

const int maxn = 2 * 1e6 + 9;
int trie[maxn][26]; //字典树
int cntword[maxn];  //记录该单词出现次数
int fail[maxn];     //失败时的回溯指针
int cnt = 0;

void insertWords(string s) {
    int root = 0;
    for (int i = 0; i < s.size(); i++) {
        int next = s[i] - 'a';
        if (!trie[root][next])
            trie[root][next] = ++cnt;
        root = trie[root][next];
    }
    cntword[root]++;      //当前节点单词数+1
}

void getFail() {
    queue<int> q;
    for (int i = 0; i < 26; i++) {      //将第二层所有出现了的字母扔进队列
        if (trie[0][i]) {
            fail[trie[0][i]] = 0;
            q.push(trie[0][i]);
        }
    }

//fail[now]    ->当前节点now的失败指针指向的地方
//    tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
    while (!q.empty()) {
        int now = q.front();
        q.pop();

        for (int i = 0; i < 26; i++) {      //查询26个字母
            if (trie[now][i]) {
                //如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
                //有点绕,为了方便理解特意加了括号

                fail[trie[now][i]] = trie[fail[now]][i];
                q.push(trie[now][i]);
            } else//否则就让当前节点的这个子节点
                //指向当前节点fail指针的这个子节点
                trie[now][i] = trie[fail[now]][i];
        }
    }
}


int query(string s) {
    int now = 0, ans = 0;
    for (int i = 0; i < s.size(); i++) {    //遍历文本串
        now = trie[now][s[i] - 'a'];  //从s[i]点开始寻找
        for (int j = now; j && cntword[j] != -1; j = fail[j]) {
            //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
            ans += cntword[j];
            cntword[j] = -1;    //将遍历国后的节点标记,防止重复计算
        }
    }
    return ans;
}

void work() {
    int n;
    cin >> n;
    string s;
    for (int i = 0; i < n; ++i) {
        cin >> s;   //  模式串
        insertWords(s);
    }
    getFail();

    //  文本串
    cin >> s;
    cout << query(s) << '\n';
}

Pollard Rho

求出x的所有质因子O(n^(1/4))

using f64 = long double;
int p;
f64 invp;
void setmod(int x){
    p = x,invp = (f64)1 / x;
}
int mul(int a,int b){
    int z = a * invp * b + 0.5;
    int res = a * b - z * p;
    return res + (res >> 63 & p);
}
int pow(int a,int x,int res = 1){
    for (;x;x >>= 1, a = mul(a,a))
        if (x & 1) res = mul(res,a);
    return res;
}
bool checkprime(int p){
    if (p == 1) return 0;
    setmod(p);
    int d = __builtin_ctzll(p - 1),s = (p - 1) >> d;
    for (int a : {2, 3, 5, 7, 11, 13, 82, 373}){
        if (a % p == 0)
            continue;
        int x = pow(a,s),y;
        for (int i = 0;i < d;++i,x = y){
            y = mul(x,x);
            if (y == 1 && x != 1 && x != p - 1){
                return false;
            }
        }
        if (x != 1) return false;
    }
    return 1;
}
int rho(int n){
    if (!(n & 1)) return 2;
    static std::mt19937_64 gen((size_t)"hehezhou");
    int x = 0,y = 0,prod = 1;
    auto f = [&](int o){return mul(o,o) + 1;};
    setmod(n);
    for (int t = 30,z = 0;t % 64 || gcd(prod,n) == 1;++t){
        if (x == y) x = ++z,y = f(x);
        if (int q = mul(prod,x + n - y)) prod = q;
        x = f(x),y = f(f(y));
    }
    return gcd(prod,n);
}
vector<int> factor(int x){
    vector<int> res;
    auto f = [&](auto f,int x){
        if (x == 1) return;
        if (checkprime(x)) return res.push_back(x);
        int y = rho(x);
        f(f,y),f(f,x / y);
    };
    f(f,x),sort(res.begin(),res.end());
    return res;
}

void work() {
    int x;
    //  求出x的所有质因子O(n^(1/4))
    vector<int> ans = factor(x);
}

 

posted @ 2023-05-05 09:41  bible_w  阅读(23)  评论(0编辑  收藏  举报