离散化
802. 区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
#include <iostream>#include <cstring>#include <algorithm>#include <vector>#define x first#define y secondconst int N = 3e5;using namespace std;typedef pair<int, int> PII;int a[N];vector<PII>add,query;vector<int>alls;int find(int x){int l = 0;int r = alls.size()-1;while(l<r){int mid = l+r>>1;if(alls[mid]==x){return mid+1;}else if(alls[mid]>x){r = mid-1;}else{l = mid+1;}}return r+1;}// find函数也可以下面这种写法int find(int x){return lower_bound(nums.begin(), nums.end(), x) - nums.begin();}int main(){int n,m;cin >> n >> m;for (int i = 0; i < n; i ++ ){int x,c;cin >> x >> c;add.push_back({x,c});alls.push_back(x);}for (int i = 0; i < m; i ++ ){int l,r;cin >> l >> r;query.push_back({l,r});alls.push_back(l);alls.push_back(r);}sort(alls.begin(),alls.end());alls.erase(unique(alls.begin(),alls.end()),alls.end());// 处理插入for(auto item:add){int x = find(item.x);a[x]+=item.y;}// 预处理前缀和for (int i = 2; i <= alls.size(); i ++ ){a[i]+=a[i-1];}// 处理查询for(auto item:query){int l = find(item.x);int r = find(item.y);cout << a[r] - a[l-1]<<endl;}}
并查集
836. 合并集合
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 m 个操作,操作共有两种:
M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。
输出格式
对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5+10;int p[N];int find(int x){if(p[x]!=x){p[x] = find(p[x]);}return p[x];}int main(){int n,m;cin >> n >> m;for (int i = 1; i <= n; i ++ ){p[i] = i;}while (m -- ){string t;int a,b;cin >> t >> a >> b;int fa = find(a);int fb = find(b);if(t[0]=='M'){p[fa] = fb;}else{if(fa==fb){puts("Yes");}else{puts("No");}}}}
837. 连通块中点的数量
给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。
现在要进行 m 个操作,操作共有三种:
C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
Q2 a,询问点 a 所在连通块中点的数量;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 C a b,Q1 a b 或 Q2 a 中的一种。
输出格式
对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No。
对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5+10;int p[N],sz[N];int find(int x){if(p[x]!=x){p[x] = find(p[x]);}return p[x];}int main(){int n,m;cin >> n >> m;for (int i = 1; i <= n; i ++ ){p[i] = i;sz[i] = 1;}while (m -- ){string t;int a,b;cin >> t;if(t[0]=='C'){cin >> a >> b;int fa = find(a);int fb = find(b);if(fa==fb){continue; // 这点很重要,如果两个点已经在一个集合中了就不需要合并了}sz[fb]+=sz[fa];p[fa] = fb;}else if(t[1]=='1'){cin >> a >> b;int fa = find(a);int fb = find(b);if(fa==fb){puts("Yes");}else{puts("No");}}else{cin >> a;cout << sz[find(a)]<<endl;}}}
240. 食物链
动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。
A 吃 B,B 吃 C,C 吃 A。
现有 N 个动物,以 1∼N 编号。
每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 N 个动物所构成的食物链关系进行描述:
第一种说法是 1 X Y,表示 X 和 Y 是同类。
第二种说法是 2 X Y,表示 X 吃 Y。
此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。
当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
当前的话与前面的某些真的话冲突,就是假话;
当前的话中 X 或 Y 比 N 大,就是假话;
当前的话表示 X 吃 X,就是假话。
你的任务是根据给定的 N 和 K 句话,输出假话的总数。
输入格式
第一行是两个整数 N 和 K,以一个空格分隔。
以下 K 行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。
若 D=1,则表示 X 和 Y 是同类。
若 D=2,则表示 X 吃 Y。
输出格式
只有一个整数,表示假话的数目。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5+10;int p[N],d[N];int find(int x) // 并查集{if(p[x]!=x){int t = find(p[x]);d[x]+=d[p[x]];p[x] = t;}return p[x];}int main(){int n,m;cin >> n >> m;for (int i = 1; i <= n; i ++ ){p[i] = i;}int res=0;while (m -- ){int t,x,y;cin >> t >> x >> y;if(x>n||y>n){res++;continue;}int px = find(x);int py = find(y);if(t==1){ //同类if(px==py&&(d[x]-d[y])%3){ // 查出来已经在集合中,但是不是同类res++;}else if(px!=py){ // 目前没说过这句话以及和这句话矛盾的话p[px] = py;d[px] = d[y]-d[x];}}else{ // x吃yif(px==py&&(d[y]-d[x]-1)%3){res++;}else if(px!=py){p[px] = py;d[px] = d[y]-d[x]-1;}}}cout << res;}
1242. 修改数组
给定一个长度为 N 的数组 A=[A1,A2,⋅⋅⋅AN],数组中有可能有重复出现的整数。
现在小明要按以下方法将其修改为没有重复整数的数组。
小明会依次修改 A2,A3,⋅⋅⋅,AN。
当修改 Ai 时,小明会检查 Ai 是否在 A1∼Ai−1 中出现过。
如果出现过,则小明会给 Ai 加上 1;如果新的 Ai 仍在之前出现过,小明会持续给 Ai 加 1,直到 Ai 没有在 A1∼Ai−1 中出现过。
当 AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。
现在给定初始的 A 数组,请你计算出最终的 A 数组。
输入格式
第一行包含一个整数 N。
第二行包含 N 个整数 A1,A2,⋅⋅⋅,AN。
输出格式
输出 N 个整数,依次是最终的 A1,A2,⋅⋅⋅,AN。
输入样例:
5
2 1 1 3 4
输出样例:
2 1 3 4 5
#include <cstdio>const int N = 1100010;int p[N];int find(int x){if (p[x] != x) p[x] = find(p[x]);return p[x];}int main(){int n;scanf("%d", &n);for (int i = 1; i < N; i ++ ) p[i] = i;for (int i = 1; i <= n; i ++ ){int x;scanf("%d", &x);x = find(x);printf("%d ", x);p[x] = x + 1;}return 0;}
1171. 距离
给出 n 个点的一棵树,多次询问两点之间的最短距离。
注意:
边是无向的。
所有节点的编号是 1,2,…,n。
输入格式
第一行为两个整数 n 和 m。n 表示点数,m 表示询问次数;
下来 n−1 行,每行三个整数 x,y,k,表示点 x 和点 y 之间存在一条边长度为 k;
再接下来 m 行,每行两个整数 x,y,表示询问点 x 到点 y 的最短距离。
树中结点编号从 1 到 n。
输出格式
共 m 行,对于每次询问,输出一行询问结果。
输入样例1:
2 2
1 2 100
1 2
2 1
输出样例1:
100
100
输入样例2:
3 2
1 2 10
3 1 15
1 2
3 2
输出样例2:
10
25
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <vector>using namespace std;typedef pair<int, int> PII;const int N = 10010, M = N * 2;int n, m;int h[N], e[M], w[M], ne[M], idx;int dist[N];//每个点和1号点的距离int p[N];int res[M];int st[N];vector<PII> query[N];//把询问存下来// query[i][first][second] first存查询距离i的另外一个点j,second存查询编号idxvoid add(int a,int b,int c){e[idx] = b;ne[idx] = h[a];w[idx] = c;h[a] = idx++;}int find(int x){if(p[x]!=x)p[x] = find(p[x]);return p[x];}void dfs(int u,int fa){for(int i=h[u];~i;i=ne[i]){int j = e[i];if(j==fa) continue;dist[j] = dist[u]+w[i];dfs(j,u);}}// 利用并查集求最近公共祖先void tarjan(int u){st[u]=1;//当前路径点标记为1// u这条路上的根节点的左下的点用并查集合并到根节点for(int i = h[u];~i;i=ne[i]){int j = e[i];if(!st[j]){tarjan(j);//往左下搜p[j] = u;//从左下回溯后把左下的点合并到根节点}}// 对于当前点u 搜索所有和ufor(auto item:query[u]){int y = item.first,id = item.second;if(st[y]==2)//如果查询的这个点已经是左下的点(已经搜索过且回溯过,标记为2){int anc = find(y);//y的根节点// x到y的距离 = d[x]+d[y] - 2*d[lca]res[id] = dist[u]+dist[y] - dist[anc]*2;//第idx次查询的结果 res[idx]}}//点u已经搜索完且要回溯了 就把st[u]标记为2st[u] = 2;}int main(){cin >> n >> m;// 建图memset(h,-1,sizeof h);for(int i=0;i<n-1;i++){int a,b,c;cin >> a >> b >> c;add(a,b,c),add(b,a,c);}// 存下询问for(int i=0;i<m;i++){int a,b;cin >> a >> b;if(a!=b){query[a].push_back({b,i});query[b].push_back({a,i});}}for(int i=1;i<=n;i++)p[i] = i;dfs(1,-1);tarjan(1);for(int i=0;i<m;i++)cout << res[i] << '\n';//把每次询问的答案输出return 0;}
Trie
835. Trie字符串统计
维护一个字符串集合,支持两种操作:
I x 向集合中插入一个字符串 x;
Q x 询问一个字符串在集合中出现了多少次。
共有 N 个操作,输入的字符串总长度不超过 105,字符串仅包含小写英文字母。
输入格式
第一行包含整数 N,表示操作数。
接下来 N 行,每行包含一个操作指令,指令为 I x 或 Q x 中的一种。
输出格式
对于每个询问指令 Q x,都要输出一个整数作为结果,表示 x 在集合中出现的次数。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 2e4+10;int tr[N][26],idx;int cnt[N];void insert(string s){int p=0;for (int i = 0; i < s.size(); i ++ ){int u = s[i]-'a';if(!tr[p][u]){tr[p][u]=++idx;}p = tr[p][u];}cnt[p]++;}int query(string s){int p=0;for (int i = 0; i < s.size(); i ++ ){int u = s[i]-'a';if(!tr[p][u]){return 0;}p = tr[p][u];}return cnt[p];}int main(){int n;string t,s;cin >> n;while (n -- ){cin >> t >> s;if(t=="I"){insert(s);}else{cout << query(s)<<endl;}}}
143. 最大异或对
在给定的 N 个整数 A1,A2……AN 中选出两个进行 xor(异或)运算,得到的结果最大是多少?
输入格式
第一行输入一个整数 N。
第二行输入 N 个整数 A1~AN。
输出格式
输出一个整数表示答案。
输入样例:
3
1 2 3
下面trie枚举的时候必须从最高位开始枚举因为要使得异或起来最大,就得使得最高位优先选择不同的#include <iostream>#include <cstring>#include <algorithm>const int N = 1e5+10;typedef long long LL;using namespace std;LL a[N];int n;int son[3100010][2], idx;LL res;void insert(int x) // 插入字符串{int p = 0;for (int i = 31; i>=0; i -- ){int u = (x>>i)&1;if (!son[p][u]) son[p][u] = ++ idx;p = son[p][u];}}void query(int x) // 查询字符串出现次数{int p = 0;LL t=0;for (int i = 31; i>=0; i -- ){int u = x>>i&1;if(son[p][!u]){p = son[p][!u];t |= (1<<i);}else if(son[p][u]){p = son[p][u];}else{return ;}}res = max(res,t);return ;}int main(){cin >> n;for (int i = 0; i < n; i ++ ){cin >> a[i];insert(a[i]);}for (int i = 0; i < n; i ++ ){query(a[i]);}cout << res;}
记忆化搜索
901. 滑雪
给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。
矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。
一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。
当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。
下面给出一个矩阵作为例子:
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
在给定矩阵中,一条可行的滑行轨迹为 24−17−2−1。
在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−1,沿途共经过 25 个区域。
现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。
输入格式
第一行包含两个整数 R 和 C。
接下来 R 行,每行包含 C 个整数,表示完整的二维矩阵。
输出格式
输出一个整数,表示可完成的最长滑雪长度。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 310;int g[N][N];int d[N][N];int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};int m,n;int ans;int dfs(int x,int y){if(d[x][y]){return d[x][y];}for (int i = 0; i < 4; i ++ ){int tx = x+dx[i];int ty = y+dy[i];if(tx<0||tx>=m||ty<0||ty>=n){continue;}if(g[tx][ty]>=g[x][y]){continue;}dfs(tx,ty);d[x][y] = max(d[x][y],d[tx][ty]);}d[x][y]+=1;}int main(){cin >> m >> n;for (int i = 0; i < m; i ++ ){for (int j = 0; j < n; j ++ ){cin >> g[i][j];}}for (int i = 0; i < m; i ++ ){for (int j = 0; j < n; j ++ ){dfs(i,j);ans = max(ans,d[i][j]);}}cout << ans;}
哈希
841. 字符串哈希
给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2,请你判断 [l1,r1] 和 [l2,r2] 这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数 n 和 m,表示字符串长度和询问次数。
第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。
接下来 m 行,每行包含四个整数 l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从 1 开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No。
#include <iostream>#include <cstring>#include <algorithm>typedef unsigned long long ULL;using namespace std;const int N = 1e5+10;int P = 131;ULL h[N], p[N];string str;ULL get(int l, int r) // 计算子串 str[l ~ r] 的哈希值{return h[r] - h[l - 1] * p[r - l + 1];}int main(){int n,m;cin >> n >> m;cin >> str;str = " "+str;p[0] = 1;// 哈希for (int i = 1; i <= n; i ++ ){h[i] = h[i-1]*P + str[i];p[i] = p[i-1]*P;}while (m -- ){int l1,r1,l2,r2;cin >> l1 >> r1 >> l2 >> r2;if(get(l1,r1)==get(l2,r2)){cout << "Yes"<<endl;}else{cout << "No" <<endl;}}}
贪心
114. 国王游戏
恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。
首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。
然后,让这 n 位大臣排成一排,国王站在队伍的最前面。
排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:
排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。
注意,国王的位置始终在队伍的最前面。
输入格式
第一行包含一个整数 n,表示大臣的人数。
第二行包含两个整数 a 和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出格式
输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
#include <cstring>#include <iostream>#include <algorithm>#include <vector>using namespace std;typedef pair<int, int> PII;const int N = 1010;int n;PII p[N];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;}vector<int> div(vector<int>a, int b){vector<int> c;bool is_first = true;for (int i = a.size() - 1, t = 0; i >= 0; i -- ){t = t * 10 + a[i];int x = t / b;if (!is_first || x){is_first = false;c.push_back(x);}t %= b;}reverse(c.begin(), c.end());return c;}vector<int> max_vec(vector<int> a, vector<int> b){if (a.size() > b.size()) return a;if (a.size() < b.size()) return b;if (vector<int>(a.rbegin(), a.rend()) > vector<int>(b.rbegin(), b.rend())) return a;return b;}int main(){cin >> n;for (int i = 0; i <= n; i ++ ){int a, b;cin >> a >> b;p[i] = {a * b, a};}sort(p + 1, p + n + 1);vector<int> product(1, 1);vector<int> res(1, 0);for (int i = 0; i <= n; i ++ ){if (i) res = max_vec(res, div(product, p[i].first / p[i].second));product = mul(product, p[i].second);}for (int i = res.size() - 1; i >= 0; i -- ) cout << res[i];cout << endl;return 0;}
- 国王游戏
恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。
首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。
然后,让这 n 位大臣排成一排,国王站在队伍的最前面。
排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:
排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。
注意,国王的位置始终在队伍的最前面。
输入格式
第一行包含一个整数 n,表示大臣的人数。
第二行包含两个整数 a 和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出格式
输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
125. 耍杂技的牛
农民约翰的 N 头奶牛(编号为 1..N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。
奶牛们不是非常有创意,只提出了一个杂技表演:
叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。
奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。
这 N 头奶牛中的每一头都有着自己的重量 Wi 以及自己的强壮程度 Si。
一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。
您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。
输入格式
第一行输入整数 N,表示奶牛数量。
接下来 N 行,每行输入两个整数,表示牛的重量和强壮程度,第 i 行表示第 i 头牛的重量 Wi 以及它的强壮程度 Si。
输出格式
输出一个整数,表示最大风险值的最小可能值。

#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 5e4;int n;struct Node{int w,s,sum;bool operator<(const Node t)const{return sum<t.sum;}}g[N];int main(){cin >> n;for (int i = 0; i < n; i ++ ){int w,s;cin >> w >> s;g[i] = {w,s,w+s};}sort(g,g+n);int sum=g[0].w;int res=-g[0].s;for (int i = 1; i < n; i ++ ){res = max(res,sum-g[i].s);sum+=g[i].w;}cout << res;}
905. 区间选点
给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
输入格式
第一行包含整数 N,表示区间数。
接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需的点的最小数量。
908. 最大不相交区间数量
给定 N 个闭区间 [ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
// 只要枚举最小的右边的位置即可#include <iostream>#include <cstring>#include <algorithm>#include <vector>#define x first#define y secondusing namespace std;const int N = 1e5+10;typedef pair<int, int> PII;vector<PII>g;int main(){int n;cin >> n;for(int i=0;i<n;i++){int l,r;cin >> l >> r;g.push_back({l,r});}sort(g.begin(),g.end());int r=g[0].y;int res = 0;for (int i = 1; i < n; i ++ ){if(g[i].x>r){res++;r = g[i].y;}else{r = min(r,g[i].y);}}res++;cout << res;}
906. 区间分组
给定 N 个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
输入格式
第一行包含整数 N,表示区间数。
接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示最小组数。
#include <iostream>#include <cstring>#include <algorithm>#include <vector>#include <queue>#define x first#define y secondusing namespace std;const int N = 1e5+10;typedef pair<int, int> PII;vector<PII>g;struct Node{int r;bool operator<(const Node t)const{return r>t.r;}};priority_queue<Node>q;int main(){int n;cin >> n;for(int i=0;i<n;i++){int l,r;cin >> l >> r;g.push_back({l,r});}sort(g.begin(),g.end());q.push({g[0].y});int res=1;for (int i = 1; i <n; i ++ ){if(q.top().r<g[i].x){q.pop();q.push({g[i].y});}else{res++;q.push({g[i].y});}}cout << res;}
907. 区间覆盖
给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 −1。
输入格式
第一行包含两个整数 s 和 t,表示给定线段区间的两个端点。
第二行包含整数 N,表示给定区间数。
接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需最少区间数。
如果无解,则输出 −1。
#include <iostream>#include <cstring>#include <algorithm>#include <vector>#define x first#define y secondusing namespace std;const int N = 1e5+10;typedef pair<int, int> PII;vector<PII>g;int st[N];int main(){int s,t;cin >> s >> t;int n;cin >> n;for(int i=0;i<n;i++){int l,r;cin >> l >> r;g.push_back({l,r});}sort(g.begin(),g.end());int res=0;int s1=s,s2=s; // 从s1开始匹配,能匹配到的最远点为s2for (int i = 0; i < n && s2<t; i ++ ){if(g[i].x<=s1){ //s2 = max(s2,g[i].y);}else{ // 当前点已经不含该s1,那么就应该从s2开始匹配了res++;st[i]++; // 所以还要从当前这个i继续从s2开始匹配,所以i--,// 当如果下次这个点超过了s2,那么就无法全部覆盖了if(st[i]>1){cout << -1<<endl;return 0;}s1 = s2;i--;}}if(s2>=t){ // 最后一步要加上res++;cout << res;}else{cout << -1 <<endl;}}
803. 区间合并
给定 n 个区间 [li,ri],要求合并所有有交集的区间。
如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6]。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
#include <iostream>#include <cstring>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 1e5;PII g[N];int main(){int n;cin >> n;for (int i = 0; i < n; i ++ ){cin >> g[i].x >> g[i].y;}sort(g,g+n);int r = g[0].y;int res = 0;for (int i = 1; i < n; i ++ ){if(g[i].x<=r){r = max(r,g[i].y);}else{r = g[i].y;res++;}}res++;cout << res;}
1235. 付账问题
几个人一起出去吃饭是常有的事。
但在结帐的时候,常常会出现一些争执。
现在有 n 个人出去吃饭,他们总共消费了 S 元。
其中第 i 个人带了 ai 元。
幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?
为了公平起见,我们希望在总付钱量恰好为 S 的前提下,最后每个人付的钱的标准差最小。
这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是 1 分钱的整数倍。
你需要输出最小的标准差是多少。
标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。
形式化地说,设第 i 个人付的钱为 bi 元,那么标准差为 :

#include <iostream>#include <cstring>#include <algorithm>#include <cmath>using namespace std;const int N = 5e5 + 10;int n;int a[N];int main() {long double s;cin >> n >> s;for (int i = 0; i < n; i++)scanf("%d", &a[i]);sort(a, a + n);long double res = 0, avg = s / n;for (int i = 0; i < n; i++) {double cur = s / (n - i);if (a[i] < cur) cur = a[i];res += (cur - avg) * (cur - avg);s -= cur;}printf("%.4Lf", sqrt(res / n)); //为了避免丢失精度,到最后再除n}
归并
787. 归并排序
给定你一个长度为 n 的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1∼109 范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;int n;const int N = 1e5+10;int a[N],t[N];void merge_sort(int l,int r){if(l>=r){return;}int mid = (l+r)>>1;merge_sort(l,mid);merge_sort(mid+1,r);int i=l,j=mid+1,k=0;while(i<=mid&&j<=r){if(a[i]<a[j]){t[k++] = a[i++];}else{t[k++] = a[j++];}}while(i<=mid){t[k++] = a[i++];}while(j<=r){t[k++] = a[j++];}for(int i=l,j=0; i<=r; i++,j++){a[i] = t[j];}}int main(){cin >> n;for (int i = 0; i < n; i ++ ){cin >> a[i];}merge_sort(0,n-1);for (int i = 0; i < n; i ++ ){cout << a[i]<<" ";}}
788. 逆序对的数量
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i
输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
#include <iostream>#include <cstring>#include <algorithm>typedef long long LL;using namespace std;const int N = 1e5+10;int n;int a[N],t[N];LL merge_sort(int l,int r){if(l>=r){return 0;}int mid = (l+r)/2;LL res=0;res += merge_sort(l,mid);res += merge_sort(mid+1,r);// 计算resint i=l,j=mid+1;while(i<=mid&&j<=r){if(a[i]>a[j]){res += mid-i+1;j++;}else{i++;}}// 归并排序i = l,j=mid+1;int k=0;while(i<=mid&&j<=r){if(a[i]>a[j]){t[k++] = a[j++];}else{t[k++] = a[i++];}}while(i<=mid){t[k++] = a[i++];}while(j<=r){t[k++] = a[j++];}for (int i = l,k=0; i <= r; i ++,k++ ){a[i] = t[k];}return res;}int main(){cin >> n;for (int i = 0; i < n; i ++ ){cin >> a[i];}cout << merge_sort(0,n-1);}
二分
789. 数的范围
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1。
// map做法#include <iostream>#include <cstring>#include <algorithm>#include <map>const int N = 1e5+10;using namespace std;map<int,int>st;map<int,int>ed;int a[N];int main(){int n,q;cin >> n >> q;for (int i = 1; i <= n; i ++ ){cin >> a[i];if(st[a[i]]){ed[a[i]] = i;}else{st[a[i]] = i;ed[a[i]] = i;}}while(q--){int x;cin >> x;if(st[x]){cout << st[x]-1 << " "<<ed[x]-1<<endl;}else{cout << "-1 -1" <<endl;}}}// 二分做法
790. 数的三次方根
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6 位小数。
数据范围
−10000≤n≤10000
#include <iostream>#include <cstring>#include <algorithm>using namespace std;int main(){double x;cin >> x;double l = -1e5;double r = 1e5;// 保留6位小数,经验值取1e-8while(r-l>1e-8){double mid = (l + r)/2;if(mid*mid*mid>=x){r = mid;}else{l = mid;}}printf("%.6lf",r);}
差分
797. 差分
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5+10;int a[N];int b[N];void insert(int l,int r,int c){b[l-1]+=c;b[r] -= c;}int main(){int n,m;cin >> n >> m;for (int i = 1; i <= n; i ++ ){cin >> a[i];insert(i,i,a[i]);}while (m -- ){int l,r,c;cin >> l >> r >> c;insert(l,r,c);}for (int i = 1; i < n; i ++ ){b[i]+=b[i-1];}for (int i = 0; i < n; i ++ ){cout << b[i] <<" ";}}
798. 差分矩阵
输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上 c。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010;int n,m,q;int a[N][N];int b[N][N];void insert(int x1,int y1,int x2,int y2,int c){b[x1][y1]+=c;b[x2+1][y1]-=c;b[x1][y2+1]-=c;b[x2+1][y2+1]+=c;}int main(){cin >> n >> m >> q;for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= m; j ++ ){cin >> a[i][j];// 初始化差分矩阵,只要将a[i][j]看成是在1*1的小方格上操作就可以insert(i,j,i,j,a[i][j]);}}while(q--){int x1,y1,x2,y2,c;cin >> x1 >> y1 >> x2 >> y2 >> c;insert(x1,y1,x2,y2,c);}// 求前缀和for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= m; j ++ ){b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];// 打印结果cout << b[i][j]<<" ";}cout << endl;}}
位运算
801. 二进制中1的个数
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;int lowbits(int x){return x&(-x);}int main(){int n;cin >> n;for (int i = 0; i < n; i ++ ){int x,res=0;cin >> x;while(x){res++;x-=lowbits(x);}cout << res<<" ";}}
数论
104. 货仓选址
在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行输入整数 N。
第二行 N 个整数 A1∼AN。
输出格式
输出一个整数,表示距离之和的最小值。

#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5+10;int a[N],n;int main(){cin >> n;for (int i = 0; i < n; i ++ ){cin >> a[i];}sort(a,a+n);int res=0;for (int i = 0; i < n; i ++ ){res+=abs(a[i]-a[n/2]);}cout << res;}
3424. 最少砝码
你有一架天平。现在你要设计一套砝码,使得利用这些砝码可以称出任意小于等于 N 的正整数重量。
那么这套砝码最少需要包含多少个砝码?
注意砝码可以放在天平两边。
输入格式
输入包含一个正整数 N。
输出格式
输出一个整数代表答案。
输入样例:
7
输出样例:
3
问题:需要使用砝码测量1,2,3,4……,99,100所有重量的物品,最少需要多少个砝码?
最直观的感受是直接使用1,2,4,8……,64 。也就是2^0,2^1,2^2……2^6,一共7个砝码,因为任何数字都可以表示成二进制数,比如100用二进制表示就是:1100100,所以使用2^2,2^5,2^6三个砝码即可。任何数字都可以表示为二进制数,在这里就意味着任何重量都可以用上述2^n砝码表示。
但这是最少数量的砝码吗?
这要视情况而定,如果只能在天平的一边放砝码,另一边放物品,这就是最少的方案。因为对于砝码而言只有两种状态:放与不放,对于n个砝码,可能出现的总体状态有2^n种状态,要表示100个数据则要保证2^n>=100,得最小的n=7,意思就是说不管什么砝码方案,至少都要7个砝码。而前面我们猜想的方案正好是7个砝码,正好是理论推导出的最少砝码个数,不会比这个数目更少了,所以是一个最少的方案。
如果允许天平两边都放砝码,也就是说物品可以和砝码放在一边,上述2^n砝码方案就不是最少的方案了。因为砝码有三种状态:不放、放左边和放右边。对于n个砝码,可能出现的总体状态有3^n种状态,要表示100个数据则要保证3^n>=100,的最小的n=5,意思就是说不管什么砝码方案,至少都要5个砝码。接下来我们尝试一下能不能找到5个砝码的方案。
任意一个数都可以表示成三进制的数,比如100用三进制表示就是10201,通过减法,我们又可以把任意一个三进制数化成只有0和1的形式,比如将10201化成10201=11001-100。而且减数与被减数相同位上的数字必然不会同时为1,假如化成减法形式后相同位同时为1,比如110-10,两个数的十位上都为1,结果必然是100,没有2,所以不用化简,矛盾。综上,3^0,3^1,3^2……3^4一共5个砝码一定可以表示出从1到3^0+3^1+3^2+…+3^4=121的所有数(注:不是3^5-1)
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e9;int main(){int n;cin >> n;int sum=1,cnt=1,num=1;while(cnt<n){sum*=3;cnt+=sum;num++;}cout << num;}
1211. 蚂蚁感冒
长 100 厘米的细长直杆子上有 n 只蚂蚁。
它们的头有的朝左,有的朝右。
每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒。
当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。
这些蚂蚁中,有 1 只蚂蚁感冒了。
并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。
请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。
输入格式
第一行输入一个整数 n, 表示蚂蚁的总数。
接着的一行是 n 个用空格分开的整数 Xi, Xi 的绝对值表示蚂蚁离开杆子左边端点的距离。
正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。
其中,第一个数据代表的蚂蚁感冒了。
输出格式
输出1个整数,表示最后感冒蚂蚁的数目。
输入样例1:
3
5 -2 8
输出样例1:
1
输入样例2:
5
-10 8 -20 12 25
输出样例2:
3
#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 55;int n;int x[N];int main(){cin >> n;for (int i = 0; i < n; i ++ ) cin >> x[i];int left = 0, right = 0; // 分别表示左边向右走的蚂蚁数量,和右边向左走的蚂蚁数量for (int i = 1; i < n; i ++ )if (abs(x[i]) < abs(x[0]) && x[i] > 0) left ++ ;else if (abs(x[i]) > abs(x[0]) && x[i] < 0) right ++ ;if (x[0] > 0 && right == 0 || x[0] < 0 && left == 0) cout << 1 << endl;else cout << left + right + 1 << endl;return 0;}
1216. 饮料换购
乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。
请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n 瓶饮料,最后他一共能喝到多少瓶饮料。
输入格式
输入一个整数 n,表示初始买入的饮料数量。
输出格式
输出一个整数,表示一共能够喝到的饮料数量。
数据范围
0
100
输出样例:
149
#include <iostream>#include <cstring>#include <algorithm>using namespace std;int main(){int n;cin >> n;int res=0;res+=n;while(n>2){res+=n/3;n = n/3 + n % 3;}cout << res<<endl;}
1246. 等差数列
数学老师给小明出了一道等差数列求和的题目。
但是粗心的小明忘记了一部分的数列,只记得其中 N 个整数。
现在给出这 N 个整数,小明想知道包含这 N 个整数的最短的等差数列有几项?
输入格式
输入的第一行包含一个整数 N。
第二行包含 N 个整数 A1,A2,⋅⋅⋅,AN。(注意 A1∼AN 并不一定是按等差数
列中的顺序给出)
输出格式
输出一个整数表示答案。
输入样例:
5
2 6 4 10 20
输出样例:
10
样例解释
包含 2、6、4、10、20 的最短的等差数列是 2、4、6、8、10、12、14、16、18、20。
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 100010;int a[N];int gcd(int a, int b){return b ? gcd(b, a % b) : a;}int main(){int n;scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);sort(a, a + n);int d = 0;for (int i = 1; i < n; i ++ ) d = gcd(d, a[i] - a[0]);// d为0表示是一个公差为0的数列if (!d) printf("%d\n", n);else printf("%d\n", (a[n - 1] - a[0]) / d + 1);return 0;}
算术基本定理,任何一个数,都可以分解成pi的@次方相加的形式 其中pi是质数






筛质数,筛选法求质数
- X的因子链
输入正整数 X,求 X 的大于 1 的因子组成的满足任意前一项都能整除后一项的严格递增序列的最大长度,以及满足最大长度的序列的个数。
输入格式
输入包含多组数据,每组数据占一行,包含一个正整数表示 X。
输出格式
对于每组数据,输出序列的最大长度以及满足最大长度的序列的个数。
每个结果占一行。
数据范围
1≤X≤220
输入样例:
2
3
4
10
100
输出样例:
1 1
1 1
2 1
2 2
4 6
#include<cstdio>typedef long long ll;const int N = (1 << 20) + 10;int primes[N];// 存质数int min_p[N]; //存最小质因子int cnt;bool st[N]; // 表示对应元素是否被筛过// 线性筛法(欧拉筛法)void get_primes(int n) {for (int i = 2; i <= n; i++) {if (!st[i]) {min_p[i] = i;primes[++cnt] = i;}for (int j = 1; primes[j] * i <= n; j++) {int t = primes[j] * i;st[t] = true; //标记合数min_p[t] = primes[j];if (i % primes[j] == 0) {//如果i是前面某个素数的倍数时, 说明i以后会由某个更大的数乘这个小素数筛去//同理, 之后的筛数也是没有必要的, 因此在这个时候, 就可以跳出循环了break;}}}}int sum[N]; //记录每个质因子出现的次数int main() {get_primes(N);int x;while (scanf("%d", &x) != EOF) {//tol用于记录最大长度,k表示第i个质因子的下标, 从0开始int k = 0, tol = 0;// 依次处理各个质因子, 求出对应质因子出现的次数while (x > 1) {int p = min_p[x]; // 通过while, 依次取出最小质因子sum[k] = 0;//处理当前质因子, 求其出现的次数while (x % p == 0) {sum[k]++;tol++;x /= p;}k++; // 准备处理下一个质因子/*例:x=12 --> 3*2^2p = 2sum[1]=1x=6sum[1]=2x=3****************p=3sum[2]=1x=1 --> 结束*/}//求所有质因子出现总次数的全排列ll res = 1;for (int i = 2; i <= tol; i++) {res *= i; // tol!}//去除各个质因子重复出现的次数for (int i = 0; i < k; i++) {for (int j = 1; j <= sum[i]; j++) {res /= j;}}printf("%d %lld\n", tol, res); //输出最长序列的长度, 以及满足最大长度的序列的个数}return 0;}

3491. 完全平方数
一个整数 a 是一个完全平方数,是指它是某一个整数的平方,即存在一个整数 b,使得 a=b^2。
给定一个正整数 n,请找到最小的正整数 x,使得它们的乘积是一个完全平方数。
输入格式
输入一行包含一个正整数 n。
输出格式
输出找到的最小的正整数 x。
输入样例1:
12
输出样例1:
3
输入样例2:
15
输出样例2:
15
分解质因数:因为为一个数的平方,而任何一个数都可以分解成所有质因数的乘积,那么所有质因数的次方必须是偶数次的
#include <iostream>#include <cstring>#include <algorithm>typedef long long LL;using namespace std;int main(){LL n;cin >> n;LL res=1;for (LL i = 2; i*i <= n; i ++ ){int s=0;if(n%i==0){while(n%i==0){n/=i;s++;}if(s%2){res*=i;}}}if(n>1){res*=n;}cout << res;}
组合数+前缀和
523. 组合数问题
组合数 Cmn 表示的是从 n 个物品中选出 m 个物品的方案数。
举个例子,从 (1, 2, 3) 三个物品中选择两个物品可以有 (1, 2), (1, 3), (2, 3) 这三种选择方法。
根据组合数的定义,我们可以给出计算组合数 Cmn 的一般公式:
Cmn=n!m!(n−m)!
其中 n! = 1 × 2 × ⋅ ⋅ ⋅ × n。
小葱想知道如果给定 n, m 和 k ,对于所有的 0≤i≤ n, 0≤j≤ min(i, m) 有多少对 (i, j) 满足 Cji 是 k 的倍数。
输入格式
第一行有两个整数 t, k ,其中 t 代表该测试点总共有多少组测试数据,k 的意义见问题描述。
接下来 t 行每行两个整数 n, m,其中 n, m 的意义见问题描述。
输出格式
共 t 行,每行一个整数代表所有的 0 ≤ i ≤ n, 0 ≤ j ≤ min(i, m) 有多少对 (i, j) 满足 Cji 是 k 的倍数。
输入样例:
1 2
3 3
输出样例:
1

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 2010;int c[N][N];int s[N][N];int main(){int T, k;scanf("%d%d", &T, &k);for (int i = 0; i < N; i ++ )for (int j = 0; j <= i; j ++ ){if (!j) c[i][j] = 1 % k;else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % k;if (!c[i][j]) s[i][j] = 1;}for (int i = 0; i < N; i ++ )for (int j = 0; j < N; j ++ ){if (i) s[i][j] += s[i - 1][j];if (j) s[i][j] += s[i][j - 1];if (i && j) s[i][j] -= s[i - 1][j - 1];}while (T -- ){int n, m;scanf("%d%d", &n, &m);printf("%d\n", s[n][m]);}return 0;}
矩阵乘法
1303. 斐波那契的前n项和
大家都知道 Fibonacci 数列吧,f1=1,f2=1,f3=2,f4=3,…,fn=fn−1+fn−2。
现在问题很简单,输入 n 和 m,求 fn 的前 n 项和 Snmodm。
输入格式
共一行,包含两个整数 n 和 m。
输出格式
输出前 n 项和 Snmodm 的值。
数据范围
1≤n≤2000000000,
1≤m≤1000000010
输入样例:
5 1000
输出样例:
12
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int N = 3;int n, m;void mul(int c[], int a[], int b[][N]){int temp[N] = {0};for (int i = 0; i < N; i ++ )for (int j = 0; j < N; j ++ )temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % m;memcpy(c, temp, sizeof temp);}void mul(int c[][N], int a[][N], int b[][N]){int temp[N][N] = {0};for (int i = 0; i < N; i ++ )for (int j = 0; j < N; j ++ )for (int k = 0; k < N; k ++ )temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % m;memcpy(c, temp, sizeof temp);}int main(){cin >> n >> m;int f1[N] = {1, 1, 1};int a[N][N] = {{0, 1, 0},{1, 1, 1},{0, 0, 1}};n -- ;while (n){if (n & 1) mul(f1, f1, a); // res = res * amul(a, a, a); // a = a * an >>= 1;}cout << f1[2] << endl;return 0;}
求解斐波那契数列的若干方法
链接:https://www.acwing.com/blog/content/25/
1217. 垒骰子
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 109+7 的结果。
输入格式
第一行包含两个整数 n,m,分别表示骰子的数目和排斥的组数。
接下来 m 行,每行两个整数 a,b,表示 a 和 b 数字不能紧贴在一起。
输出格式
共一个数,表示答案模 109+7 的结果。
数据范围
1≤n≤109,
1≤m≤36,
1≤a,b≤6
输入样例:
2 1
1 2
输出样例:
544

#include <iostream>#include <cstring>#include <algorithm>typedef long long LL;using namespace std;const int N = 7,MOD=1e9+7;int n,m;LL st[N][N];LL f[N][N];int get_op(int x){if (x >= 4) return x - 3;return x + 3;}void mul(LL c[][N],LL a[][N],LL b[][N]){LL tmp[N][N];memset(tmp,0,sizeof tmp);for (int i = 1; i < N; i ++ ){for (int j = 1; j < N; j ++ ){for (int k = 1; k < N; k ++ ){tmp[i][j] = (tmp[i][j]+(a[i][k]*b[k][j])%MOD)%MOD;}}}memcpy(c,tmp,sizeof tmp);}int main(){cin >> n >> m;for (int i = 1; i <= 6; i ++ ){for (int j = 1; j <= 6; j ++ ){st[i][j]=4;}}for (int i = 0; i < m; i ++ ){int x,y;cin >> x >> y;st[x][get_op(y)]=0;st[y][get_op(x)]=0;}for (int i = 1; i <= 6; i ++ ){f[1][i] = 4; //初始化为4}n--;while(n){ // 快速幂if(n%2){mul(f,f,st);}mul(st,st,st);n>>=1;}LL res=0;for (int i = 1; i <= 6; i ++ ){res = (res + f[1][i]%MOD)%MOD;}cout << res;}
双指针,滑动窗口
800. 数组元素的目标和
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。
数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。
输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B。
输出格式
共一行,包含两个整数 i 和 j。
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5;int a[N],b[N];int main(){int n,m,x;cin >> n >> m >> x;for (int i = 0; i < n; i ++ ){cin >> a[i];}for (int i = 0; i < m; i ++ ){cin >> b[i];}int i=0,j=m-1;while(i<n&&j>=0){if(a[i]+b[j]==x){cout << i << " "<<j<<endl;return 0;}else if(a[i]+b[j]>x){j--;}else{i++;}}}
799. 最长连续不重复子序列
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
输入样例:
5
1 2 2 3 5
输出样例:
3
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5;int a[N];bool st[N];int main(){int n;cin >> n;for (int i = 0; i < n; i ++ ){cin >> a[i];}int i=0,j=1;st[a[0]] = true;int res=0;while(j<n){if(st[a[j]]){ // i是开始的元素res = max(res,j-i);while(a[i]!=a[j]){st[a[i]] = false;i++;}i++;}st[a[j]] = true;j++;}/*51 2 2 3 5如果只写上面的话,2 3 5 只会计算为2*/res = max(res,j-i);cout << res;}
1238. 日志统计
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。
其中每一行的格式是:
ts id
表示在 ts 时刻编号 id 的帖子收到一个”赞”。
现在小明想统计有哪些帖子曾经是”热帖”。
如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。
具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。
给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。
输入格式
第一行包含三个整数 N,D,K。
以下 N 行每行一条日志,包含两个整数 ts 和 id。
输出格式
按从小到大的顺序输出热帖 id。
每个 id 占一行。
输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5+10;int n,d,K;struct Node{int t;int id;bool operator<(const Node& node)const{return t<node.t;}}g[N];int cnt[N];bool st[N];int main(){cin >> n >> d >> K;for (int i = 0; i < n; i ++ ){int t,id;cin >> t >> id;g[i] = {t,id};}sort(g,g+n);for (int i = 0, j = 0; i < n; i ++ ){auto t = g[i];cnt[t.id]++;while(g[i].t-g[j].t>=d){cnt[g[j].id]--;j++;}if(cnt[t.id]>=K){st[t.id] = true;}}for (int i = 0; i < N; i ++ ){if(st[i]){cout << i<<endl;}}}
搜索
1101. 献给阿尔吉侬的花束
阿尔吉侬是一只聪明又慵懒的小白鼠,它最擅长的就是走各种各样的迷宫。
今天它要挑战一个非常大的迷宫,研究员们为了鼓励阿尔吉侬尽快到达终点,就在终点放了一块阿尔吉侬最喜欢的奶酪。
现在研究员们想知道,如果阿尔吉侬足够聪明,它最少需要多少时间就能吃到奶酪。
迷宫用一个 R×C 的字符矩阵来表示。
字符 S 表示阿尔吉侬所在的位置,字符 E 表示奶酪所在的位置,字符 # 表示墙壁,字符 . 表示可以通行。
阿尔吉侬在 1 个单位时间内可以从当前的位置走到它上下左右四个方向上的任意一个位置,但不能走出地图边界。
输入格式
第一行是一个正整数 T,表示一共有 T 组数据。
每一组数据的第一行包含了两个用空格分开的正整数 R 和 C,表示地图是一个 R×C 的矩阵。
接下来的 R 行描述了地图的具体内容,每一行包含了 C 个字符。字符含义如题目描述中所述。保证有且仅有一个 S 和 E。
输出格式
对于每一组数据,输出阿尔吉侬吃到奶酪的最少单位时间。
若阿尔吉侬无法吃到奶酪,则输出“oop!”(只输出引号里面的内容,不输出引号)。
每组数据的输出结果占一行。
数据范围
1
输入样例:
3
3 4
.S..
###.
..E.
3 4
.S..
.E..
….
3 4
.S..
..E.
输出样例:
5
1
oop!
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <queue>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 210;int n, m;char g[N][N];int dist[N][N];int bfs(PII start, PII end){queue<PII> q;memset(dist, -1, sizeof dist);dist[start.x][start.y] = 0;q.push(start);int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};while (q.size()){auto t = q.front();q.pop();for (int i = 0; i < 4; i ++ ){int x = t.x + dx[i], y = t.y + dy[i];if (x < 0 || x >= n || y < 0 || y >= m) continue; // 出界if (g[x][y] == '#') continue; // 障碍物if (dist[x][y] != -1) continue; // 之前已经遍历过dist[x][y] = dist[t.x][t.y] + 1;if (end == make_pair(x, y)) return dist[x][y];q.push({x, y});}}return -1;}int main(){int T;scanf("%d", &T);while (T -- ){scanf("%d%d", &n, &m);for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);PII start, end;for (int i = 0; i < n; i ++ )for (int j = 0; j < m; j ++ )if (g[i][j] == 'S') start = {i, j};else if (g[i][j] == 'E') end = {i, j};int distance = bfs(start, end);if (distance == -1) puts("oop!");else printf("%d\n", distance);}return 0;}
1113. 红与黑
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。
你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。
请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入格式
输入包括多个数据集合。
每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。
在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。
输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
数据范围
1≤W,H≤20
输入样例:
6 9
….#.
…..#
……
……
……
……
……
#@…#
.#..#.
0 0
输出样例:
45
#include <iostream>#include <cstring>#include <algorithm>#include <queue>using namespace std;const int N = 22;int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};char g[N][N];int n,m;bool st[N][N];int dfs(int x,int y){int cnt=1;st[x][y]=true;for (int i = 0; i < 4; i ++ ){int tx = x+dx[i],ty=y+dy[i];if(tx>=0&&tx<n&&ty>=0&&ty<m&&g[tx][ty]=='.'&&!st[tx][ty]){cnt+=dfs(tx,ty);}}return cnt;}struct Node{int x,y;};int bfs(int x,int y){memset(st, 0, sizeof st);queue<Node>q;q.push({x,y});st[x][y] = true;int cnt=1;while(!q.empty()){auto t = q.front();q.pop();for (int i = 0; i < 4; i ++ ){int tx = t.x+dx[i],ty=t.y+dy[i];if(tx>=0&&tx<n&&ty>=0&&ty<m&&g[tx][ty]=='.'&&!st[tx][ty]){cnt+=1;st[tx][ty] = true;q.push({tx,ty});}}}return cnt;}int main(){while(cin>>m>>n,n||m){ //m是x方向,y方向是nint x,y;memset(st,false,sizeof(st));for (int i = 0; i < n; i ++ ){cin>>g[i];}for (int i = 0; i < n; i ++ ){for (int j = 0; j < m; j ++ ){if(g[i][j]=='@'){x=i,y=j;break;}}}cout<<bfs(x,y)<<endl;}}
1224. 交换瓶子
有 N 个瓶子,编号 1∼N,放在架子上。
比如有 5 个瓶子:
2 1 3 5 4
要求每次拿起 2 个瓶子,交换它们的位置。
经过若干次后,使得瓶子的序号为:
1 2 3 4 5
对于这么简单的情况,显然,至少需要交换 2 次就可以复位。
如果瓶子更多呢?你可以通过编程来解决。
输入格式
第一行包含一个整数 N,表示瓶子数量。
第二行包含 N 个整数,表示瓶子目前的排列状况。
输出格式
输出一个正整数,表示至少交换多少次,才能完成排序。
数据范围
1≤N≤10000,
输入样例1:
5
3 1 2 5 4
输出样例1:
3
输入样例2:
5
5 4 3 2 1
输出样例2:
2

// 本质上就是求环的数量#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 10010;int n;int b[N];bool st[N];int main(){scanf("%d", &n);for (int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);int cnt = 0;// 求环的数量for (int i = 1; i <= n; i ++ ) // 从每个点开始遍历if (!st[i]){cnt ++ ;for (int j = i; !st[j]; j = b[j])st[j] = true;}printf("%d\n", n - cnt);return 0;}
树,图,链表
1240. 完全二叉树的权值
给定一棵包含 NN 个节点的完全二叉树,树上每个节点都有一个权值,按从上到下、从左到右的顺序依次是 A1,A2,⋅⋅⋅ANA1,A2,···AN,如下图所示:
现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点权值之和最大?
如果有多个深度的权值和同为最大,请你输出其中最小的深度。
注:根的深度是 11。
输入格式
第一行包含一个整数 NN。
第二行包含 NN 个整数 A1,A2,⋅⋅⋅ANA1,A2,···AN。
输出格式
输出一个整数代表答案。
输入样例:
7 1 6 5 4 3 2 1
输出样例:
2
#include <iostream>#include <cstring>#include <algorithm>typedef long long LL;using namespace std;const int N = 1e5+10;int n,w[N];LL level[N],maxd;// 核心代码int dfs(int u,int d){ // 父节点,当前深度if(u>n){maxd = d;return 0;}level[d] += dfs(u<<1,d+1); // i的左儿子是2*i,右儿子是2*i+1level[d] += dfs((u<<1)+1,d+1);return w[u];}int main(){cin >> n;for (int i = 1; i <= n; i ++ ){cin >> w[i];}dfs(1,2);int max_l = 1;LL max_v = w[1];for (int i = 2; i < maxd; i ++ ){if(level[i]>max_v){max_v = level[i];max_l = i;}}cout << max_l;}
826. 单链表
实现一个单链表,链表初始为空,支持三种操作:
向链表头插入一个数;
删除第 k 个插入的数后面的数;
在第 k 个插入的数后插入一个数。
现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
H x,表示向链表头插入一个数 x。
D k,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
I k x,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 0)。
输出格式
共一行,将整个链表从头到尾输出。
数据范围
1≤M≤100000
输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5
#include <iostream>#include <algorithm>using namespace std;const int N = 100010;int head, e[N], ne[N], idx;void init(){head = -1;}void add_head(int x){e[idx] = x, ne[idx] = head, head = idx ++ ;}void add_k(int k, int x){e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;}void remove(int k){ne[k] = ne[ne[k]];}int main(){init();int m;cin >> m;while (m -- ){char op;int k, x;cin >> op;if (op == 'H'){cin >> x;add_head(x);}else if (op == 'I'){cin >> k >> x;add_k(k - 1, x);}else{cin >> k;if (!k) head = ne[head];else remove(k - 1);}}for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';cout << endl;return 0;}
模拟
3492. 负载均衡
有 n 台计算机,第 i 台计算机的运算能力为 vi。
有一系列的任务被指派到各个计算机上,第 i 个任务在 ai 时刻分配,指定计算机编号为 bi,耗时为 ci 且算力消耗为 di。
如果此任务成功分配,将立刻开始运行,期间持续占用 bi 号计算机 di 的算力,持续 ci 秒。
对于每次任务分配,如果计算机剩余的运算能力不足则输出 −1,并取消这次分配,否则输出分配完这个任务后这台计算机的剩余运算能力。
输入格式
输入的第一行包含两个整数 n,m,分别表示计算机数目和要分配的任务数。
第二行包含 n 个整数 v1,v2,⋅⋅⋅vn,分别表示每个计算机的运算能力。
接下来 m 行每行 4 个整数 ai,bi,ci,di,意义如上所述。数据保证 ai 严格递增,即 ai
输出 m 行,每行包含一个数,对应每次任务分配的结果。
输入样例:
2 6
5 5
1 1 5 3
2 2 2 6
3 1 2 3
4 1 6 1
5 1 3 3
6 1 3 4
输出样例:
2
-1
-1
1
-1
0
样例解释
时刻 1,第 1 个任务被分配到第 1 台计算机,耗时为 5,这个任务时刻 6 会结束,占用计算机 1 的算力 3。
时刻 2,第 2 个任务需要的算力不足,所以分配失败了。
时刻 3,第 1 个计算机仍然正在计算第 1 个任务,剩余算力不足 3,所以失败。
时刻 4,第 1 个计算机仍然正在计算第 1 个任务,但剩余算力足够,分配后剩余算力 1。
时刻 5,第 1 个计算机仍然正在计算第 1,4 个任务,剩余算力不足 4,失败。
时刻 6,第 1 个计算机仍然正在计算第 4 个任务,剩余算力足够,且恰好用完。
#include <iostream>#include <cstring>#include <algorithm>#include <queue>using namespace std;const int N = 200000+10;int n,m;int g[N];struct Node{int id; // 这项任务占用计算机编号int w; // 这项任务消耗的算力int t; // 这项任务持续到t时刻bool operator<(const Node& node)const{return t>node.t;}};priority_queue<Node>q;bool work(int a){while(!q.empty()&&q.top().t<a){auto tt = q.top();q.pop();g[tt.id]+=tt.w;}}int main(){cin >> n >> m;for (int i = 1; i <= n; i ++ ){cin >> g[i];}while (m -- ){int a,b,c,d;cin >> a >> b >> c >>d;work(a);if(g[b]>=d){q.push({b,d,a+c-1});g[b]-=d;cout << g[b]<<endl;}else{cout << -1 <<endl;}}}
思维题








