算法模板-2022

目录:

    • 经典动态规划
    • 树和图
    • 字符串和字典树
    • 记忆化搜索
    • 排序及逆序对
    • 离散化
    • 树链剖分
    • 素数筛法:
    • 同余定理
    • 单调栈
    • 数学
    • LCA
    • 计算几何

经典动态规划

设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
输入格式
第一行为一个整数N,表示 N×N 的方格图。
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
行和列编号从 1 开始。
一行“0 0 0”表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
数据范围
N≤10
输入样例:
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
输出样例:
67

#include
using namespace std;
const int N = 20;
int dp[N][N][N],a[N][N];

int main(){
   
	
	int n;
	scanf("%d",&n);
	int r,c,num;
	while(scanf("%d%d%d",&r,&c,&num)){
   
		if(r==0&&c==0&&num==0)break;
		a[r][c]=num;
	}
	for(int k = 1;k <= n + n;k ++){
   
		for(int i1 = max(1, k-n); i1 <= k-1; i1++){
   
			for(int i2 = max(1, k-n); i2 <= k-1; i2++){
   
				int j1 = k - i1, j2 = k - i2;
				if(j1 >= 1 && j1 <=n && j2 >= 1 && j2 <= n){
   
					int t = a[i1][j1];
					if(i1 != i2)t += a[i2][j2];
					int &x = dp[k][i1][i2];
					x=max(x, dp[k-1][i1][i2]+t);
					x=max(x, dp[k-1][i1-1][i2]+t);
					x=max(x, dp[k-1][i1][i2-1]+t);
					x=max(x, dp[k-1][i1-1][i2-1]+t);
				}
			}
		}
	}
	printf("%d\n", dp[n+n][n][n]);
} 

最长上升子序列
五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入格式
第一行包含整数N,表示景点数量。
第二行包含N个整数,表示每个景点的海拔。
输出格式
输出一个整数,表示最多能浏览的景点数。
数据范围
2≤N≤1000
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4

#include
using namespace std;
const int N = 2000;
int a[N],dp1[N],dp2[N];
int main(){
   
	int ans=1,n;
	scanf("%d",&n);
	for(int i = 1;i <= n;i ++){
   
	    dp1[i]=1;
	    scanf("%d", &a[i]);  
	    for(int j = 1; j < i;j++){
   
	        if(a[i] > a[j]){
   
	            dp1[i] = max(dp1[i], dp1[j] + 1);
	        }
	    }
	}
	for(int i = n; i; i --){
   
	    dp2[i] = 1;
	    for(int j = n;j > i;j --){
   
	        if(a[i] > a[j]){
   
	            dp2[i]=max(dp2[i], dp2[j]+1);
	            ans=max(ans, dp2[i]);
	        }
	    }
	}
	for(int i = 1;i <= n;i ++)ans=max(ans,dp1[i] + dp2[i]-1);
	printf("%d\n",ans);
    return 0;
} 

01背包变式
宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。
小智也想收服其中的一些小精灵。
然而,野生的小精灵并不那么容易被收服。
对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。
当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。
当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。
如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。
请问,小智该如何选择收服哪些小精灵以达到他的目标呢?
输入格式
输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。

数据范围
0 0 0 输入样例1:
10 100 5
7 10
2 40
2 50
1 20
4 20
输出样例1:
3 30
输入样例2:
10 100 5
8 110
12 10
20 10
5 200
1 110
输出样例2:
0 100

#include
using namespace std;
const int N = 1500,INF=0x3f3f3f3f;
int a[N],b[N],dp[N][N];
int main(){
   
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k); 
    for(int i = 1;i <= k;i ++){
   
        scanf("%d%d", &a[i], &b[i]);
    }
    dp[0][0]=0;
    for(int i = 1;i <= k;i ++){
   
        for(int j = n;j >= a[i];j --){
   
            for(int k = m;k >= b[i];k --){
   
                dp[j][k] = max(dp[j][k], dp[j-a[i]][k-b[i]]+1);
            }
        }
    }
    int ans=-1,pre;
    for(int i = 0;i <= n;i ++){
   
        for(int j = 0;j < m;j ++){
   
            if(dp[i][j] > ans||(dp[i][j] == ans && j < pre)){
   
                ans=dp[i][j];
                pre=j;
            }
        }
    }
    printf("%d %d\n", ans, m-pre);
}

数字组合
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数,表示 A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。
数据范围
1≤N≤100,
1≤M≤10000,
1≤Ai≤1000,
答案保证在 int 范围内。
输入样例:
4 4
1 1 2 2
输出样例:
3

#include
using namespace std;
const int N = 10010;
int dp[N];
int main(){
   
    int n,m;
    scanf("%d%d",&n,&m);
    dp[0]=1;
    for(int i=1;i<=n;i++){
   
        int v;
        scanf("%d",&v);
        for(int j=m;j>=v;j--)dp[j]+=dp[j-v];
    }
    printf("%d\n",dp[m]);
}

多重背包
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。
期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
输入格式
第一行二个数n,m,其中n代表希望购买的奖品的种数,m表示拨款金额。
接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可)。
输出格式
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
数据范围
n≤500,m≤6000,
v≤100,w≤1000,s≤10
输入样例:
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
输出样例:
1040

#include
using namespace std;
const int N = 6015;
int dp[N];
vector<int>v;
int main(){
   
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
   
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        for(int j=m;j>=a;j--){
   
            for(int k=1;k<=c&&j>=k*a;k++){
   
                dp[j]=max(dp[j],dp[j-k*a]+k*b);
            }
        }
    }
    printf("%d\n",dp[m]);
}

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N 元钱就行”。
今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 N 元。
于是,他把每件物品规定了一个重要度,分为 5 等:用整数 1~5 表示,第 5 等最重要。
他还从因特网上查到了每件物品的价格(都是整数元)。
他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。?
设第 j 件物品的价格为 v[j],重要度为 w[j],共选中了 k 件物品,编号依次为 j1,j2,…,jk,则所求的总和为:?
v[j1]×w[j1]+v[j2]×w[j2]+…+v[jk]×w[jk]
请你帮助金明设计一个满足要求的购物单。
输入格式
输入文件的第 1 行,为两个正整数 N 和 m,用一个空格隔开。(其中 N 表示总钱数,m 为希望购买物品的个数)?
从第 2 行到第 m+1 行,第 j 行给出了编号为 j?1 的物品的基本数据,每行有 2 个非负整数 v 和 p。(其中 v 表示该物品的价格,p 表示该物品的重要度)
输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(数据保证结果不超过 108)。

数据范围
1≤N<30000,
1≤m<25,
0≤v≤10000,
1≤p≤5
输入样例:
1000 5
800 2
400 5
300 5
400 3
200 2
输出样例:
3900

#include
using namespace std;
#define ll long long
const int N = 30005;
int dp[N];
int main(){
   
    int n,m;
    scanf("%d%d",&n,&m);
    while(m--){
   
        int a,b,w;
        scanf("%d%d",&a,&b);
        w=a*b;
        for(int i=n;i>=a;i--){
   
            dp[i]=max(dp[i],dp[i-a]+w);
        }
    }
    printf("%d\n",dp[n]);
}

状态机
阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。
这条街上一共有 N 家店铺,每家店中都有一些现金。
阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。
作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。
他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?
输入格式
输入的第一行是一个整数 T,表示一共有 T 组数据。
接下来的每组数据,第一行是一个整数 N ,表示一共有 N 家店铺。
第二行是 N 个被空格分开的正整数,表示每一家店铺中的现金数量。
每家店铺中的现金数量均不超过1000。
输出格式
对于每组数据,输出一行。
该行包含一个整数,表示阿福在不惊动警察的情况下可以得到的现金数量。
数据范围
1≤T≤50,
1≤N≤105
输入样例:
2
3
1 8 2
4
10 7 6 14
输出样例:
8
24
样例解释
对于第一组样例,阿福选择第2家店铺行窃,获得的现金数量为8。
对于第二组样例,阿福选择第1和4家店铺行窃,获得的现金数量为10+14=24。

#include
using namespace std;
int a[100005];
int dp[100005][2];
const int INF = 0x3f3f3f3f;

int main(){
   
    int T;
    scanf("%d",&T);
    while(T--)
    {
   
    int n;
    scanf("%d",&n);
    dp[0][0]=0,dp[0][1]=-INF;
    for(int i=1;i<=n;i++){
   
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++){
   
        dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
        dp[i][1]=dp[i-1][0]+a[i];
    }
    printf("%d\n",max(dp[n][0],dp[n][1]));    
    }
    return 0;
}

状态压缩dp
司令部的将军们打算在 N×M 的网格地图上部署他们的炮兵部队。
一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地(用 H 表示),也可能是平原(用 P 表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
图上其它白色网格均攻击不到。
从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示 N 和 M;
接下来的 N 行,每一行含有连续的 M 个字符(P 或者 H),中间没有空格。按顺序表示地图中每一行的数据。
输出格式
仅一行,包含一个整数 K,表示最多能摆放的炮兵部队的数量。
数据范围
N≤100,M≤10
输入样例:
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出样例:
6

#include
using namespace std;
const int N = 100,maxn= (1<<10)+10;
int n,m,dp[2][maxn][maxn],cnt[maxn];
int g[N];
vector<int>state;

//判断是否两个1的距离大于1.
bool check(int x){
   
    return !((x&x>>1)||(x&x>>2));
}

//对每个表示状态的二进制数进行计数(计算1的个数)
void count(int x){
   
    cnt[x]=0;
    for(int i=0;i<m;i++){
   
        if(x&1<<i)cnt[x]++;
    }
}

int main(){
   
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
   
        string str;
        cin>>str;
        for(int j=m-1,k=0;j>=0;k++,j--){
   
            if(str[j]=='H')g[i]+=1<<k;
        }
    }
    // for(int i=0;i
    //     cout<
    // }
    for(int i=0;i<(1<<m);i++){
   
        if(check(i)){
   
            state.push_back(i);
            count(i);
    //        printf("%d ",i);
        }
    }
    //枚举到最后一行的下两行
    for(int i=0;i<n+2;i++){
   
        for(int j=0;j<state.size();j++){
   
            for(int k=0;k<state.size();k++){
   
                for(int l=0;l<state.size();l++){
   
                    int a=state[j];
                    int b=state[k];
                    int c=state[l];
                    if((a&b)||(b&c)||(c&a))continue;
                    if((g[i]&c))continue;
                    dp[i&1][k][l]=max(dp[i&1][k][l],dp[i-1&1][j][k]+cnt[c]);
                }
            }
        }
    }
    printf("%d\n",dp[n+1&1][0][0]);
}

区间dp
环形石子合并
将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 n?1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n?1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
数据范围
1≤n≤200
输入样例:
4
4 5 9 4
输出样例:
43
54

#include 
#include 
#include 
using namespace std;
const int N = 550;
int a[N], s[N], f[N][N], g[N][N];
int main()
{
   
    int n;
    cin >> n;
    for(int i = 1;i <= n; i ++){
   cin >> a[i];a[i + n] = a[i];}
    for(int i = 1;i <= 2 * n; i ++){
   s[i] = s[i-1] + a[i];}
    memset(f, 0x3f, sizeof(f));
    memset(g, 0, sizeof(g));
    for(int len = 1; len <= n; len ++){
   
        for(int l = 1; l + len - 1 <= 2 * n; l ++){
   
            int r = l + len - 1;
            if(len == 1){
   
                g[l][r] = f[l][r] = 0;
            } 
            else{
     
                for(int k = l ; k < r; k ++){
    
                    g[l][r] = max(g[l][r], g[l][k] + g[k+1][r] + s[r] - s[l - 1]);
                    f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + s[r] - s[l - 1]);
                } 
            }
        }
    }
    int ans1 = 0x3f3f3f3f, ans2 = 0;
    for(int i = 1; i <= n; i ++){
   
        ans1 = min(ans1, f[i][i + n - 1]);
        ans2 = max(ans2, g[i][i + n - 1]);
    }
    cout << ans1 << endl << ans2 << endl;
    return 0;
}

树的最长路径
给定一棵树,树中包含 n 个结点(编号1~n)和 n?1 条无向边,每条边都有一个权值。
现在请你找到树中的一条最长路径。
换句话说,要找到一条路径,使得使得路径两端的点的距离最远。
注意:路径中可以只包含一个点。
输入格式
第一行包含整数 n。
接下来 n - 1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。
输出格式
输出一个整数,表示树的最长路径的长度。
数据范围
1≤n≤10000,
1≤ai,bi≤n,
-105≤ci≤105
输入样例:
6
5 1 6
1 4 5
6 3 9
2 6 8
6 1 7
输出样例:
22

#include
using namespace std;
const int N = 10010, M = 20010;
int ver[M],edge[M],Next[M],head[N];
int tot;

void add(int x,int y,int z){
   
    ver[++tot]=y;edge[tot]=z;//真实数据
    Next[tot]=head[x];head[x]=tot;//在表头x处插入
}

int ans=0;
int dfs(int u,int father){
   
    int dist=0;
    int d1=0,d2=0;
    for(int i=head[u];i;i=Next[i]){
   
        int j=ver[i];
        if(j==father)continue;
        int d=dfs(j,u)+edge[i];
        dist=max(dist,d);
        if(d>=d1)d2=d1,d1=d;
        else if(d>d2)d2=d;
    }
    ans=max(ans,d1+d2);
    return dist;
}

int main(){
   
    int n;
    scanf("%d",&n);
    for(int i=1;i<n;i++){
   
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    dfs(1,0);
    printf("%d\n",ans);
    return 0;   
}

树和图

Acwing285. 没有上司的舞会
Ural 大学有 N 名职员,编号为 1~N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。
在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
输入格式
第一行一个整数 N。
接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。
接下来 N - 1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。
输出格式
输出最大的快乐指数。

数据范围
1≤N≤6000,
-128≤Hi≤127
输入样例:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出样例:
5

#include
using namespace std;
const int N = 6500, M = 2 * N;
int h[N], e[M], ne[M], idx; 
int dp[N][2], a[N], vis[N];

void add(int a, int b){
   
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

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

int main()
{
   
    int n;
    cin >> n;
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n; i ++){
   
        cin >> a[i];
    }
    for(int i = 0; i < n - 1; i ++){
   
        int x, y;
        cin >> x >> y;
        add(y, x);
        vis[x] = 1;
    }
    int root = 1;
    while(vis[root]) root ++;
    dfs(root, -1);
    cout << max(dp[root][0], dp[root][1]) << endl;
    return 0;
}

dijstra算法

#include
using namespace std;
const int N = 3000,INF = 0x3f3f3f3f;
int a[N][N];
int d[N],n,ts,te;
bool v[N];
int dijstra(int v0){
   
    v[v0]=true;
    for(int i=1;i<=n;i++){
   
        d[i]=a[v0][i];
    }
    for(int i=1;i<n;i++){
   
        int x=-1,minnum=INF;
        for(int j=1;j<=n;j++){
   
            if(v[j]==false&&(x==-1||minnum>d[j])){
   
                minnum=

你可能感兴趣的:(题解,ACM训练题题解,笔记,算法,图论,c++)