资料整理

1.区间最大值

求数组(连续)在某一区间最大值(简单的动态规划的思想)

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
int main(){
    int n;
    int a[100];
    while(~scanf("%d", &n)){
        int i, sum = 0;
        for(i = 0; i < n; i++){
            cin>>a[i];
        }
        int max1 = 0;
        for(i = 0; i < n; i++){
            sum += a[i];
            max1 = max(sum, max1);
            if(sum < 0)sum = 0;
        }
        cout<<max1<<endl;
    }
return 0;
}

2.N!问题

2.1N!是质因数分解:

x = [ n / p ] + [ n / p ∧ 2 ] + [ n / p ∧ 3 ] + ⋯ ⋯ ⋯ \mathrm{x}=[\mathrm{n} / \mathrm{p}]+\left[\mathrm{n} / \mathrm{p}^{\wedge} 2\right]+\left[\mathrm{n} / \mathrm{p}^{\wedge} 3\right]+\cdots \cdots \cdots x=[n/p]+[n/p2]+[n/p3]+
例如:给你N,问你N!有几个0.我们可有求出2的个数n, 5的个数m,res = min(n, m), n和m的求法按照上面的公式。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
int a[maxn], vis[maxn];
int cn = 1;
void Prime()
{
    memset(vis, 0, sizeof(maxn));
    for(int i = 2; i <= maxn; i++)
    {
        if(!vis[i])
        {
            a[cn++] = i;
        }
        for(int j = 1; j < cn && i*a[j] <= maxn; j++)
        {
            vis[i* a[j]] = 1;
            if(i % a[j] == 0)break;
        }
    }
}
int query(int x, int n)//对应x的公式
{
    int ans= 0;
    while(n)
    {
        ans += n / x;
        n /= x;
    }
    return ans;
}
int main()
{
    int n;
    Prime();
    int T, ca = 0;
    scanf("%d", &T);
    while(T--)
    {
        cin>>n;
        printf("Case %d: %d =", ++ca, n);
        for(int i = 1; i < cn; i++)
        {
            int t = query(a[i], n);
            if(t == 0)break;
            if(i != 1)printf(" *");
            printf(" %d (%d)", a[i], t);
 
        }
        printf("\n");
 
    }
    return 0;
}

2.2求出N,把他转化为base禁制, N!是几位的。

N! = base^k,这个应该能想明白吧。然后等式两边同时取对数也就是log(N!) = k*log(base),然后log(N!) = log(1)+log(2)+…+log(n),log(N!)/k就是最后的结果了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
double sum[maxn];
void Init()
{
    for(int i = 2; i <= maxn; i++)//预处理
    {
        sum[i] += sum[i-1]+log(double(i));
    }
}
int main()
{
    int T, n, base;
    Init();
    cin>>T;
    int t, ca = 0;
    while(T--)
    {
        cin>>n>>base;
        if(n == 0)printf("Case %d: 1\n",++ca);
        else
        {
            double ans =sum[n];
            ans /= log((double) base);
            if(ans != (int)ans)t = (int)ans + 1;
            else t = (int)ans;
            printf("Case %d: %d\n",++ca, t );
        }
    }
 
    return 0;
}

3. 1~N的约数问题

3.1 1~n所有数的约数之和:

暴力的方法就是n/i*i;
例题:首先给你个函数SOD(n)为除了1和本身所有的因子之和,如果则会个数的因子是一样的话只能算一个,比如4的因子就是一个2,然后在给你一个函数CSOD(n)它为SOD(n)从1到n的和。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
int main()
{
    LL ans, a;
    int T;
    cin>>T;
    int ca= 0;
    while(T--)
    {
        ans = 0;
        cin>>a;
        int m = sqrt(a);
        LL t1, t2 = m+1;;
        for(int i = 2; i <= m; i++)
        {
            t1 = a / i;
            ans += (t1-1)*i;//减1的情况就是这个因子本身
            if(t1 >= t2)
            {
                ans += (t1-t2+1)*(t1+t2)/2;
//当为2的时候算的是从11到50的值,当i为3的时候算的是从11到33的值。。。
//发没发现34~50之间的数只算了一边。。。。
            }
        }
        printf("Case %d: %lld\n",++ca, ans);
    }
    return 0;
}

3.2 1~n所有数的约束的个数

int main()
{
    int n;
    cin>>n;
    long ans=0;
    int i;
    for(i=1; i*i<=n; i++)
    {
        ans+=n/i;
    }
    ans+=(n-i+1);//当i*i> n以后因数只剩下一个
    cout<<ans<<endl;
}

4. 大数取模(同余模定理)

被除数是一个非常大的数,除数最大是一个长整型的数

#include<bits/stdc++.h>
using namespace std;
#define LL long long
char a[205];
int main()
{
    int T;
    scanf("%d", &T);
    int ca = 0;
    while(T--)
    {
        getchar();
        LL n;
        scanf("%s %lld", a, &n);
        int len = strlen(a);
        printf("Case %d: ", ++ca);
        if(a[0] == '0' && len == 1)
        {
            printf("divisible\n");
            continue;
        }
        if(n < 0)n = -n;
        LL ans = 0;
        int i = 0;
        if(a[0] == '-')i = 1;
        for(; i < len; i++)
        {
            ans  = (ans*10+a[i]-'0')%n;
        }
        if(ans == 0)printf("divisible\n");
        else printf("not divisible\n");
    }
    return 0;
}

5.求Hn

H n = 1 + 1 2 + 1 3 + 1 4 + ⋯ + 1 n = ∑ k = 1 n 1 k H_{n}=1+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+\cdots+\frac{1}{n} =\sum_{k=1}^{n} \frac{1}{k} Hn=1+21+31+41++n1=k=1nk1
方法一(分块打表):题意十分的简单,但是看到这道题第一想法就是暴力,后来发现有T次查询,如果暴力指定超时,所以就想到了打表,打表的时间复杂度是O(n),查询时O(1),但是问题来了数据范围最大到1e8,内存会爆掉的,所以就需要优化一下节约内存,所以可以采用分段打表,100数打一个表,空间是1e6那么内存就不会。

#include<bits/stdc++.h>
using namespace std;
const int maxn  = 1e6+5;
const int INF = 1e8+5;
double a[maxn];
void init(){//打表
    double tmp = 0;
    for(int i = 1; i <= INF; i++){
        tmp += double(1.0/i);
        if(i % 100  == 0){
            a[i/100] = tmp;
        }
    }
}
int main(){
    int T;
    init();
    cin>>T;
    int ca = 0;
    while(T--){
        int n;
        scanf("%d", &n);
        int t = n / 100;//算出n在那一块
        double ans = a[t];
        for(int i = t * 100+1; i <= n; i++){//剩下的部分
            ans += 1.0/(i*1.0);
        }
        printf("Case %d: %.10lf\n",++ca, ans);
    }
return 0;
}

方法二(公式法):
f(n)=ln(n)+C+1/(2*n), 公式中C≈0.57721566490153286060651209;但是使用的时候n不能过小, 10000以上的数就可以用这个公式,剩下的就是打表,10000以内的数打表应该非常容易。

#include<bits/stdc++.h>
using namespace std;
const double  C = 0.57721566490153286060651209;
const int maxn = 100000;
double a[maxn];
void Init(){
     a[1] = 1;
     for(int i = 2; i <= maxn; i++){
        a[i] = a[i-1]+double(1.0/i);
     }
}
int main(){
    int T;
    Init();
    cin>>T;
    int ca = 0;
    while(T--){
        int n;
        scanf("%d", &n);
        if(n <= maxn)printf("Case %d: %.10lf\n",++ca, a[n]);
        else {
            printf("Case %d: %.10lf\n",++ca, log(n)+C+double(1.0/(2*n)));
        }
    }
 
return 0;
}

6.求n^k的前三位数字与后三位数字

两个数n、k,求n^k的前三位数字与后三位数字。
题目解析:后三位数字很容易求,通过幂取模即可。先考虑n不做幂运算时如何取前三位,令d=log10(n),则d=a(整数部分)+i(小数部分)
n = 1 0 d = 1 0 a + i = 1 0 a ∗ 1 0 i \mathrm{n}=10^{d}=10^{a+i}=10^\mathrm{a} * 10^ \mathrm{i} n=10d=10a+i=10a10i
1 0 i ∗ 100 10^{i}*100 10i100即为前三位。以123456为例,d=log10(123456)=5+i,则 123456 = 1 0 i ∗ 1 0 5 123456=10^i*10^5 123456=10i105又因为 123456 = 1.23456 ∗ 1 0 5 123456=1.23456*10^5 123456=1.23456105所以 1 0 i = 1.23456 10^i=1.23456 10i=1.23456所以前三位为 1 0 i ∗ 100 = 123 10^i*100=123 10i100=123
再来考虑n做幂运算时,将 n k n^k nk变换为 1 0 k l o g 10 ( n ) 10^{klog10(n)} 10klog10(n) d = k ∗ l o g 10 ( n ) d=k*log10(n) d=klog10(n)

#include<string.h>
#include<bits/stdc++.h>
using namespace std;
#define LL long long
int q_pow(LL a, LL b){
   LL base = a, ans = 1;
   while(b){
       if(b & 1)ans = (ans * base) % 1000;
       base = (base * base) % 1000;
       b >>= 1;
   }
   return ans;
}
int main(){
    int T;
    cin>>T;
    int ca = 0;
    while(T--){
        LL n, k;
        scanf("%lld %lld", &n, &k);
        int ans1 = q_pow(n, k);
        double d = k * log10(n);
        int ans2 = pow(10, (d-(int)d))*100;
        printf("Case %d: %d %03d\n",++ca, ans2, ans1);
 
    }
}

7.莫队算法

#include<bits/stdc++.h>
using namespace std;
const int maxn = 50005;
int a[maxn];
struct Node{
      int l, r, i;
}pos[maxn];
int ans = 0, block;
int cnt[maxn], res[maxn];
void Add(int x){//先减去之前的,更新一下,加上现在的
     ans -= cnt[a[x]]*cnt[a[x]];
     ++cnt[a[x]];
     ans += cnt[a[x]] * cnt[a[x]];
}
void Remove(int x){
     ans -= cnt[a[x]]*cnt[a[x]];
     --cnt[a[x]];
     ans += cnt[a[x]] * cnt[a[x]];
}
 inline int read(){
     int k=0;
     char c;
     c=getchar();
     while(!isdigit(c))c=getchar();
     while(isdigit(c)){k=(k<<3)+(k<<1)+c-'0';c=getchar();}
     return k;
 }
bool cmp(Node A, Node B){
     if(A.l / block == B.l/ block)return A.r < B.r;
     else return A.l < B.l;
}
int main(){
    int n, m, k;
    n = read();
    m = read();
    k = read();
    for(int i = 1; i <= n; i++){
        a[i] = read();
    }
        block = sqrt(n);
    for(int i = 1; i <= m; i++){
        pos[i].l = read();
        pos[i].r = read();
        pos[i].i = i;
    }
    sort(pos+1, pos+m+1, cmp);
    int L = 1, R = 0;
    ans = 0;
    for(int i = 1; i <= m; i++){
        while(L < pos[i].l){
            Remove(L++);
        }
        while(L > pos[i].l){
            Add(--L);
        }
        while(R < pos[i].r){
            Add(++R);
        }
        while(R > pos[i].r){
            Remove(R--);
        }
       // cout<<ans<<endl;
        res[pos[i].i] = ans;
    }
    for(int i = 1; i <= m; i++){
        printf("%d\n", res[i]);
    }
return 0;
}

8.AC自动机

8.1给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+7;
 
int trie[maxn][26];
int word[maxn];
int fail[maxn];
int cnt = 0;
void InsertWord(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];
     }
     word[root]++;
}
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]);
        }
     }
     while(!q.empty()){
          int now = q.front();
          q.pop();
          for(int i = 0; i < 26; i++){
            if(trie[now][i]){
                fail[trie[now][i]] = trie[fail[now]][i];
                q.push(trie[now][i]);
            }
            else 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'];
        for(int j = now; j  && word[j] != -1; j = fail[j]){
            ans += word[j];
            word[j] = -1;
        }
    }
    return ans;
}
int main(){
    ios::sync_with_stdio(false);
    int n;
    string a, s;
    cin>>n;
    for(int i = 1; i <= n; i++){
        cin>>a;
        InsertWord(a);
    }
    fail[0] = 0;
    getFail();
    cin>>s;
    cout<<query(s)<<endl;
return 0;
}

8.2有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100000;
struct Node
{
    int id, cn;
} re[maxn];
string a[200];
int trie[maxn][26];
int word[maxn];
int fail[maxn];
int cnt = 0;
map<int, int>mp;//映射到是哪一个串
void InsertWord(string s, int i)
{
    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];
    }
    word[root]++;
    mp[root] = i;
}
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]);
        }
    }
    while(!q.empty())
    {
        int now = q.front();
        q.pop();
        for(int i = 0; i < 26; i++)
        {
            if(trie[now][i])
            {
                fail[trie[now][i]] = trie[fail[now]][i];
                q.push(trie[now][i]);
            }
            else trie[now][i] = trie[fail[now]][i];
        }
    }
}
void query(string s)
{
    int now = 0, ans = 0;
    for(int i = 0; i < s.size(); i++)
    {
        now = trie[now][s[i]-'a'];
        for(int j = now; j; j = fail[j])
        {
            if(word[j])
            {
                int t = mp[j];
                re[t].id = t;
                re[t].cn++;
            }
 
        }
    }
}
bool cmp(Node A, Node B)
{
    if(A.cn == B.cn)
    {
        return A.id < B.id;
    }
    else return A.cn > B.cn;
}
void init(){//初始化
     cnt = 0;
     memset(word, 0, sizeof(word));
     memset(re, 0, sizeof(re));
     memset(fail, 0, sizeof(fail));
     memset(trie, 0, sizeof(trie));
}
int main()
{
    ios::sync_with_stdio(false);
    int n;
    string s;
    while(cin>>n && n != 0)
    {
 
        init();
        for(int i = 1; i <= n; i++)
        {
            cin>>a[i];
            InsertWord(a[i], i);
        }
        fail[0] = 0;
        cin>>s;
        getFail();
        query(s);
        sort(re+1, re+1+n, cmp);
        int ans = re[1].cn;
        cout<<ans<<endl;
        for(int i = 1; i <= n; i++)
        {
            if(re[i].cn == ans)
            {
                cout<<a[re[i].id]<<endl;
            }
            else break;
        }
    }
    return 0;
}

10.给你一个数n求LCM(1, 2,…,n ),LCM 为最大公约数

思路:位图+二分+欧拉筛+二分

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int maxn = 1e8+5;
unsigned int pri[5800000];
const long long mod = 1LL<<32;
unsigned int sum[5800000];
bitset<maxn>vis;//位图压缩空间
int cn = 1;
void Prime(){//欧拉筛
     pri[0] = 0;
     for(int i = 2; i < maxn; i++){
        if(!vis[i])pri[cn++] = i;
        for(int j = 1; j < cn && i * pri[j] < maxn; j++){
            vis[i*pri[j]] = true;
            if(i % pri[j] == 0)break;
        }
     }
     sum[0] = 1;
     for(int i = 1; i < cn; i++){//前缀积
        sum[i] = sum[i-1]*pri[i];
     }
    // pri[cn] = maxn;
}
LL q_pow(int a, int b){//快速幂
   LL ans = 1, base = a;
   while(b){
       if(b & 1)ans = (ans * base) % mod;
       base = (base * base) % mod;
       b >>= 1;
   }
return ans;
}
int main(){
    Prime();
    int T;
    scanf("%d", &T);
    int ca = 0;
    //cout<<cn<<endl;
    while(T--){
        int n;
        scanf("%d", &n);
        int pos = upper_bound(pri+1, pri+cn, n) - pri-1;//二分查找第一个比n大的数,然后减去1,就小于等于n
        LL ans = sum[pos] % mod;
        for(int i = 1; i < cn && pri[i]*pri[i] <= n; i++){//
            int t = n;
            int k = 0;
            while(t / pri[i]){
                t /= pri[i];
                k++;
            }
            ans = q_pow(pri[i], --k)*ans % mod;
        }
        printf("Case %d: %lld\n",++ca, ans%mod);
    }
 
return 0;
}

11.二维前缀和

二维前缀和+二分+超级快读挂
本题的题意就是给你一个矩阵,只包含0或者1,然后让你求出最多只包含一个1的矩阵最大有多大。
思路:一开始看到这道题之后,感觉像搜索问题仔细寻思了一下发现不是,然后就想到了二维前缀和,求出二维前缀和之后,遍历所有的点,以(i, j)为正方形的右下角去二分查找,最大正方形的边长,然后找到最大的边长。但是这道题会卡你的数据,所以需要快速读入。

#include<bits/stdc++.h>
const int maxn = 1005;
int a[maxn][maxn], sum[maxn][maxn];
int n, m;
namespace fastIO
{
#define BUF_SIZE 100000
//fread -> read
bool IOerror = 0;
inline char nc()
{
    static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
    if(p1 == pend)
    {
        p1 = buf;
        pend = buf + fread(buf, 1, BUF_SIZE, stdin);
        if(pend == p1)
        {
            IOerror = 1;
            return -1;
        }
    }
    return *p1++;
}
inline bool blank(char ch)
{
    return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
}
inline void read(int &x)
{
    char ch;
    while(blank(ch = nc()));
    if(IOerror) return;
    for(x = ch - '0'; (ch = nc()) >= '0' && ch <= '9'; x = x * 10 + ch - '0');
}
#undef BUF_SIZE
};
using namespace fastIO;
int Judge(int i, int j, int d)//二分查找
{
    int l = 1, r = d, mid, ans = 1;
    while(l < r)
    {
        int mid = (l + r + 1)>>1;
        if (sum[i][j]-sum[i-mid][j]-sum[i][j-mid]+sum[i-mid][j-mid] <= 1)//注意题意最多一个
        {
            l = mid;
        }
        else r = mid-1;
    }
    return l;
}
using namespace std;
int main()
{
    int T;
    read(T);
    while(T--)
    {
        read(n);
        read(m);
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                read(a[i][j]);
                sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
            }
        }
        int ans = 0;
        for(int i = 1; i <= n; i++)//遍历所有的点
        {
            for(int j = 1; j <= m; j++)
            {
                int d = min(i, j);//以(i, j)为右下角的正方形的边长最大为d
                int t = Judge(i, j, d);//
                ans = max(ans, t);
            }
        }
        printf("%d\n", ans);

    }
    return 0;
}

12.强连通分量

在这个集合中任意两个村庄X,Y都满足<X,Y>。现在你的任务是,找出最大的绝对连通区域,并将这个绝对连通区域的村庄按编号依次输出。若存在两个最大的,输出字典序最小的,比如当存在1,3,4和2,5,6这两个最大连通区域时,输出的是1,3,4。

#include<bits/stdc++.h>
using namespace std;
 
const int maxn = 5010;
const int N =  50010;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0;
int head[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn];
set<int>g[maxn];
set<int>::iterator it;
int ans = 0;
struct edge
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void tarjan(int u)
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            g[Bcnt].insert(v);
        }
        while(u != v);
        int  f = g[Bcnt].size();
        ans = max(f, ans);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0, ans = 0;
    memset(ins, 0, sizeof(ins));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(head, -1, sizeof(head));
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= p; i++)
    {
        scanf("%d %d %d", &u, &v, &choose);
        add(u, v);
        if(choose == 2)add(v, u);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    int mi = INT_MAX, pos = 0;
    cout<<ans<<endl;
    for(int i = 1; i <= Bcnt; i++)
    {
        if(g[i].size() == ans)
        {
            int first_num = *(g[i].begin());
            if(mi > first_num)
            {
                pos = i;
                mi = first_num;
            }
        }
    }
    int t = 0;
    for(it = g[pos].begin(); it != g[pos].end(); it++)
    {
        if(t != 0)cout<<" ";
        printf("%d", *it);
        t++;
    }
    cout<<endl;
    return 0;
}

题意:就是有N头牛,给你M中关系,问你有多少头牛受欢迎,当一个牛被除了自己本身的所有牛喜欢那么这头牛就是最受欢迎的牛。
题解:本题采用Tarjan+缩点,把强连通分量看成一个点,统计出度为0的点有多少个,如果出度为0的点为一个的话,那么则这个点(可能是强连通分量缩的点)就是最受欢的,输出这个点包含多少头牛。如果出度为0的点有两个以上,那么就不存在最受欢迎的牛。

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 10010;
const int N = 50010;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0;
int head[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn];
int num[maxn];
int degree[maxn];
 
struct edge
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void tarjan(int u)
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
            num[Bcnt]++;
 
        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0;
    memset(ins, 0, sizeof(ins));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(head, -1, sizeof(head));
    memset(num, 0, sizeof(num));
    memset(belong , 0, sizeof(belong));
    memset(degree, 0, sizeof(degree));
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= p; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    for(int i = 1; i <= n; i++){
        for(int j = head[i]; j != -1; j = e[j].next){
            int v = e[j].v;
            if(belong[i] != belong[v]){
                degree[belong[i]]++;
            }
        }
    }
    int flag = 0, pos = 0;
    for(int i = 1; i <= Bcnt; i++){
        if(degree[i] == 0){
                flag++;
                pos = i;
        }
    }
    if(flag > 1)printf("0\n");
    else printf("%d\n", num[pos]);
    return 0;
}

缩点+DP
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 10010;
const int N = 500100;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0, cn = 0, ans;
int head[N], head1[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn], point[maxn], w[maxn], degree[maxn];
int dis[maxn];
 
struct edge
{
    int v, next;
} e[N];
struct edge_
{
    int v, next;
} e_[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void add_(int u, int v)
{
    e_[cn].v = v;
    e_[cn].next = head1[u];
    head1[u] = cn++;
}
void tarjan(int u)//强连通分量
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0, cn = 1, ans = -1;
    memset(head1, -1,sizeof(head1));
    memset(head, -1, sizeof(head));
 
}
void TopSort()//拓扑排序
{
     queue<int>q;
     for(int i = 1; i <= Bcnt; i++){
        if(!degree[i])q.push(i);
        dis[i] = w[i];
     }
     while(!q.empty()){
        int x = q.front();
        q.pop();
        for(int i = head1[x]; i != -1; i = e_[i].next){
            int v = e_[i].v;
            dis[v] = max(dis[v], dis[x]+w[v]);
            degree[v]--;
            if(degree[v] == 0)q.push(v);
        }
     }
     for(int i = 1; i <= Bcnt; i++){
        ans = max(ans, dis[i]);
     }
 
 
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &point[i]);
    }
    for(int i = 1; i <= p; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    for(int i = 1; i <= n; i++)
    {
        int v;
        for(int j = head[i]; j != -1; j = e[j].next)
        {
            v = e[j].v;
            if(belong[i] != belong[v])//缩点
            {
                add_(belong[i], belong[v]);
                degree[belong[v]]++;
            }
        }
        w[belong[i]] += point[i];
    }
    TopSort();
    printf("%d\n", ans);
    return 0;
}

如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B。有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕新的间谍,掌握新的情报。
思路:本题的思路就是,如果当这个人不能被贿赂并且这个人跟其他人没有关系,那么就是NO。剩下的就是利用缩点,把环缩成一个点,那么这个点的最小值就是整个点的价值。

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 10010;
const int N = 50010;
const int INF = 1e9+100;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0;
int head[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn];
int num[maxn];
int degree[maxn], money[maxn];
 
struct edge
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void tarjan(int u)
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
            num[Bcnt] = (num[Bcnt], money[v]);//更新环的最小值
 
        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0;
    memset(ins, 0, sizeof(ins));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(head, -1, sizeof(head));
    memset(num, 0, sizeof(num));
    memset(belong, 0, sizeof(belong));
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= n; i++)money[i] = INF;
    for(int i = 1; i <= p; i++)
    {
        int pos, val;
        scanf("%d %d", &pos, &val);
        money[pos] = val;
    }
    int m;
    scanf("%d", &m);
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i] && money[i] != INF)tarjan(i);
    for(int i = 1; i <= n; i++)
    {
        if(!dfn[i]) //如果这个人没有被贿赂,或者跟其他人没有关系
        {
            printf("NO\n");
            printf("%d\n", i);
            return 0;
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = head[i]; j != -1; j = e[j].next)
        {
            int v = e[j].v;
            if(belong[i] != belong[v])
            {
                degree[belong[v]]++;
            }
        }
    }
    for(int i = 1; i <= Bcnt; i++)
    {
        if(!degree[i]) //利用缩点,找到入度为0的点,
        {
            ans += num[i];
        }
    }
    printf("YES\n");
    printf("%d\n", ans);
    return 0;
}

割点:
给出一个n个点,m条边的无向图,求图的割点。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 20010;
const int N = 200100;
int head[N];
int dfn[maxn], low[maxn], k = 0, tot = 0, root, cut[maxn];
struct Node
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void Tarjan(int x)
{
    dfn[x] = low[x] = ++tot;
    int flag = 0;
    for(int i = head[x]; i != -1; i = e[i].next)
    {
        int y = e[i].v;
        if(!dfn[y])
        {
            Tarjan(y);
            low[x] = min(low[x], low[y]);
            if(dfn[x] <= low[y] && (x != root || ++flag > 1))cut[x] = 1;
        }
        else low[x] = min(low[x], dfn[y]);
    }
}
int main()
{
    memset(head, -1, sizeof(head));
    int n, m, a, b;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d", &a, &b);
        add(a, b);
        add(b, a);
    }
    for(int i = 1; i <= n; i++)
    {
        if(!dfn[i])root = i, Tarjan(i);
    }
    int cn = 0;
    for(int i = 1; i <= n; i++)
    {
        if(cut[i])cn++;
    }
    printf("%d\n", cn);
    for(int i =  1; i <= n; i++)
    {
        if(cut[i])printf("%d ", i);
    }
    printf("\n");
}

N(2<N<100)各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。2,至少需要添加几条传输线路(边),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。
思想:第一问就是问你最少给多少人发信息,所以利用缩点,然后寻找入度为0的点就是第一问的答案。第二问,最少增加多少条边,这个图示强联通图,所以就是缩点之后,找到入度和初度为0的最大值。

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 10010;
const int N = 500100;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0, cn = 0, ans;
int head[N], head1[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn], in[maxn], out[maxn];
 
struct edge
{
    int v, next;
} e[N];
struct edge_
{
    int v, next;
} e_[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void add_(int u, int v)
{
    e_[cn].v = v;
    e_[cn].next = head1[u];
    head1[u] = cn++;
}
void tarjan(int u)//强连通分量
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0, cn = 1, ans = -1;
    memset(head1, -1,sizeof(head1));
    memset(head, -1, sizeof(head));
 
}
 
int main()
{
    int T, p, u, v, choose;
    init();
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int v;
        while(scanf("%d", &v) && v != 0)
        {
            add(i, v);
        }
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    for(int i = 1; i <= n; i++)
    {
        int v;
        for(int j = head[i]; j != -1; j = e[j].next)
        {
            v = e[j].v;
            if(belong[i] != belong[v])//缩点
            {
                add_(belong[i], belong[v]);
                in[belong[v]]++;
                out[belong[i]]++;
            }
        }
    }
    int ans1 = 0, ans2 = 0;
    for(int i = 1; i <= Bcnt; i++)
    {
        if(!in[i])ans1++;
        if(!out[i])ans2++;
    }
    printf("%d\n", ans1);
    if(Bcnt ==  1)
    {
        printf("0\n");
        return 0;
    }
 
 
    ans = max(ans1, ans2);
    printf("%d\n", ans);
    return 0;
}

13.Trie:

给你n个数,让你求出任意两个数的异或值的最大值。
思路:本题如果采用异或操作时间指定会超时,所以就需要优化,我们可以换一种方法去思考这个问题,可以把所有的A[i]的写成二进制,把这些二进制用Trie来存储,在查找第A[i]的异或的最大值的时候,我们可以从头开始找的时候如果有跟A[i]的二进制相反的那么就找相反的,如果没有相反的就找相同的

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
const int N  = 3e7+10;
int a[maxn];
int trie[N][2];
int tot = 0;
void Insert(int val){//建树
     int rt = 0;
     for(int i = 30; ~i; i--){
        int x = val>>i&1;//取最高位
        if(!trie[rt][x]){
            trie[rt][x] = ++tot;
        }
        rt = trie[rt][x];
     }
}
int query(int val){
    int rt = 0, res = 0;
    for(int i = 30; ~i; i--){
        int x = val >> i&1;
        if(trie[rt][!x]){//如果有相反的
            res += 1 << i;
            rt = trie[rt][!x];
        }
        else rt = trie[rt][x];//没有相反的
    }
    return res;
}
int main(){
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        Insert(a[i]);
    }
    int res = 0;
    for(int i = 1; i <= n; i++){
        res = max(res, query(a[i]));
    }
    printf("%d\n", res);
}

给定N个字符串S1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1~SN中有多少个字符串是T的前缀。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int trie[maxn][26];
int sum[maxn], tot = 0;
char str[maxn];
void Insert(char *s, int rt)
{
    int len = strlen(s);
    for(int i = 0; i < len; i++)
    {
        int x = s[i] - 'a';
        if(!trie[rt][x])
        {
            trie[rt][x] = ++tot;
        }
        rt = trie[rt][x];

    }
    sum[rt]++;
}
int query(char *s, int rt)
{
    int len = strlen(s);
    int ans = 0;
    for(int i = 0; i < len; i++)
    {
        int x = s[i] - 'a';
        if(!trie[rt][x])return ans;
        if(trie[rt][x]) ans += sum[trie[rt][x]];
        rt = trie[rt][x];
    }
    return ans;
}
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    while(n--)
    {
        scanf("%s", str);
        Insert(str, 0);
    }
    while(m--)
    {
        scanf("%s", str);
        printf("%d\n",query(str, 0));
    }
    return 0;
}

给定一个树,树上的边都具有权值。
树中一条路径的异或长度被定义为路径上所有边的权值的异或和:
⊕ 为异或符号。
给定上述的具有n个节点的树,你能找到异或长度最大的路径吗?

#include<bits/stdc++.h>
using namespace std;
const int maxn = 101000;
const int N  = 1000000;
int a[maxn];
int trie[N][2];
int tot = 0;
int head[N];
struct Node
{
    int v, w, next;
} e[N];
int k = 1;
void add(int u, int v, int w)
{
    e[k].v = v;
    e[k].w = w;
    e[k].next = head[u];
    head[u] = k++;
}
void Insert(int val)
{
    int rt = 0;
    for(int i = 30; ~i; i--)
    {
        int x = val>>i&1;
        if(!trie[rt][x])
        {
            trie[rt][x] = ++tot;
        }
        rt = trie[rt][x];
    }
}
int query(int val)
{
    int rt = 0, res = 0;
    for(int i = 30; ~i; i--)
    {
        int x = val >> i&1;
        if(trie[rt][!x])
        {
            res += 1 << i;
            rt = trie[rt][!x];
        }
        else rt = trie[rt][x];
    }
    return res;
}
void dfs(int u, int fa, int sum)
{
    a[u] = sum;
    for(int i = head[u]; ~i; i = e[i].next)
    {
        int v = e[i].v;
        if(v != fa)dfs(v, u, sum^e[i].w);
    }
}
int main()
{
    int n;
    scanf("%d", &n);
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= n-1; i++)
    {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        add(u, v, w);
        add(v, u, w);
    }
    dfs(0, -1, 0);
    int res = 0;
    for(int i = 0; i < n; i++)Insert(a[i]);
    for(int i = 0; i < n; i++)
    {
        res = max(res, query(a[i]));
    }
    printf("%d\n", res);

}

14.字符串HASH:

最大回文子串:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2001000;//注意数组开的范围
typedef unsigned long long ULL;
char s[maxn];
ULL hr[maxn], hl[maxn], p[maxn];
int base = 131;
ULL query(ULL h[], int l, int r)
{
    return h[r] - h[l-1]*p[r-l+1];
}
int main()
{
    int ca = 0;
    while(scanf("%s", s+1) && strcmp(s+1, "END"))
    {
        int n = strlen(s+1);
        n *= 2;
        for(int i =  n; i > 0; i -= 2) //强行在数组中加入新的辅助符号
        {
            s[i] = s[i/2];
            s[i-1] = 'z'+1;
        }
        p[0] = 1;
        for(int i = 1, j = n; i <= n; i++, j--) //字符串哈希
        {
            hl[i] = hl[i-1]*base + (s[i]-'a'+1);
            hr[i] = hr[i-1]*base +(s[j]-'a'+1);
            p[i] = p[i-1]*base;
        }
        int res = 0;
        int l, r;
        for(int i = 1; i <= n; i++)
        {
            l = 0, r = min(i-1, n-i);
            while(l < r)
            {
                int mid = (l+r+1)>>1;
                if(query(hl, i-mid, i-1) != query(hr, n-(i+mid)+1, n-(i+1)+1))r = mid-1;
                else l = mid;
            }
            if(s[i-l] <= 'z')res = max(res, l+1);
            else res = max(res, l);
        }
        printf("Case %d: %d\n", ++ca, res);
    }

    return 0;
}

我们每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000100;
#define ULL unsigned long long
ULL h[maxn], p[maxn];
char str[maxn];
int main(){
    scanf("%s", str+1);
    int n = strlen(str+1), q;
    scanf("%d", &q);
    p[0] = 1;
    for(int i = 1; i <= n; i++){
        h[i] = h[i-1] * 131+(str[i] - 'a' + 1);
        p[i] = p[i-1] * 131;
    }
    for(int i = 1; i <= q; i++){
        int l_1, r_1, l_2, r_2;
        scanf("%d %d %d %d", &l_1, &r_1, & l_2, &r_2);
        if(h[r_1]-h[l_1-1]*p[r_1-l_1+1] == h[r_2]-h[l_2-1]*p[r_2-l_2+1]){
            printf("Yes\n");
        }
        else {
            printf("No\n");
        }
    }
return 0;
}

详细地说,给定一个长度为 n 的字符串S(下标 0~n-1),我们可以用整数 k(0≤k<n0≤k<n) 表示字符串S的后缀 S(k~n-1)。
把字符串S的所有后缀按照字典序排列,排名为 i 的后缀记为 SA[i]。
额外地,我们考虑排名为 i 的后缀与排名为 i-1 的后缀,把二者的最长公共前缀的长度记为 Height[i]。
我们的任务就是求出SA与Height这两个数组。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 301000;
typedef unsigned long long ULL;
ULL p[maxn], h[maxn];
char s[maxn];
int vis[maxn], base = 131, n;
ULL get(int l, int r){
    return h[r] - h[l-1]*p[r-l+1];
}
int Binary_Search(int a, int b){
    int l = 0, r = min(n-a+1, n-b+1);
    while(l < r){
        int mid = (l+r+1)>>1;
        if(get(a, a+mid-1) != get(b, b+mid-1))r = mid-1;
        else l = mid;
    }
    return l;
}
bool cmp(int a, int b){
     int l = Binary_Search(a, b);
     int a_val = a+l > n ? INT_MIN : s[a+l];
     int b_val = b+l > n ? INT_MIN : s[b+l];
     return a_val < b_val;
}
int main(){
    scanf("%s", s+1);
    n = strlen(s+1);
    p[0] = 1;
    for(int i = 1; i <= n; i++){
        h[i] = h[i-1]*base+(s[i]-'a'+1);
        p[i] = p[i-1]*base;
        vis[i] = i;
    }
    sort(vis+1, vis+1+n, cmp);
    for(int i = 1; i <= n; i++){
       printf("%d ", vis[i]-1);
    }
    cout<<endl;
    for(int i = 1; i <= n; i++){
        if(i == 1)printf("0");
        else printf(" %d", Binary_Search(vis[i], vis[i-1]));
    }
    cout<<endl;
return 0;
}

15.矩阵快速幂求斐波那契

[ F n + 1 F n F n F n − 1 ] = [ 1 1 1 0 ] n = [ 1 1 1 0 ] [ 1 1 1 0 ] ⎵ n  times  \left[ \begin{array}{cc}{F_{n+1}} &amp; {F_{n}} \\ {F_{n}} &amp; {F_{n-1}}\end{array}\right]=\left[ \begin{array}{cc}{1} &amp; {1} \\ {1} &amp; {0}\end{array}\right]^{n}=\underbrace{\left[ \begin{array}{cc}{1} &amp; {1} \\ {1} &amp; {0}\end{array}\right] \left[ \begin{array}{cc}{1} &amp; {1} \\ {1} &amp; {0}\end{array}\right]}_{n \text { times }} [Fn+1FnFnFn1]=[1110]n=n times  [1110][1110]

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define mod 10000
struct Matrix
{
    long long ma[2][2];
};
Matrix mul(Matrix A,Matrix B)
{
    Matrix C;
    C.ma[0][0]=C.ma[0][1]=C.ma[1][0]=C.ma[1][1]=0;
    for(int i=0;i<2;i++)
    {
        for(int j=0;j<2;j++)
        {
            for(int k=0;k<2;k++)
            {
                C.ma[i][j]=(C.ma[i][j]+A.ma[i][k]*B.ma[k][j])%mod;
            }
        }
    }
    return C;
}
Matrix pow_mod(Matrix A,long long n)
{
    Matrix B;
    B.ma[0][0]=B.ma[1][1]=1;
    B.ma[0][1]=B.ma[1][0]=0;
    while(n)
    {
        if(n&1) B=mul(B,A);
        A=mul(A,A);
        n>>=1;
    }
    return B;
}
int main()
{
    long long n;
    while(~scanf("%lld",&n)&&n!=-1)
    {
        Matrix A;
        A.ma[0][0]=1;A.ma[0][1]=1;
        A.ma[1][0]=1;A.ma[1][1]=0;
        Matrix ans=pow_mod(A,n);
        printf("%lld\n",ans.ma[0][1]);
    }
    return 0;
}

16.Lucas定理:

普通Lucas定理时间复杂度O(logPlogN),并且p为质数

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
typedef  long long LL;
const int mod = 1000003;
LL f[maxn];//开long long
void init() //初始化阶乘
{
    f[0] = 1;
    for(int i = 1; i <= maxn; i++)
    {
        f[i] = (f[i-1]*i)%mod;
    }
}
LL inv(LL a, LL mod) //求逆元
{
    if(a == 1)return 1;
    return inv(mod%a, mod)*(mod - mod/a)%mod;
}
LL Lucas(LL n, LL m, LL p)//Lucas定理
{
    LL ans = 1;
    while(n && m)
    {
        LL a = n % p;
        LL b = m % p;
        if(a < b)return 0;
        ans = ans*f[a]%p*inv(f[b]*f[a-b]%mod, mod)%mod;
        n /= p;
        m /= p;
    }
    return ans;
}
int main()
{
    int T, ca = 0;
    init();
    scanf("%d", &T);
    while(T--)
    {
        LL a, b;
        scanf("%lld %lld", &a, &b);
        printf("Case %d: %lld\n",++ca, Lucas(a, b, mod));
    }
    return 0;
}

扩展Lucas, 时间复杂度PlogP

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=11;
ll p;
ll n,m;
static ll w[MAXN];
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    ll res=exgcd(b,a%b,x,y),t;
    t=x;
    x=y;
    y=t-a/b*y;
    return res;
}
ll power(ll a,ll b,ll mod)
{
    ll sm;
    for(sm=1; b; b>>=1,a=a*a%mod)if(b&1)
            sm=sm*a%mod;
    return sm;
}

ll fac(ll n,ll pi,ll pk)
{
    if(!n)return 1;
    ll res=1;
    for(register ll i=2; i<=pk; ++i)
        if(i%pi)(res*=i)%=pk;
    res=power(res,n/pk,pk);
    for(register ll i=2; i<=n%pk; ++i)
        if(i%pi)(res*=i)%=pk;
    return res*fac(n/pi,pi,pk)%pk;
}

ll inv(ll n,ll mod)
{
    ll x,y;
    exgcd(n,mod,x,y);
    return (x+=mod)>mod?x-mod:x;
}

ll CRT(ll b,ll mod)
{
    return b*inv(p/mod,mod)%p*(p/mod)%p;
}

ll C(ll n,ll m,ll pi,ll pk)
{
    ll up=fac(n,pi,pk),d1=fac(m,pi,pk),d2=fac(n-m,pi,pk);
    ll k=0;
    for(register ll i=n; i; i/=pi)k+=i/pi;
    for(register ll i=m; i; i/=pi)k-=i/pi;
    for(register ll i=n-m; i; i/=pi)k-=i/pi;
    return up*inv(d1,pk)%pk*inv(d2,pk)%pk*power(pi,k,pk)%pk;
}

ll exlucus(ll n,ll m)
{
    ll res=0,tmp=p,pk;
    static int lim=sqrt(p)+5;
    for(register int i=2; i<=lim; ++i)if(tmp%i==0)
        {
            pk=1;
            while(tmp%i==0)pk*=i,tmp/=i;
            (res+=CRT(C(n,m,i,pk),pk))%=p;
        }
    if(tmp>1)(res+=CRT(C(n,m,tmp,tmp),tmp))%=p;
    return res;
}

int main()
{
    scanf("%lld %lld%d",&n,&m,&p);
    printf("%d\n",exlucus(n,m));
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值