算法基础课2021-8-24

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20000,M=2000000;
int h[N], e[M], ne[M], idx;
void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int match[N],st[N];
bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j]))
            {
                match[j] = x;
                return true;
            }
        }
    }
    
    return false;
}
int main(){
    int n1,n2,m;
    cin>>n1>>n2>>m;
    memset(h, -1, sizeof h);
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    int ans=0;
    for(int i=1;i<=n1;i++){
        memset(st, 0, sizeof st);
        if(find(i))ans++;
    }
    cout<<ans;
    
}


#include<bits/stdc++.h>
using namespace std;
const int N = 200,M=20000;
int h[N], e[M], ne[M], idx;
int match[N],st[N];
int n,m;
void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool find(int x){
    for(int i=h[x];~i;i=ne[i]){
        int j=e[i];
        if(st[j])continue;
        st[j]=1;
        if(match[j]==0||find(match[j])){
            match[j]=x;
            return 1;
        }
    }
    return 0;
}
int main(){
    cin>>m>>n;
    memset(h, -1, sizeof h);
    while(1){
        int a,b;
        cin>>a>>b;
        if(a==-1)break;
        add(b,a);
    }
    int ans=0;
    for(int i=m+1;i<=n;i++){
        memset(st, 0, sizeof st);
        if(find(i))ans++;
    }
    printf("%d\n",ans);
    for(int i=1;i<=m;i++)if(match[i])printf("%d %d\n",i,match[i]);
}

01背包

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m;
int a,b;
int dp[1001];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a>>b;
        for(int j=m;j>=a;j--)dp[j]=max(dp[j],dp[j-a]+b);
    }
    cout<<dp[m];
}

完全背包

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m;
int dp[1001];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int v,w;
        cin>>v>>w;
        for(int j=v;j<=m;j++)dp[j]=max(dp[j],dp[j-v]+w);
    }
    cout<<dp[m];
}

多重背包

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m;
int v[1000],w[1000],s[1000];
int dp[1000][1000];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
                dp[i][j]=max(dp[i][j] , dp[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout<<dp[n][m];
}

二进制优化
(就是把多重背包问题转化成01背包
(优化第三层,不用一个个去枚举

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20001;
int v[N],w[N],n,m,dp[N],cnt;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;
        while(k<=s){
            cnt++;
            v[cnt]=k*a;
            w[cnt]=k*b;
            s-=k;
            k*=2;
        }
        if(s>0){
            cnt++;
            v[cnt]=s*a;
            w[cnt]=s*b;
        }
    }
    //转换成了01背包问题(正序遍历会出错,要逆序)
    for(int i=1;i<=cnt;i++){
        for(int j=m;j>=v[i];j--){
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m];
}

分组背包

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105;
int v[N][N],w[N][N],s[N],n,m,dp[N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=1;j<=s[i];j++)
        cin>>v[i][j]>>w[i][j];
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=0;j--){
            for(int k=1;k<=s[i];k++){
                if(j>=v[i][k])dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
            }
        }
    }
    cout<<dp[m];
}

最长上升子序列解题报告
给定一个长度为N的数列(w[N]),求数值严格单调递增的子序列的长度最长是多少。

样例
输入格式
第一行包含整数N。

第二行包含N个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度。

数据范围
1 ≤ N ≤ 1000,
−1e9 ≤ 数列中的数 ≤ 1e9

输入样例:

7
3 1 2 1 8 5 6
输出样例:

4
算法1
(动态规划) O(n2)O(n2)
状态表示:f[i]表示从第一个数字开始算,以w[i]结尾的最大的上升序列。(以w[i]结尾的所有上升序列中属性为最大值的那一个)

状态计算(集合划分):j∈(0,1,2,…,i-1), 在w[i] > w[j]时,
f[i] = max(f[i], f[j] + 1)。
有一个边界,若前面没有比i小的,f[i]为1(自己为结尾)。

最后在找f[i]的最大值。

时间复杂度
O(n2)O(n2) 状态数(nn) * 转移数(nn)
C++ 代码

#include <iostream>

using namespace std;

const int N = 1010;

int n;
int w[N], f[N];

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> w[i];

    int mx = 1;    // 找出所计算的f[i]之中的最大值,边算边找
    for (int i = 0; i < n; i++) {
        f[i] = 1;    // 设f[i]默认为1,找不到前面数字小于自己的时候就为1
        for (int j = 0; j < i; j++) {
            if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1);    // 前一个小于自己的数结尾的最大上升子序列加上自己,即+1
        }
        mx = max(mx, f[i]);
    }

    cout << mx << endl;
    return 0;
}

算法2
(动态规划 + 二分) O(nlogn)O(nlogn)
状态表示:f[i]表示长度为i的最长上升子序列,末尾最小的数字。(长度为i的最长上升子序列所有结尾中,结尾最小min的) 即长度为i的子序列末尾最小元素是什么。

状态计算:对于每一个w[i], 如果大于f【cnt-1】(下标从0开始,cnt长度的最长上升子序列,末尾最小的数字),那就cnt+1,使得最长上升序列长度+1,当前末尾最小元素为w[i]。 若w[i]小于等于f[cnt-1],说明不会更新当前的长度,但之前末尾的最小元素要发生变化,找到第一个 大于或等于 (这里不能是大于) w[i],更新以那时候末尾的最小元素。

f[i]一定以一个单调递增的数组,所以可以用二分法来找第一个大于或等于w[i]的数字。

时间复杂度
O(nlogn)O(nlogn) 状态数(nn) * 转移数(lognlogn)
C++ 代码

#include <iostream>

using namespace std;

const int N = 1010;
int n, cnt;
int w[N], f[N];

int main() {
    cin >> n;
    for (int i = 0 ; i < n; i++) cin >> w[i];

    f[cnt++] = w[0];
    for (int i = 1; i < n; i++) {
        if (w[i] > f[cnt-1]) f[cnt++] = w[i];
        else {
            int l = 0, r = cnt - 1;
            while (l < r) {
                int mid = (l + r) >> 1;
                if (f[mid] >= w[i]) r = mid;
                else l = mid + 1;
            }
            f[r] = w[i];
        }
    }
    cout << cnt << endl;
    return 0;
}

作者:VictorWu
链接:https://www.acwing.com/solution/content/4807/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

还有个用堆栈模拟和lower_bound()更简单更牛逼

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(void) {
    int n; cin >> n;
    vector<int>arr(n);
    for (int i = 0; i < n; ++i)cin >> arr[i];

    vector<int>stk;//模拟堆栈
    stk.push_back(arr[0]);

    for (int i = 1; i < n; ++i) {
        if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈
            stk.push_back(arr[i]);
        else//替换掉第一个大于或者等于这个数字的那个数
            *lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];
    }
    cout << stk.size() << endl;
    return 0;
}
/*
例 n: 7
arr : 3 1 2 1 8 5 6

stk : 3

1 比 3 小
stk : 1

2 比 1 大
stk : 1 2

1 比 2 小
stk : 1 2

8 比 2 大
stk : 1 2 8

5 比 8 小
stk : 1 2 5

6 比 5 大
stk : 1 2 5 6

stk 的长度就是最长递增子序列的长度

*/

作者:233
链接:https://www.acwing.com/solution/content/3783/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述


最长公共子序列

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
char a[N],b[N];
int dp[N][N];
int n,m;
signed main(){
    cin>>n>>m>>a+1>>b+1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i]==b[j])dp[i][j]=dp[i-1][j-1]+1;
            else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    }
    cout<<dp[n][m];
}

给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:

删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
char a[N],b[N];
int dp[N][N];
//dp[i][j]表示a的前i个变成b的前j个所需的最少操作次数
int n,m;
int minn(int a,int b,int c){
    return min(a,min(b,c));
}
signed main(){
    cin>>n>>a+1>>m>>b+1;
    //入口
    for(int i=0;i<=n;i++)dp[i][0]=i;//全删除所需的操作次数
    for(int j=0;j<=m;j++)dp[0][j]=j;//全插入所需的操作次数
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i]==b[j])dp[i][j]=dp[i-1][j-1];
            else dp[i][j]=minn(dp[i-1][j-1] , dp[i-1][j] , dp[i][j-1])+1;
                                //替换         插入         删除
        }
    }
    //出口
    cout<<dp[n][m];
}

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
char a[N],b[N];
int dp[N][N];
char c[N][11];
int k;
//dp[i][j]表示a的前i个变成b的前j个所需的最少操作次数
int n,m;
int minn(int a,int b,int c){
    return min(a,min(b,c));
}
int cal(char *a,char*b){
    int n=strlen(a+1);
    int m=strlen(b+1);
    //cout<<n<<m<<endl;
    for(int i=0;i<=n;i++)dp[i][0]=i;//全删除所需的操作次数
    for(int j=0;j<=m;j++)dp[0][j]=j;//全插入所需的操作次数
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i]==b[j])dp[i][j]=dp[i-1][j-1];
            else dp[i][j]=minn(dp[i-1][j-1] , dp[i-1][j] , dp[i][j-1])+1;
                                //替换         插入         删除
        }
    }
    return dp[n][m];
}
int solve(){
    cin>>a+1>>k;
    int ans=0;
    for(int i=1;i<=n;i++)
        if(cal(a,c[i])<=k)ans++;
        
    cout<<ans<<endl;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>c[i]+1;
    while(m--)solve();
    
}

石子合并

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 330;
int a[N],s[N],f[N][N],n;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        s[i]=s[i-1]+a[i];
    }
    memset(f,0x3f,sizeof f);
    for(int i=0;i<=n;i++)f[i][i]=0;
    
    for(int len=2;len<=n;len++){
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            for(int k=l;k<r;k++){
                f[l][r]=min(f[l][r] , f[l][k]+f[k+1][r]+s[r]-s[l-1]);
            }
        }
    }
    cout<<f[1][n];
}

整数划分
完全背包解法
状态表示:

f[i][j]表示只从1~i中选,且总和等于j的方案数

状态转移方程:
f[i][j] = f[i - 1][j] + f[i][j - i];

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

int n;
int f[N];

int main()
{
    cin >> n;

    f[0] = 1;
    for (int i = 1; i <= n; i ++ )
        for (int j = i; j <= n; j ++ )
            f[j] = (f[j] + f[j - i]) % mod;

    cout << f[n] << endl;

    return 0;
}
其他算法
状态表示:
f[i][j]表示总和为i,总个数为j的方案数

状态转移方程:
f[i][j] = f[i - 1][j - 1] + f[i - j][j];

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

int n;
int f[N][N];

int main()
{
    cin >> n;

    f[1][1] = 1;
    for (int i = 2; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ )
            f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;

    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = (res + f[n][i]) % mod;

    cout << res << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/62496/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。

例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3 次等等…

输入格式
输入包含多组测试数据。

每组测试数据占一行,包含两个整数 a 和 b。

当读入一行为 0 0 时,表示输入终止,且该行不作处理。

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 10;

/*

001~abc-1, 999

abc
    1. num[i] < x, 0
    2. num[i] == x, 0~efg
    3. num[i] > x, 0~999

*/

int get(vector<int> num, int l, int r)
{
    int res = 0;
    for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
    return res;
}

int power10(int x)
{
    int res = 1;
    while (x -- ) res *= 10;
    return res;
}

int count(int n, int x)
{
    if (!n) return 0;

    vector<int> num;
    while (n)
    {
        num.push_back(n % 10);
        n /= 10;
    }
    n = num.size();

    int res = 0;
    for (int i = n - 1 - !x; i >= 0; i -- )
    {
        if (i < n - 1)
        {
            res += get(num, n - 1, i + 1) * power10(i);
            if (!x) res -= power10(i);
        }

        if (num[i] == x) res += get(num, i - 1, 0) + 1;
        else if (num[i] > x) res += power10(i);
    }

    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);

        for (int i = 0; i <= 9; i ++ )
            cout << count(b, i) - count(a - 1, i) << ' ';
        cout << endl;
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/64211/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

蒙德里安的梦想
求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

如下图所示:

2411_1.jpg

输入格式
输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

#include<bits/stdc++.h>
using namespace std;
const int N = 12,M=1<<N;
long long dp[N][M];
bool st[M];
int n,m;
signed main(){
    while(cin>>n>>m,n||m){
        for(int i=0;i<1<<n;i++){
            st[i]=1;
            int cnt=0;
            for(int j=0;j<n;j++){
                if(i>>j&1){
                    if(cnt&1){st[i]=0;break;}
                    cnt=0;
                }
                else cnt++;
            }
            if(cnt&1)st[i]=0;
        }
        memset(dp,0,sizeof dp);
        dp[0][0]=1;
        for(int i=1;i<=m;i++){
            for(int j=0;j<1<<n;j++){
                for(int k=0;k<1<<n;k++){
                    if((j&k)==0&&st[j|k])
                    dp[i][j]+=dp[i-1][k];
                }
            }
        }
        cout<<dp[m][0]<<endl;
    }
}

给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20,M=1<<20;
int w[N][N];
int dp[M][N];
int n;
signed main(){
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)cin>>w[i][j];
        
    memset(dp,0x3f,sizeof dp);
    dp[1][0]=0;
    for(int i=0;i<1<<n;i++){
        for(int j=0;j<n;j++){
            if(i>>j&1)
                for(int k=0;k<n;k++){
                    if(i>>k&1)
                    dp[i][j]=min(dp[i][j] , dp[i-(1<<j)][k]+w[k][j]);
                }
        }
    }
    cout<<dp[(1<<n)-1][n-1];
}

在这里插入图片描述


#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 7000,M=N;
int a[N],n;
int dp[N][2];
int h[N], e[M], ne[M], idx;
void add(int a, int b)  // 添加一条边a->b,边权为c
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int rudu[N];

void dfs(int u){
    dp[u][1]=a[u];
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        dfs(j);
        dp[u][1]+=dp[j][0];
        dp[u][0]+=max(dp[j][0],dp[j][1]);
    }
}

signed main(){
    memset(h, -1, sizeof h);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<n;i++){
        int L,K;
        cin>>L>>K;
        add(K,L);
        rudu[L]++;
    }
    int root=-1;
    for(int i=1;i<=n;i++){
        if(rudu[i]==0){
            root=i;
            break;
        }
    }
    dfs(root);
    cout<<max(dp[root][1],dp[root][0]);
}


滑雪(记忆化搜索

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int a[301][301],dp[301][301];
int n,m;
int xx[]={1,0,-1,0};
int yy[]={0,1,0,-1};
int dfs(int x,int y){
    int &t=dp[x][y];
    if(t)return t;
    for(int i=0;i<4;i++){
        int dx=x+xx[i];
        int dy=y+yy[i];
        if(a[dx][dy]<=a[x][y])continue;
        t=max(t,dfs(dx,dy)+1);
    }
    return t;
}
int main(){
    cin>>n>>m;
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)ans=max(ans,dfs(i,j));
    }
    cout<<ans+1;
}


试除法
时间复杂度O(T*根号n)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,t;

bool is_prime(int x){
    if(x<2)return 0;
    for(int i=2;i<=n/i;i++){
        if(n%i==0)return 0;
    }
    return 1;
}

int main(){
    cin>>t;
    while(t--){
        cin>>n;
        if(is_prime(n))puts("Yes");
        else puts("No");
    }
}

分解质因数

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int T,n;
void divid(int x){
    for(int i=2;i<=x/i;i++){
        if(x%i==0){
            int s=0;
            while(x%i==0)x/=i,s++;
            cout<<i<<" "<<s<<endl;
        }
    }        
    if(x>1)cout<<x<<" "<<1<<endl;

}
signed main(){
    cin>>T;
    while(T--){
        cin>>n;
        divid(n);
        cout<<endl;
    }
}

给定一个正整数 n,请你求出 1∼n 中质数的个数。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6;
int n;
int st[N],primes[N],cnt;
void get_primes(int n)  // 线性筛质数
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int main(){
    cin>>n;
    get_primes(n);
    cout<<cnt;
}

试除法求约数(我用优先队列来做省去了用vector做要sort的麻烦

#include<bits/stdc++.h>
using namespace std;
int x;
priority_queue<int,vector<int>,greater<int>> get_divisors(int x){
    priority_queue<int,vector<int>,greater<int>>q;
    int i=1;
    for(;i<x/i;i++){
        if(x%i==0)q.push(i),q.push(x/i);
    }
    if(i*i==x)q.push(i);
    return q;
}

int main(){
    int T;
    cin>>T;
    while(T--){
        cin>>x;
        auto q=get_divisors(x);
        while(!q.empty()){
            cout<<q.top()<<" ";
            q.pop();
        }
        puts("");
    }
}

给定 n 个正整数 ai,请你求出每个数的欧拉函数。

欧拉函数的定义
1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。
若在算数基本定理中,N=pa11pa22…pamm,则:
ϕ(N) = N×p1−1p1×p2−1p2×…×pm−1pm
输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个正整数 ai。

输出格式
输出共 n 行,每行输出一个正整数 ai 的欧拉函数。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int T,n;
int phi(int x){
    int ans=x;
    for(int i=2;i<=x/i;i++){
        if(x%i==0){
            ans=ans/i*(i-1);
            while(x%i==0)x/=i;
        }
    }
    if(x>1)ans=ans/x*(x-1);
    return ans;
}
int main(){
    cin>>T;
    while(T--){
        cin>>n;
        cout<<phi(n)<<endl;
    }
}

求约数个数

#include <unordered_map>
#include <iostream>
#include <cstring>
#include <algorithm>
#define hash h
using namespace std;
const int mod=1e9+7;
int x;
int T;
unordered_map<int,int>hash;
int main(){
    cin>>T;
    while(T--){
        cin>>x;
        for(int i=2;i<=x/i;i++){
            while(x%i==0){
                x/=i;
                hash[i]++;
            }
        }
        if(x>1)hash[x]++;
    }
    long long ans=1;
    for(auto t:hash)ans*=(t.second+1);
    cout<<ans;
}

在这里插入图片描述


约数之和
算法分析
基本思想:
如果 N=p1c1∗p2c2∗…∗pkckN=p1c1∗p2c2∗…∗pkck
约数个数:(c1+1)∗(c2+1)∗…∗(ck+1)(c1+1)∗(c2+1)∗…∗(ck+1)
约数之和: (p10+p11+…+p1c1)∗…∗(pk0+pk1+…+pkck)(p10+p11+…+p1c1)∗…∗(pk0+pk1+…+pkck)
while (b – ) t = (t * a + 1) % mod;

t=t∗p+1t=t∗p+1
t=1t=1
t=p+1t=p+1
t=p2+p+1t=p2+p+1
……
t=pb+pb−1+…+1
(当然你也可以用快速幂提速

#include<bits/stdc++.h>
#define hash h
using namespace std;
int T,n;
const int mod=1e9+7;
long long ans=1;
signed main(){
    cin>>T;
    unordered_map<int,int>hash;
    while(T--){
        cin>>n;
        for(int i=2;i<=n/i;i++){
            while(n%i==0){
                n/=i;
                hash[i]++;
            }
        }
        if(n>1)hash[n]++;
    }
    for(auto t:hash){
        int a=t.first;
        int b=t.second;
        long long res=1;
        while(b--)res=(res*a+1)%mod;
        ans=(ans*res)%mod;
    }
    cout<<ans;
}

给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1000010;

int primes[N], cnt;
int phi[N];
bool st[N];

void get_eulers(int n)
{
    phi[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        if (!st[i])
        {
            primes[cnt++] = i;
            phi[i] = i - 1; 
        }
        for (int j = 0; primes[j] <= n / i; j++)
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0)
            {
                phi[primes[j] * i] = phi[i] * primes[j]; 
                break;
            }
            phi[primes[j] * i] = phi[i] * (primes[j] - 1);
        }
    }
}

int main()
{
    int n;
    cin >> n;

    get_eulers(n);

    LL res = 0;
    for (int i = 1; i <= n; i++) res += phi[i];
    printf("%lld\n", res);

    return 0;
}

作者:番茄酱
链接:https://www.acwing.com/solution/content/3952/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
int qmi(int a,int b,int q){
    int ans=1;
    while(b){
        if(b&1)ans=(ans*a)%q;
        a=(a*a)%q;
        b/=2;
    }
    return ans;
}
signed main(){
    int t;
    cin>>t;
    while(t--){
        int a,b,p;
        cin>>a>>b>>p;
        cout<<qmi(a,b,p)<<endl;
    }
}

求逆元

当n为质数时,可以用快速幂求逆元:
a / b ≡ a * x (mod n)
两边同乘b可得 a ≡ a * b * x (mod n)1 ≡ b * x (mod n)
同 b * x ≡ 1 (mod n)
由费马小定理可知,当n为质数时
b ^ (n - 1)1 (mod n)
拆一个b出来可得 b * b ^ (n - 2)1 (mod n)
故当n为质数时,b的乘法逆元 x = b ^ (n - 2)

当n不是质数时,可以用扩展欧几里得算法求逆元:
a有逆元的充要条件是a与p互质,所以gcd(a, p) = 1
假设a的逆元为x,那么有a * x ≡ 1 (mod p)
等价:ax + py = 1
exgcd(a, p, x, y)

快速幂求逆元
#include <iostream>
using namespace std;
typedef long long LL;

LL qmi(int a, int b, int p)
{
    LL res = 1;
    while(b){
        if(b & 1) res = res * a % p;
        a = (LL)a * a % p;
        b >>= 1;
    }
    return res;
}

int main()
{
    int n; cin >> n;
    while(n --){
        int a, p;
        cin >> a >> p;
        if(a % p == 0) puts("impossible");
        else cout << qmi(a, p - 2, p) << endl;
    }
    return 0;
}
扩展欧几里得算法求逆元
#include <iostream>
using namespace std;
typedef long long LL;
int n;

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


int main()
{
    cin >> n;
    while (n --)
    {
        int a, p, x, y;
        // if (a < p) swap(a, p);
        cin >>  a >> p;
        int d = exgcd(a, p, x, y);
        if (d == 1) cout << ((LL)x + p) % p << endl;//保证x是正数
        else puts("impossible");

    }
    return 0;
}


作者:Hz
链接:https://www.acwing.com/solution/content/3054/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai×xi≡bi(modmi),如果无解则输出 impossible。

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;


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


int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, b, m;
        scanf("%d%d%d", &a, &b, &m);

        int x, y;
        int d = exgcd(a, m, x, y);
        if (b % d) puts("impossible");//如果b与公约数gcd不互质就无解
        else printf("%d\n", (LL)b / d * x % m);
    }

    return 0;-
}


扩展中国剩余定理
给定 2n 个整数 a1,a2,…,an 和 m1,m2,…,mn,求一个最小的非负整数 x,满足 ∀i∈[1,n],x≡mi(mod ai)。

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
int n;
LL exgcd(LL a, LL b, LL &x, LL &y){
    if(b == 0){
        x = 1, y = 0;
        return a;
    }

    LL d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}
LL inline mod(LL a, LL b){
    return ((a % b) + b) % b;
}
int main(){
    scanf("%d", &n);
    LL a1, m1;
    scanf("%lld%lld", &a1, &m1);
    for(int i = 1; i < n; i++){
        LL a2, m2, k1, k2;
        scanf("%lld%lld", &a2, &m2);
        LL d = exgcd(a1, -a2, k1, k2);
        if((m2 - m1) % d){ puts("-1"); return 0; }
        k1 = mod(k1 * (m2 - m1) / d, abs(a2 / d));
        m1 = k1 * a1 + m1;
        a1 = abs(a1 / d * a2);
    }
    printf("%lld\n", m1);
    return 0;
}

作者:墨染空
链接:https://www.acwing.com/solution/content/3539/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

高斯消元解线性方程组

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 110;
const double eps = 1e-6;

int n;
double a[N][N];


int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;

        if (fabs(a[t][c]) < eps) continue;

        for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];

        for (int i = r + 1; i < n; i ++ )
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];

        r ++ ;
    }

    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)
                return 2;
        return 1;
    }

    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[j][n] * a[i][j];

    return 0;
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n + 1; j ++ )
            cin >> a[i][j];

    int t = gauss();

    if (t == 0)
    {
        for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);
    }
    else if (t == 1) puts("Infinite group solutions");
    else puts("No solution");

    return 0;
}


高斯消元解异或线性方程组
在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;


int n;
int a[N][N];


int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )
            if (a[i][c])
                t = i;

        if (!a[t][c]) continue;

        for (int i = c; i <= n; i ++ ) swap(a[r][i], a[t][i]);
        for (int i = r + 1; i < n; i ++ )
            if (a[i][c])
                for (int j = n; j >= c; j -- )
                    a[i][j] ^= a[r][j];

        r ++ ;
    }

    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (a[i][n])
                return 2;
        return 1;
    }

    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] ^= a[i][j] * a[j][n];

    return 0;
}


int main()
{
    cin >> n;

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n + 1; j ++ )
            cin >> a[i][j];

    int t = gauss();

    if (t == 0)
    {
        for (int i = 0; i < n; i ++ ) cout << a[i][n] << endl;
    }
    else if (t == 1) puts("Multiple sets of solutions");
    else puts("No solution");

    return 0;
}


求组合数

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int mod=1e9+7;
int c[2001][2001];
int a,b;

void init(){
    for(int i=0;i<=2000;i++){
        for(int j=0;j<=i;j++){
            if(!j)c[i][j]=1;
            else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }
    }
}

int main(){
    init();
    int T;
    cin>>T;
    while(T--){
        cin>>a>>b;
        cout<<c[a][b]<<endl;
    }
}

组合数Ⅱ
当给出的a,b数据范围扩大时
1≤n≤10000,
1≤b≤a≤105
O(n) 时间复杂度的极速解法

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

vector<int> getRangeModularMultInv(int n, int p) {
    vector<int> inv(n + 1);
    inv[1] = 1;
    for (int i = 2; i <= n; ++i)
        inv[i] = (LL)(p - p / i) * inv[p % i] % p;
    return inv;
}

const int N = 100010, MOD = 1e9 + 7;
int fac[N], invFac[N];
void calcFac(int n, int m) {
    auto invI = getRangeModularMultInv(n, m);
    fac[0] = 1; invFac[0] = 1;
    for (int i = 1; i < N; i++) {
        fac[i] = (LL)fac[i - 1] * i % m;
        invFac[i] = (LL)invFac[i - 1] * invI[i] % m;
    }
}

int getC(int a, int b, int m) {
    return (LL)fac[a] * invFac[a - b] % m * invFac[b] % m;
}

int main() {
    int q; cin >> q;
    calcFac(N, MOD);
    while (q--) {
        int a, b; cin >> a >> b;
        cout << getC(a, b, MOD) << endl;
    }
    return 0;
}

作者:zan
链接:https://www.acwing.com/solution/content/22453/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
快速幂求组合数 O(a∗log(mod))O(a∗log(mod))

#include<iostream>
using namespace std;
const int mod=1e9+7,N=1e5+10;
typedef long long LL;
long long fac[N],infac[N];
int quick_pow(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}
int main()
{
    int n;
    fac[0]=infac[0]=1;
    for(int i=1;i<=1e5;i++)
    {
        fac[i]=fac[i-1]*i%mod;
        infac[i]=(LL)infac[i - 1] * quick_pow(i,mod-2,mod)%mod;
    }
    cin>>n;
    while(n--)
    {
        int a,b;
        cin>>a>>b;
        cout<<(LL)fac[a] * infac[b] % mod * infac[a - b] % mod<<endl;
        //Cab=a的阶乘除以(b的阶乘*(a-b)的阶乘)
        //等价于a!*b逆元*(a-b)逆元
    }
}

作者:码
链接:https://www.acwing.com/solution/content/22076/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

AcWing 887. 求组合数 III(lucas定理)

题解

#include<iostream>
#include<algorithm>

using namespace std;

typedef long long LL;

int qmi(int a,int k,int p)
{
    int res = 1;
    while(k)
    {
        if(k&1)res = (LL)res*a%p;
        a = (LL)a*a%p;
        k>>=1;
    }
    return res;
}

int C(int a,int b,int p)//自变量类型int
{
    if(b>a)return 0;//漏了边界条件
    int res = 1;
    // a!/(b!(a-b)!) = (a-b+1)*...*a / b! 分子有b项
    for(int i=1,j=a;i<=b;i++,j--)//i<=b而不是<
    {
        res = (LL)res*j%p;
        res = (LL)res*qmi(i,p-2,p)%p;
    }
    return res;
}
//对公式敲
int lucas(LL a,LL b,int p)
{
    if(a<p && b<p)return C(a,b,p);//lucas递归终点是C_{bk}^{ak}
    return (LL)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;//a%p后肯定是<p的,所以可以用C(),但a/p后不一定<p 所以用lucas继续递归
}

int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        LL a,b;
        int p;
        cin >> a >> b >> p;
        cout << lucas(a,b,p) << endl;
    }
    return 0;
}

作者:仅存老实人
链接:https://www.acwing.com/solution/content/26553/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

y
题解有个人对C(a,b,p)函数做了简单的优化,板子比yxc快了10倍

#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 100010, mod = 1e9+7;

int fact[N], infact[N];

int qmi(int a, int k, int p)
{
    int res = 1;
    while(k)
    {
        if(k & 1)
            res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

int C(int a, int b, int p)
{
    if(a < b)return 0;
    int down = 1, up = 1;
    for(int i=a, j=1; j<=b; i--, j++)
    {
        up = (LL)up * i % p;
        down = (LL)down * j % p;
    }

    return (LL)up * qmi(down, p-2, p) % p;
}

int lucas(LL a, LL b, int p)
{
    if(a<p && b<p)return C(a, b, p);
    else return (LL)C(a%p, b%p, p)*lucas(a/p, b/p, p)%p;
}

int main()
 {

    int n;
    scanf("%d", &n);
    while(n--)
    {
        LL a, b, p;
        cin>>a>>b;
        cin>>p;
        cout<<lucas(a, b, p)<<endl;
     }

    return 0;
 }



求组合数Ⅳ
不mod p要用高精度计算出结果

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;


const int N = 5010;

int primes[N], cnt;
int sum[N];
bool st[N];


void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}


int get(int n, int p)
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}


vector<int> mul(vector<int> a, int b)
{
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }
    return c;
}


int main()
{
    int a, b;
    cin >> a >> b;

    get_primes(a);

    for (int i = 0; i < cnt; i ++ )
    {
        int p = primes[i];
        sum[i] = get(a, p) - get(a - b, p) - get(b, p);
    }

    vector<int> res;
    res.push_back(1);

    for (int i = 0; i < cnt; i ++ )
        for (int j = 0; j < sum[i]; j ++ )
            res = mul(res, primes[i]);

    for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
    puts("");

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/53401/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

满足条件的01序列
把问题放到坐标系中去解决

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;


int qmi(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}


int C(int a, int b, int p)
{
    if (b > a) return 0;

    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (LL)res * j % p;
        res = (LL)res * qmi(i, p - 2, p) % p;
    }
    return res;
}


int lucas(LL a, LL b, int p)
{
    if (a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}


int main()
{
    int n;
    cin >> n;

    while (n -- )
    {
        LL a, b;
        int p;
        cin >> a >> b >> p;
        cout << lucas(a, b, p) << endl;
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/53399/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

火车进出栈问题(卡特拉数经典应用
一列火车n节车厢,依次编号为1,2,3,…,n1,2,3,…,n。

每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种。
这道题也是求卡特兰数,不同的是这道题需要高精度和卡时

#include <stdio.h>

const int N = 11311;
const int base = 1e9;  // 压 9 位
const int basebit = 9;

int n;
int sum[N];             // 存筛出的质数及其次数的结果
long long res[4500];    // 存答案,res[0] 存 res 的位数
int primes[N], cnt;     // 存筛的质数
bool st[120001];        // 筛质数用的 bool 数组

inline void init()      // 线性筛质数 + 求分解质因数的结果
{
    for (register int i = 2; i <= n << 1; i ++ )
    {
        if (!st[i])     // 如果 st[i] == false,说明该数 i 是质数
        {
            primes[ ++ cnt] = i; // 先把它存进 primes
            for (register int j = (n << 1) / i; j; j /= i) // 加上 (2n)! 含有 i 的数量。
                sum[cnt] += j;
            for (register int j = n / i; j; j /= i) // 减去 (n!)^2 含有 i 的数量。
                sum[cnt] -= j << 1; // (n!)^2 含有 i 的数量即两倍的 n! 所含有的 i 的数量,所以只用处理 n!,然后减去其所含数量两倍即可
            for (register int j = n + 1; j % i == 0; j /= i) // 减去 n + 1 含有 i 的数量
                sum[cnt] -- ;
        }
        for (register int j = 1; primes[j] <= (n << 1) / i; j ++ ) // 线性筛的板子
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break ;
        }
    }
}

inline int qmi(int a, int b) // 快速幂(其实并没有卵用。。大部分调用都会进到那个 if (b == 1) 里面去。。所以加了个特判,效率会更高一点)
{
    if (b == 1) return a;
    int res = 1;
    for (; b; a *= a, b >>= 1)
        if (b & 1) res *= a;
    return res;
}

inline void multi(int b)     // 将 res 乘 b
{
    register long long t = 0;
    for (register int i = 1; i <= res[0]; i ++ )
    {
        res[i] = res[i] * b + t;
        t = res[i] / base;
        res[i] %= base;
    }
    while (t) res[ ++ res[0]] = t % base, t /= base;
}

void print(int x, int i = basebit) // 快速输出 9 位
{
    if (!i) return ;
    print(x / 10, i - 1);
    putchar(x % 10 ^ 48);
}

int main()
{
    scanf("%d",&n);
    init();              // 初始化 primes + 初始化 sum
    res[0] = res[1] = 1; // 出初始化 res,res 的长度制为 1,res 的值制成 1
    for (register int i = 1; i <= cnt; i ++ ) // 枚举一遍分解出来的所有的质数
        if (sum[i]) multi(qmi(primes[i], sum[i]));
    printf("%lld", res[res[0]]);              // 第一位不用输出 9 位
    if (res[0] > 1)      // 如果 res 的位数大于一的话,那么输出后面的
        for (register int i = res[0] - 1; i; i -- )
            print(res[i]);
    return 0;
}

作者:垫底抽风
链接:https://www.acwing.com/solution/content/15603/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
import math

n = int(input())
A = math.factorial(2 * n)
B = math.factorial(n)
print(A // B // B // (n + 1))

在这里插入图片描述


给定一个整数 n 和 m 个不同的质数 p1,p2,…,pm。

请你求出 1∼n 中能被 p1,p2,…,pm 中的至少一个数整除的整数有多少个。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long 
int primes[16];
int ans=0;
signed main(){
    int n,m;
    cin>>n>>m;
    for(int i=0;i<m;i++)cin>>primes[i];
    
    //i从0开始是错的,从1开始才是对的
    //这也是为了保证容斥原理最少计算一个图
    for(int i=1;i<1<<m;i++){
        int t=1,cnt=0;
        for(int j=0;j<m;j++){
            if(i>>j&1){
                if(primes[j]*t>n){
                    t=-1;
                    break;
                }
                t*=primes[j];
                cnt++;
            }
        }
        if(t==-1)continue;
        
        if(cnt&1)ans+=n/t;
        else ans-=n/t;
    }
    cout<<ans;
}

中国剩余定理

#include<bits/stdc++.h>
using namespace std;
#define int long long
int mod(int a,int b){
    return (a%b+b)%b;
}
int exgcd(int a,int b,int &x,int &y){
    if(!b){
        x=1,y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
signed main(){
    int n;
    cin>>n;
    int a1,m1,k1;
    cin>>a1>>m1;
    for(int i=1;i<n;i++){
        int a2,m2,k2;
        cin>>a2>>m2;
        int d=exgcd(a1,-a2,k1,k2);
        if((m2-m1)%d){puts("-1");return 0;}
        k1=mod(k1*(m2-m1)/d , abs(a2/d));
        m1=m1+k1*a1;
        a1=abs(a1/d*a2);
    }
    cout<<m1;
    
}

若一个游戏满足:

由两名玩家交替行动
在游戏进行的任意时刻,可以执行的合法行动与轮到哪位玩家无关
不能行动的玩家判负
则称该游戏为一个公平组合游戏。

尼姆游戏(NIM)属于公平组合游戏,但常见的棋类游戏,比如围棋就不是公平组合游戏,因为围棋交战双方分别只能落黑子和白子,胜负判定也比较负责,不满足条件2和3。

题目描述
给定nn堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。

例如:有两堆石子,第一堆有2个,第二堆有3个,先手必胜。

操作步骤:

  1. 先手从第二堆拿走1个,此时第一堆和第二堆数目相同
  2. 无论后手怎么拿,先手都在另外一堆石子中取走相同数量的石子即可。

必胜状态和必败状态
在解决这个问题之前,先来了解两个名词:

必胜状态,先手进行某一个操作,留给后手是一个必败状态时,对于先手来说是一个必胜状态。即先手可以走到某一个必败状态。
必败状态,先手无论如何操作,留给后手都是一个必胜状态时,对于先手来说是一个必败状态。即先手走不到任何一个必败状态。
结论
假设nn堆石子,石子数目分别是a1,a2,…,ana1,a2,…,an,如果a1⊕a2⊕…⊕an≠0a1⊕a2⊕…⊕an≠0,先手必胜;否则先手必败。

  1. 如果先手面对的局面是a1⊕a2⊕…⊕an≠0a1⊕a2⊕…⊕an≠0,那么先手总可以通过拿走某一堆若干个石子,将局面变成a1⊕a2⊕…⊕an=0a1⊕a2⊕…⊕an=0。如此重复,最后一定是后手面临最终没有石子可拿的状态。先手必胜。
  2. 如果先手面对的局面是a1⊕a2⊕…⊕an=0a1⊕a2⊕…⊕an=0,那么无论先手怎么拿,都会将局面变成a1⊕a2⊕…⊕an≠0a1⊕a2⊕…⊕an≠0,那么后手总可以通过拿走某一堆若干个石子,将局面变成a1⊕a2⊕…⊕an=0a1⊕a2⊕…⊕an=0。如此重复,最后一定是先手面临最终没有石子可拿的状态。先手必败。
#include<bits/stdc++.h>
using namespace std;
int n;
signed main(){
    cin>>n;
    int ans=0;
    while(n--){
        int x;
        cin>>x;
        ans^=x;
    }
    if(ans==0)puts("No");
    else puts("Yes");
}

现在,有一个 n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i 级台阶上有 ai 个石子(i≥1)。

两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。

已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。
(核心就是:先手总是把奇数台阶异或为0的状态留给对面,即总是将必败态交给对面)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
signed main(){
    cin>>n;
    int ans=0;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        if(i&1)ans^=x;
    }
    if(ans)puts("Yes");
    else puts("No");
}

#include<bits/stdc++.h>
using namespace std;
const int N = 110,M=10010;
int n,m;
int s[N],SG[M];
int sg(int x){
    if(SG[x]!=-1)return SG[x];
    unordered_set<int>S;
    for(int i=0;i<m;i++){
        if(x>=s[i])S.insert(sg(x-s[i]));
    }
    for(int i=0;;i++){
        if(!S.count(i))return SG[x]=i;
    }
}
signed main(){
    memset(SG,-1,sizeof SG);
    cin>>m;
    for(int i=0;i<m;i++)cin>>s[i];
    cin>>n;
    int ans=0;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        ans^=sg(x);
    }
    if(ans)puts("Yes");
    else puts("No");
}

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
using namespace std;
const int N = 110;
int f[N];
int n;
int sg(int x){
    if(f[x]!=-1)return f[x];
    unordered_set<int>S;
    for(int i=0;i<x;i++){
        for(int j=0;j<=i;j++){
            S.insert(sg(i)^sg(j));
        }
    }
    //mex操作:找到集合里不存在的最小自然数
    for(int i=0;;i++){
        if(!S.count(i))return f[x]=i;
    }
}
signed main(){
    cin>>n;
    memset(f,-1,sizeof f);
    int ans=0;
    while(n--){
        int x;
        cin>>x;
        ans^=sg(x);
    }
    if(ans)puts("Yes");
    else puts("No");
}

区间分组

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
struct node{
    int l,r;
    bool operator<(const node&t){
        return l<t.l;
    }
}range[N];
signed main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++)cin>>range[i].l>>range[i].r;
    priority_queue<int,vector<int>,greater<int>>heap;
    sort(range,range+n);
    for(int i=0;i<n;i++){
        if(heap.empty()||range[i].l<=heap.top())heap.push(range[i].r);
        else{
            heap.pop();
            heap.push(range[i].r);
        }
    }
    cout<<heap.size();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值