飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 0x08 总结 - 图1 个把手的冰箱。
已知每个把手可以处于以下两种状态之一:打开或关闭。
只有当所有把手都打开时,冰箱才会打开。
把手可以表示为一个 0x08 总结 - 图2 的矩阵,您可以改变任何一个位置 0x08 总结 - 图3 上把手的状态。
但是,这也会使得第 0x08 总结 - 图4 行和第 0x08 总结 - 图5 列上的所有把手的状态也随着改变。
请你求出打开冰箱所需的切换把手的次数最小值是多少。

输入格式
输入一共包含四行,每行包含四个把手的初始状态。
符号 + 表示把手处于闭合状态,而符号 - 表示把手处于打开状态。
至少一个手柄的初始状态是关闭的。

输出格式
第一行输出一个整数 0x08 总结 - 图6,表示所需的最小切换把手次数。
接下来 0x08 总结 - 图7 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。
注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。

数据范围
0x08 总结 - 图8
输入样例:

  1. -+--
  2. ----
  3. ----
  4. -+--

输出样例:

  1. 6
  2. 1 1
  3. 1 3
  4. 1 4
  5. 4 1
  6. 4 3
  7. 4 4

由于仅有 16 个位置,所以通过二进制枚举每个位置的翻转情况,来确定最佳翻转方案。
注意提前预处理 chang[x][y] 数组,表示修改 (x,y) 位置后引起的位置修改。
下面代码将 (x, y) 转换为 x * 4 + y 来处理。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. const int N = 16;
  7. int change[N];
  8. int get(int x, int y) {
  9. return 4 * x + y;
  10. }
  11. int main(){
  12. int s = 0;
  13. for(int i = 0; i < 4; i++){
  14. char buf[5];
  15. scanf("%s", buf);
  16. for(int j = 0; j < 4; j++){
  17. if(buf[j] == '+') s += 1 << get(i, j);
  18. }
  19. }
  20. //cout << s << endl;
  21. for(int i = 0; i < 4; i++){
  22. for(int j = 0; j < 4; j++){
  23. int x = get(i, j);
  24. for(int k = 0; k < 4; k++){
  25. change[x] += (1 << get(i, k)) + (1 << get(k, j));
  26. }
  27. change[x] -= 1 << x;
  28. }
  29. }
  30. vector<pair<int,int>> ans, tmp;
  31. for(int st = 0; st < (1 << 16); st ++){
  32. int t = s;
  33. tmp.clear();
  34. for(int i = 0; i < 16; i++){
  35. if(st >> i & 1) {
  36. int x = i / 4, y = i % 4;
  37. tmp.push_back({x, y});
  38. t ^= change[i];
  39. }
  40. }
  41. if(t == 0 && (ans.empty() || ans.size() > tmp.size())) ans = tmp;
  42. }
  43. cout << ans.size() << "\n";
  44. for(auto &[x, y] : ans) {
  45. cout << x + 1 << " " << y + 1 << "\n";
  46. }
  47. }

占卜DIY

达达学会了使用扑克 DIY 占卜。
方法如下:
一副去掉大小王的扑克共 0x08 总结 - 图9 张,打乱后均分为 0x08 总结 - 图10 堆,编号 0x08 总结 - 图11,每堆 0x08 总结 - 图12 张,其中第 0x08 总结 - 图13 堆称作“生命牌”,也就是说你有 0x08 总结 - 图14 条命。
这里边,0x08 总结 - 图150x08 总结 - 图16 被称作死神。
初始状态下,所有的牌背面朝上扣下。
流程如下:
1.抽取生命牌中的最上面一张(第一张)。
2.把这张牌翻开,正面朝上,放到牌上的数字所对应编号的堆的最上边。(例如抽到 0x08 总结 - 图17,正面朝上放到第 0x08 总结 - 图18 堆牌最上面,又比如抽到 0x08 总结 - 图19,放到第 0x08 总结 - 图20 堆牌最上边,注意是正面朝上放)
3.从刚放了牌的那一堆最底下(最后一张)抽取一张牌,重复第 0x08 总结 - 图21 步。(例如你上次抽了 0x08 总结 - 图22,放到了第二堆顶部,现在抽第二堆最后一张发现是 0x08 总结 - 图23,又放到第 0x08 总结 - 图24 堆顶部………)
4.在抽牌过程中如果抽到 0x08 总结 - 图25,则称死了一条命,就扔掉 0x08 总结 - 图26 再从第 0x08 总结 - 图27 步开始。
5.当发现四条命都死了以后,统计现在每堆牌上边正面朝上的牌的数目,只要同一数字的牌出现 0x08 总结 - 图28 张正面朝上的牌(比如 0x08 总结 - 图290x08 总结 - 图30),则称“开了一对”,当然 0x08 总结 - 图310x08 总结 - 图32 是不算的。
6.统计一共开了多少对,开了 0x08 总结 - 图33 对称作”极凶”,0x08 总结 - 图34 对为“大凶”,0x08 总结 - 图35 对为“凶”,0x08 总结 - 图36 对为“小凶”,0x08 总结 - 图37 对为“中庸”,0x08 总结 - 图38 对“小吉”,0x08 总结 - 图39 对为“吉”,0x08 总结 - 图40 为“大吉”,0x08 总结 - 图41 为“满堂开花,极吉”。
输入格式
一共输入 0x08 总结 - 图42 行数据,每行四个数字或字母,表示每堆牌的具体牌型(不区分花色只区分数字),每堆输入的顺序为从上到下。
为了便于读入,用 0x08 总结 - 图43 代表 0x08 总结 - 图44
同行数字用空格隔开。
输出格式
输出一个整数,代表统计得到的开出的总对数。
输入样例:

  1. 8 5 A A
  2. K 5 3 2
  3. 9 6 0 6
  4. 3 4 3 4
  5. 3 4 4 5
  6. 5 6 7 6
  7. 8 7 7 7
  8. 9 9 8 8
  9. 9 0 0 0
  10. K J J J
  11. Q A Q K
  12. J Q 2 2
  13. A K Q 2

输出样例:

  1. 9

按照题意模拟即可。
注意正面牌不可以再翻,也就是翻过的牌扔掉即可,不用放在最顶端。
数据量较小,可以使用 vector ,deque 。最佳的复杂度应该是list

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. const int N = 14;
  7. deque<int> a[N];
  8. unordered_map<char,int> mp;
  9. int cnt[N];
  10. int main(){
  11. for(int i = 2; i <= 9; i++) mp[char('0' + i)] = i;
  12. mp['0'] = 10;
  13. mp['A'] = 1; mp['J'] = 11; mp['Q'] = 12; mp['K'] = 13;
  14. rep(i,1,13) {
  15. char buf[3];
  16. rep(j,1,4) {
  17. scanf("%s", buf);
  18. int x = mp[buf[0]];
  19. a[i].push_back(x);
  20. }
  21. }
  22. while (a[13].size() > 0)
  23. {
  24. int x = a[13].front(); a[13].pop_front();
  25. while(x != 13) {
  26. cnt[x] ++;
  27. if(a[x].size() == 0) break;
  28. int nx = a[x].back();
  29. a[x].pop_back();
  30. x = nx;
  31. }
  32. }
  33. int rs = 0;
  34. for(int i = 1; i <= 12; i++){
  35. if(cnt[i] == 4) rs ++;
  36. }
  37. cout << rs << endl;
  38. return 0;
  39. }

分形

分形,具有以非整数维形式充填空间的形态特征。
通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。
现在,定义“盒子分形”如下:
一级盒子分形:

  1. X

二级盒子分形:

  1. X X
  2. X
  3. X X

如果用 0x08 总结 - 图45#card=math&code=B%28n%20-%201%29&id=YMtIj) 代表第 0x08 总结 - 图46 级盒子分形,那么第 0x08 总结 - 图47 级盒子分形即为:

  1. B(n - 1) B(n - 1)
  2. B(n - 1)
  3. B(n - 1) B(n - 1)

你的任务是绘制一个 0x08 总结 - 图48 级的盒子分形。
输入格式
输入包含几个测试用例。
输入的每一行包含一个不大于 0x08 总结 - 图49 的正整数 0x08 总结 - 图50,代表要输出的盒子分形的等级。
输入的最后一行为 0x08 总结 - 图51,代表输入结束。
输出格式
对于每个测试用例,使用 X 符号输出对应等级的盒子分形。
请注意 X 是一个大写字母。
每个测试用例后输出一个独立一行的短划线。
输入样例:

  1. 1
  2. 2
  3. 3
  4. 4
  5. -1

输出样例

  1. X
  2. -
  3. X X
  4. X
  5. X X
  6. -
  7. X X X X
  8. X X
  9. X X X X
  10. X X
  11. X
  12. X X
  13. X X X X
  14. X X
  15. X X X X
  16. -
  17. X X X X X X X X
  18. X X X X
  19. X X X X X X X X
  20. X X X X
  21. X X
  22. X X X X
  23. X X X X X X X X
  24. X X X X
  25. X X X X X X X X
  26. X X X X
  27. X X
  28. X X X X
  29. X X
  30. X
  31. X X
  32. X X X X
  33. X X
  34. X X X X
  35. X X X X X X X X
  36. X X X X
  37. X X X X X X X X
  38. X X X X
  39. X X
  40. X X X X
  41. X X X X X X X X
  42. X X X X
  43. X X X X X X X X
  44. -

n = 1 时边长为 1
n = 2 时边长为 3
可以推断出 n 阶边长为 0x08 总结 - 图52
然后以左上角的坐标代入 DFS 绘制即可。
注意字符数组截止字符 '\0' ,以及每组测试数据单独清空。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. const int N = 900;
  7. char s[N][N];
  8. int n;
  9. void dfs(int x, int y, int len) {
  10. if(len == 1) {
  11. s[x][y] = 'X';
  12. return;
  13. }
  14. int sub = len / 3;
  15. dfs(x, y, sub);
  16. dfs(x, y + 2 * sub, sub);
  17. dfs(x + sub, y + sub, sub);
  18. dfs(x + 2 * sub, y, sub);
  19. dfs(x + 2 * sub, y + 2 * sub, sub);
  20. }
  21. int main(){
  22. while(cin >> n) {
  23. if(n == -1) break;
  24. int len = pow(3, n - 1);
  25. rep(i,1,len) {
  26. rep(j,1,len) s[i][j] = ' ';
  27. s[i][len+1] = '\0';
  28. }
  29. dfs(1, 1, len);
  30. rep(i,1,len) {
  31. printf("%s\n", s[i] + 1);
  32. }
  33. puts("-");
  34. }
  35. }

袭击

在与联盟的战斗中屡战屡败后,帝国撤退到了最后一个据点。
依靠其强大的防御系统,帝国击退了联盟的六波猛烈进攻。
经过几天的苦思冥想,联盟将军亚瑟终于注意到帝国防御系统唯一的弱点就是能源供应。
该系统由 0x08 总结 - 图53 个核电站供应能源,其中任何一个被摧毁都会使防御系统失效。
将军派出了 0x08 总结 - 图54 个特工进入据点之中,打算对能源站展开一次突袭。
不幸的是,由于受到了帝国空军的袭击,他们未能降落在预期位置。
作为一名经验丰富的将军,亚瑟很快意识到他需要重新安排突袭计划。
他现在最想知道的事情就是哪个特工距离其中任意一个发电站的距离最短。
你能帮他算出来这最短的距离是多少吗?
输入格式
输入中包含多组测试用例。
第一行输入整数 0x08 总结 - 图55,代表测试用例的数量。
对于每个测试用例,第一行输入整数 0x08 总结 - 图56
接下来 0x08 总结 - 图57 行,每行输入两个整数 0x08 总结 - 图580x08 总结 - 图59,代表每个核电站的位置的 0x08 总结 - 图60 坐标。
在接下来 0x08 总结 - 图61 行,每行输入两个整数 0x08 总结 - 图620x08 总结 - 图63,代表每名特工的位置的 0x08 总结 - 图64 坐标。
输出格式
每个测试用例,输出一个最短距离值,结果保留三位小数。
每个输出结果占一行。
数据范围
0x08 总结 - 图65,
0x08 总结 - 图66
输入样例:

  1. 2
  2. 4
  3. 0 0
  4. 0 1
  5. 1 0
  6. 1 1
  7. 2 2
  8. 2 3
  9. 3 2
  10. 3 3
  11. 4
  12. 0 0
  13. 0 0
  14. 0 0
  15. 0 0
  16. 0 0
  17. 0 0
  18. 0 0
  19. 0 0

输出样例:

  1. 1.414
  2. 0.000

分治
详细题解请参考:AcWing 119. 袭击 - AcWing
这个题目数据很弱,还是有极端的例子可以卡掉这个做法。
比如一半同类点均匀分布在左侧,另外一半均匀分布在右侧。可以想到往深层次递归时,左右两侧的复杂度均是 0x08 总结 - 图67#card=math&code=O%28n%5E2%29&id=b9WpY)。
所以该题讲解分治算法,仅做知识点学习参考。这种方法在随机数据下表现良好。(非恶意构造数据),就像快排的期望复杂度是 0x08 总结 - 图68#card=math&code=O%28n%5Clog%20n%29&id=ljPBp),但是最坏复杂度为 0x08 总结 - 图69#card=math&code=O%28n%5E2%29&id=YAem7)。
另外,解决 K 维空间信息,可以使用 K-D Tree 来维护。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 2e5 + 10;
  4. const double INF = 1e15;
  5. const double eps = 1e-6;
  6. struct node{
  7. double x,y;
  8. bool type;
  9. bool operator <(const node b){
  10. return x < b.x;
  11. }
  12. }points[N],tmp[N];
  13. double inf;
  14. double dist(node a,node b){
  15. if(a.type == b.type) return inf;
  16. double dx = a.x - b.x,dy = a.y - b.y;
  17. return sqrt(dx * dx + dy * dy);
  18. }
  19. double dfs(int l,int r){
  20. if(l >= r) return INF;
  21. int mid = l + r >> 1;
  22. double mid_x = points[mid].x;
  23. double res = min(dfs(l,mid),dfs(mid+1,r));
  24. {
  25. int i = l, j = mid + 1, k = 0;
  26. while(i <= mid && j <= r){
  27. if(points[i].y < points[j].y){
  28. tmp[++k] = points[i++];
  29. }else{
  30. tmp[++k] = points[j++];
  31. }
  32. }
  33. while(i <= mid)tmp[++k] = points[i++];
  34. while(j <= r)tmp[++k] = points[j++];
  35. for(int i=1,j=l;i<=k;i++,j++)points[j] = tmp[i];
  36. }
  37. int k = 0;
  38. for(int i=l;i<=r;i++){
  39. if(points[i].x >= mid_x - res && points[i].x <= mid_x + res){
  40. tmp[++k] = points[i];
  41. }
  42. }
  43. for(int i=1;i<=k;i++){
  44. for(int j=i-1;j>=1 && tmp[i].y - tmp[j].y < res;j--){
  45. res = min(res,dist(tmp[i],tmp[j]));
  46. }
  47. }
  48. inf = min(inf, res);
  49. return res;
  50. }
  51. int main(){
  52. int T,n;
  53. scanf("%d",&T);
  54. while(T--){
  55. scanf("%d",&n);
  56. for(int i=1;i<=n;i++){
  57. scanf("%lf%lf",&points[i].x,&points[i].y);
  58. points[i].type = 0;
  59. }
  60. for(int i=n+1;i<=2*n;i++){
  61. scanf("%lf%lf",&points[i].x,&points[i].y);
  62. points[i].type = 1;
  63. }
  64. n *= 2;
  65. inf = dist(points[1], points[n]);
  66. sort(points + 1,points + 1 + n);
  67. printf("%.3f\n",dfs(1,n));
  68. }
  69. return 0;
  70. }

防线

达达学习数学竞赛的时候受尽了同仁们的鄙视,终于有一天……受尽屈辱的达达黑化成为了黑暗英雄怪兽达达。
就如同中二漫画的情节一样,怪兽达达打算毁掉这个世界。
数学竞赛界的精英 lqr 打算阻止怪兽达达的阴谋,于是她集合了一支由数学竞赛选手组成的超级行动队。
由于队员们个个都智商超群,很快,行动队便来到了怪兽达达的黑暗城堡的下方。
但是,同样强大的怪兽达达在城堡周围布置了一条“不可越过”的坚固防线。
防线由很多防具组成,这些防具分成了 0x08 总结 - 图70 组。
我们可以认为防线是一维的,那么每一组防具都分布在防线的某一段上,并且同一组防具是等距离排列的。
也就是说,我们可以用三个整数 0x08 总结 - 图710x08 总结 - 图720x08 总结 - 图73 来描述一组防具,即这一组防具布置在防线的 0x08 总结 - 图74(0x08 总结 - 图75D%3EE#card=math&code=K%E2%88%88%20Z%EF%BC%8CS%20%2B%20KD%E2%89%A4E%EF%BC%8CS%20%2B%20%28K%20%2B%201%29D%3EE&id=Tc6vM))位置上。
黑化的怪兽达达设计的防线极其精良。
如果防线的某个位置有偶数个防具,那么这个位置就是毫无破绽的(包括这个位置一个防具也没有的情况,因为 0x08 总结 - 图76 也是偶数)。
只有有奇数个防具的位置有破绽,但是整条防线上也最多只有一个位置有奇数个防具。
作为行动队的队长,lqr 要找到防线的破绽以策划下一步的行动。
但是,由于防具的数量太多,她实在是不能看出哪里有破绽。
作为 lqr 可以信任的学弟学妹们,你们要帮助她解决这个问题。
输入格式
输入文件的第一行是一个整数 0x08 总结 - 图77,表示有 0x08 总结 - 图78 组互相独立的测试数据。
每组数据的第一行是一个整数 0x08 总结 - 图79
之后 0x08 总结 - 图80 行,每行三个整数 0x08 总结 - 图81,代表第 0x08 总结 - 图82 组防具的三个参数,数据用空格隔开。
输出格式
对于每组测试数据,如果防线没有破绽,即所有的位置都有偶数个防具,输出一行 “There’s no weakness.”(不包含引号) 。
否则在一行内输出两个空格分隔的整数 0x08 总结 - 图830x08 总结 - 图84,表示在位置 0x08 总结 - 图850x08 总结 - 图86 个防具。当然 0x08 总结 - 图87 应该是一个奇数。
数据范围
防具总数不多于0x08 总结 - 图88,
0x08 总结 - 图89,
0x08 总结 - 图90,
0x08 总结 - 图91,
0x08 总结 - 图92
输入样例:

  1. 3
  2. 2
  3. 1 10 1
  4. 2 10 1
  5. 2
  6. 1 10 1
  7. 1 10 1
  8. 4
  9. 1 10 1
  10. 4 4 1
  11. 1 5 1
  12. 6 10 1

输出样例:

  1. 1 1
  2. There's no weakness.
  3. 4 3

给定一个具体位置,很容易算出对于第 i 组防线,在这个位置之前放置了多少个防线。

由于题目给出了最多只有一个奇数点位置的条件,所以可以通过二分这个位置,在 0x08 总结 - 图93#card=math&code=O%28n%29&id=HaORp) 的时间内判断这个位置之前是否存在奇数点即可。

此题难点在于单调性不容易发现。是一个非常有趣的题目。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. const int N = 200010;
  7. int T, n;
  8. int s[N], e[N], d[N];
  9. bool check(int m) {
  10. ll rs = 0;
  11. rep(i,1,n) {
  12. int r = min(e[i], m);
  13. if(r < s[i]) continue;
  14. rs += (r - s[i]) / d[i] + 1;
  15. }
  16. return rs & 1;
  17. }
  18. int main(){
  19. scanf("%d", &T);
  20. while(T--){
  21. scanf("%d", &n);
  22. rep(i,1,n) {
  23. scanf("%d%d%d", &s[i], &e[i], &d[i]);
  24. }
  25. ll l = 0, r = INT_MAX;
  26. while(l < r) {
  27. int m = (l + r) / 2;
  28. if(check(m)) r = m;
  29. else l = m + 1;
  30. }
  31. int cnt = 0;
  32. rep(i,1,n) {
  33. if(l >= s[i] && l <= e[i] && (l - s[i]) % d[i] == 0) cnt ++;
  34. }
  35. if(cnt & 1)
  36. printf("%lld %d\n", l, cnt);
  37. else
  38. puts("There's no weakness.");
  39. }
  40. }

赶牛入圈

农夫约翰希望为他的奶牛们建立一个畜栏。
这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含 0x08 总结 - 图94 单位的三叶草,来当做它们的下午茶。
畜栏的边缘必须与 0x08 总结 - 图95 轴平行。
约翰的土地里一共包含 0x08 总结 - 图96 单位的三叶草,每单位三叶草位于一个 0x08 总结 - 图97 的土地区域内,区域位置由其左下角坐标表示,并且区域左下角的 0x08 总结 - 图98 坐标都为整数,范围在 0x08 总结 - 图990x08 总结 - 图100 以内。
多个单位的三叶草可能会位于同一个 0x08 总结 - 图101 的区域内,因为这个原因,在接下来的输入中,同一个区域坐标可能出现多次。
只有一个区域完全位于修好的畜栏之中,才认为这个区域内的三叶草在畜栏之中。
请你帮约翰计算一下,能包含至少 0x08 总结 - 图102 单位面积三叶草的情况下,畜栏的最小边长是多少。
输入格式
第一行输入两个整数 0x08 总结 - 图1030x08 总结 - 图104
接下来 0x08 总结 - 图105 行,每行输入两个整数 0x08 总结 - 图1060x08 总结 - 图107,代表三叶草所在的区域的 0x08 总结 - 图108 坐标。
同一行数据用空格隔开。
输出格式
输出一个整数,代表畜栏的最小边长。
数据范围
0x08 总结 - 图109,
0x08 总结 - 图110
输入样例:

  1. 3 4
  2. 1 2
  3. 2 1
  4. 4 1
  5. 5 2

输出样例:

  1. 4

蓄栏必须是正方形的。坐标范围 1~10000,但点数最多只有 500。
求最小边长的蓄栏,使得至少包含 C 单位面积三叶草。
可以想到,最终蓄栏的边上一定会有三叶草。而求解最小边长可以使用二分,当边长固定下来后,可以枚举蓄栏的右下角坐标,并且使用双指针来维护蓄栏的左上角即可。
蓄栏的坐标可以提前离散化,共 500 个点,所以最多会有 1000 个不同的数字。
复杂度是 0x08 总结 - 图111#card=math&code=O%28n%5E2%5Clog%20%5Ctext%7BMAX%7D%29&id=koLAL),n 为离散化之后的数量,MAX为坐标最大值。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. const int N = 1010;
  7. int n, c;
  8. int s[N][N], x[N], y[N];
  9. vector<int> v;
  10. bool check(int m) {
  11. int u = 1, l = 1;
  12. for(int d = 1; d <= n; d ++){
  13. // 双指针调整,注意离散化数组 v 的下标访问要 -1
  14. while(v[d - 1] - v[u - 1] + 1 > m) u ++;
  15. l = 1;
  16. for(int r = 1; r <= n; r ++){
  17. while(v[r - 1] - v[l - 1] + 1 > m) l ++;
  18. if(s[d][r] - s[d][l - 1] - s[u - 1][r] + s[u - 1][l - 1] >= c)
  19. {
  20. return true;
  21. }
  22. }
  23. }
  24. return false;
  25. }
  26. int main(){
  27. ios::sync_with_stdio(false); cin.tie(nullptr);
  28. cin >> c >> n;
  29. rep(i,1,n) {
  30. cin >> x[i] >> y[i];
  31. v.push_back(x[i]);
  32. v.push_back(y[i]);
  33. }
  34. sort(v.begin(), v.end());
  35. v.erase(unique(v.begin(), v.end()), v.end());
  36. rep(i,1,n) {
  37. int a = lower_bound(v.begin(), v.end(), x[i]) - v.begin() + 1;
  38. int b = lower_bound(v.begin(), v.end(), y[i]) - v.begin() + 1;
  39. s[a][b] ++;
  40. }
  41. n = v.size();
  42. rep(i,1,n) rep(j,1,n) s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
  43. int l = 1, r = v.size();
  44. while(l < r) {
  45. int m = (l + r) / 2;
  46. if(check(m)) r = m;
  47. else l = m + 1;
  48. }
  49. cout << l << endl;
  50. return 0;
  51. }

糖果传递

0x08 总结 - 图112 个小朋友坐成一圈,每人有 0x08 总结 - 图113 个糖果。
每人只能给左右两人传递糖果。
每人每次传递一个糖果代价为 0x08 总结 - 图114
求使所有人获得均等糖果的最小代价。
输入格式
第一行输入一个正整数 0x08 总结 - 图115,表示小朋友的个数。
接下来 0x08 总结 - 图116 行,每行一个整数 0x08 总结 - 图117,表示第 0x08 总结 - 图118 个小朋友初始得到的糖果的颗数。
输出格式
输出一个整数,表示最小代价。
数据范围
0x08 总结 - 图119,
0x08 总结 - 图120,
数据保证一定有解。
输入样例:

  1. 4
  2. 1
  3. 2
  4. 5
  5. 4

输出样例:

  1. 4

请参考「七夕祭」一题。
每个数字与平均值作差后,求前缀和。记前缀和数组的中位数是 mid,统计mid与前缀和数组中的每个数字的差的绝对值和,即为答案。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. const int N = 1000010;
  7. int n, a[N];
  8. ll s[N];
  9. int main(){
  10. ios::sync_with_stdio(false); cin.tie(nullptr);
  11. cin >> n;
  12. ll sum = 0;
  13. rep(i,1,n) {
  14. cin >> a[i];
  15. sum += a[i];
  16. }
  17. ll ave = sum / n;
  18. rep(i,1,n) {
  19. s[i] = s[i - 1] + a[i] - ave;
  20. }
  21. int mid = (n + 1) / 2;
  22. nth_element(s + 1, s + mid, s + n + 1);
  23. ll m = s[mid], rs = 0;
  24. rep(i,1,n) {
  25. rs += abs(s[i] - m);
  26. }
  27. cout << rs << endl;
  28. return 0;
  29. }

士兵

格格兰郡的 0x08 总结 - 图121 名士兵随机散落在全郡各地。
格格兰郡中的位置由一对 0x08 总结 - 图122#card=math&code=%28x%2Cy%29&id=wUUvN) 整数坐标表示。
士兵可以进行移动,每次移动,一名士兵可以向上,向下,向左或向右移动一个单位(因此,他的 0x08 总结 - 图1230x08 总结 - 图124 坐标也将加 0x08 总结 - 图125 或减 0x08 总结 - 图126)。
现在希望通过移动士兵,使得所有士兵彼此相邻的处于同一条水平线内,即所有士兵的 0x08 总结 - 图127 坐标相同并且 0x08 总结 - 图128 坐标相邻。
请你计算满足要求的情况下,所有士兵的总移动次数最少是多少。
需注意,两个或多个士兵不能占据同一个位置。
输入格式
第一行输入整数 0x08 总结 - 图129,代表士兵的数量。
接下来的 0x08 总结 - 图130 行,每行输入两个整数 0x08 总结 - 图1310x08 总结 - 图132,分别代表一个士兵所在位置的 0x08 总结 - 图133 坐标和 0x08 总结 - 图134 坐标,第 0x08 总结 - 图135 行即为第 0x08 总结 - 图136 个士兵的坐标 0x08 总结 - 图137#card=math&code=%28x%5Bi%5D%2Cy%5Bi%5D%29&id=UpTsK)。
输出格式
输出一个整数,代表所有士兵的总移动次数的最小值。
数据范围
0x08 总结 - 图138,
0x08 总结 - 图139
输入样例:

  1. 5
  2. 1 2
  3. 2 2
  4. 1 3
  5. 3 -2
  6. 3 3

输出样例:

  1. 8

y 轴上的问题可以看做是「货仓选址」中位数问题。
x 轴上略有些不同。
首先对 x 轴排序,可以想到,第 i 个士兵最终会在第 i 个位置。
假设第 1 个士兵它的 x 的坐标为 a,那么第 i 个士兵将是 a + i - 1。
那么第 i 个士兵要走 0x08 总结 - 图140%7C%20%3D%20%7C(x_i%20-%20i)%20-%20(a%20-%201)%7C#card=math&code=%7Cx_i-%28a%2Bi-1%29%7C%20%3D%20%7C%28x_i%20-%20i%29%20-%20%28a%20-%201%29%7C&id=xGr3g) 单位距离。
0x08 总结 - 图141 看做一个整体,问题又可转换为「货仓选址」。
时间复杂度 0x08 总结 - 图142#card=math&code=O%28n%5Clog%20n%29&id=hL7zZ)

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. const int N = 10010;
  7. int n, x[N], y[N];
  8. int main(){
  9. ios::sync_with_stdio(false); cin.tie(nullptr);
  10. cin >> n;
  11. rep(i,1,n) {
  12. cin >> x[i] >> y[i];
  13. }
  14. sort(x + 1, x + 1 + n);
  15. sort(y + 1, y + 1 + n);
  16. rep(i,1,n) x[i] -= i;
  17. sort(x + 1, x + 1 + n);
  18. int rs = 0, m = (n + 1) / 2;
  19. rep(i,1,n) {
  20. rs += abs(y[i] - y[m]) + abs(x[i] - x[m]));
  21. }
  22. cout << rs << endl;
  23. }

数的进制转换

编写一个程序,可以实现将一个数字由一个进制转换为另一个进制。
这里有 0x08 总结 - 图143 个不同数位 0x08 总结 - 图144
输入格式
第一行输入一个整数,代表接下来的行数。
接下来每一行都包含三个数字,首先是输入进制(十进制表示),然后是输出进制(十进制表示),最后是用输入进制表示的输入数字,数字之间用空格隔开。
输入进制和输出进制都在 0x08 总结 - 图1450x08 总结 - 图146 的范围之内。
(在十进制下)0x08 总结 - 图147 (0x08 总结 - 图148 仍然表示 0x08 总结 - 图149)。
输出格式
对于每一组进制转换,程序的输出都由三行构成。
第一行包含两个数字,首先是输入进制(十进制表示),然后是用输入进制表示的输入数字。
第二行包含两个数字,首先是输出进制(十进制表示),然后是用输出进制表示的输入数字。
第三行为空白行。
同一行内数字用空格隔开。
输入样例:

  1. 8
  2. 62 2 abcdefghiz
  3. 10 16 1234567890123456789012345678901234567890
  4. 16 35 3A0C92075C0DBF3B8ACBC5F96CE3F0AD2
  5. 35 23 333YMHOUE8JPLT7OX6K9FYCQ8A
  6. 23 49 946B9AA02MI37E3D3MMJ4G7BL2F05
  7. 49 61 1VbDkSIMJL3JjRgAdlUfcaWj
  8. 61 5 dl9MDSWqwHjDnToKcsWE1S
  9. 5 10 42104444441001414401221302402201233340311104212022133030

输出样例:

  1. 62 abcdefghiz
  2. 2 11011100000100010111110010010110011111001001100011010010001
  3. 10 1234567890123456789012345678901234567890
  4. 16 3A0C92075C0DBF3B8ACBC5F96CE3F0AD2
  5. 16 3A0C92075C0DBF3B8ACBC5F96CE3F0AD2
  6. 35 333YMHOUE8JPLT7OX6K9FYCQ8A
  7. 35 333YMHOUE8JPLT7OX6K9FYCQ8A
  8. 23 946B9AA02MI37E3D3MMJ4G7BL2F05
  9. 23 946B9AA02MI37E3D3MMJ4G7BL2F05
  10. 49 1VbDkSIMJL3JjRgAdlUfcaWj
  11. 49 1VbDkSIMJL3JjRgAdlUfcaWj
  12. 61 dl9MDSWqwHjDnToKcsWE1S
  13. 61 dl9MDSWqwHjDnToKcsWE1S
  14. 5 42104444441001414401221302402201233340311104212022133030
  15. 5 42104444441001414401221302402201233340311104212022133030
  16. 10 1234567890123456789012345678901234567890

10以内的进制互转是容易,不论是 10 进制转换为 x 进制还是 x 进制转换为 10 进制。
该题需要从 a 进制转换为 b 进制数字,0x08 总结 - 图150
如果你首先把 a 转换为 10 进制,再转换为 b 进制,那么该题需要实现高精度,因为数字很大。
当然我们不想随随便便就写高精度。
设 a 进制数字为 0x08 总结 - 图151,b 进制数字为 0x08 总结 - 图152
问题集中到求解 0x08 总结 - 图1530x08 总结 - 图154%5Cover%20b%5E%7Bi%7D%7D%20%5C%25%20b#card=math&code=bi%20%3D%20%7B%28a%7Bn-1%7Da_%7Bn-2%7D%5Ccdots%20a_0%29%5Cover%20b%5E%7Bi%7D%7D%20%5C%25%20b&id=XUjis)。
所以,可以按照高精度除以低精度的模版来求解 b 进制下的数字。
例如,将一个 long long 存储的数字 a = 123456789012345679 转成数组存储,你或许可以这么写:

  1. ll a = 123456789012345679;
  2. vector<int> b;
  3. while(a) {
  4. b.push_back(a % 10);
  5. a /= 10;
  6. }
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
  4. #define per(i,j,k) for(int i=int(j);i>=int(k);i--)
  5. typedef long long ll;
  6. int main(){
  7. ios::sync_with_stdio(false); cin.tie(nullptr);
  8. int T;
  9. cin >> T;
  10. while(T--){
  11. int A, B;
  12. string line;
  13. cin >> A >> B >> line;
  14. vector<int> a, b;
  15. for(auto c : line) {
  16. if(isdigit(c)) a.push_back(c - '0');
  17. else if(isupper(c)) a.push_back(c - 'A' + 10);
  18. else a.push_back(c - 'a' + 36);
  19. }
  20. reverse(a.begin(), a.end());
  21. // 核心
  22. while(a.size()) {
  23. int t = 0;
  24. // 做除法, 顺便求余数
  25. for(int i = a.size() - 1; i >= 0; i--) {
  26. a[i] += t * A;
  27. t = a[i] % B;
  28. a[i] /= B;
  29. }
  30. b.push_back(t);
  31. while(a.size() && a.back() == 0) a.pop_back();
  32. }
  33. reverse(b.begin(), b.end());
  34. cout << A << ' ' << line << "\n";
  35. line = "";
  36. for(auto c : b) {
  37. if(c <= 9) line += char('0' + c);
  38. else if(c <= 35) line += char('A' + c - 10);
  39. else line += char('a' + c - 36);
  40. }
  41. cout << B << ' ' << line << "\n\n";
  42. }
  43. }

耍杂技的牛

农民约翰的 0x08 总结 - 图155 头奶牛(编号为 0x08 总结 - 图156)计划逃跑并加入马戏团,为此它们决定练习表演杂技。
奶牛们不是非常有创意,只提出了一个杂技表演:
叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。
奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。
0x08 总结 - 图157 头奶牛中的每一头都有着自己的重量 0x08 总结 - 图158 以及自己的强壮程度 0x08 总结 - 图159
一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。
您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。

输入格式
第一行输入整数 0x08 总结 - 图160,表示奶牛数量。
接下来 0x08 总结 - 图161 行,每行输入两个整数,表示牛的重量和强壮程度,第 0x08 总结 - 图162 行表示第 0x08 总结 - 图163 头牛的重量 0x08 总结 - 图164 以及它的强壮程度 0x08 总结 - 图165

输出格式
输出一个整数,表示最大风险值的最小可能值。

数据范围
0x08 总结 - 图166,
0x08 总结 - 图167,
0x08 总结 - 图168

输入样例:

  1. 3
  2. 10 3
  3. 2 5
  4. 3 3

输出样例:

  1. 2

对于两头牛 a 和 牛 b,对比 a 在 b 上面与 b 在 a 上面这两种情况的最大风险值:

0x08 总结 - 图169%2C(sum%2Bw_a)-s_b)~%3F~%20%5Cmax((sum%20-%20s_b)%2C(sum%20%2B%20w_b)%20-%20s_a))%20%5C%5C%0A%5CDownarrow%5C%5C%0Amax(-s_a%2Cw_a-s_b)~%3F~max(-s_b%2Cw_b-s_a)%5C%5C%0A#card=math&code=%5Cmax%28%28sum%20-%20s_a%29%2C%28sum%2Bw_a%29-s_b%29~%3F~%20%5Cmax%28%28sum%20-%20s_b%29%2C%28sum%20%2B%20w_b%29%20-%20s_a%29%29%20%5C%5C%0A%5CDownarrow%5C%5C%0Amax%28-s_a%2Cw_a-s_b%29~%3F~max%28-s_b%2Cw_b-s_a%29%5C%5C%0A&id=GRCwM)

如果 a 应该在 b 上面,那么前式应当小于右式,最终化简为 0x08 总结 - 图170

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. typedef pair<int, int> PII;
  5. const int N = 5e4 + 5;
  6. PII a[N];
  7. int main()
  8. {
  9. int n;
  10. cin >> n;
  11. for(int i = 0; i < n; i ++ )
  12. {
  13. int x, y;
  14. scanf("%d %d", &x, &y);
  15. a[i].first = x + y;
  16. a[i].second = y;
  17. }
  18. sort(a, a + n);
  19. ll res = -1e18, sum = 0;
  20. for(int i = 0; i < n; i ++ )
  21. {
  22. sum -= a[i].second;
  23. res = max(res, sum);
  24. sum += a[i].first;
  25. }
  26. cout << res << endl;
  27. return 0;
  28. }

最大的和

给定一个包含整数的二维矩阵,子矩形是位于整个阵列内的任何大小为 0x08 总结 - 图171 或更大的连续子阵列。
矩形的总和是该矩形中所有元素的总和。
在这个问题中,具有最大和的子矩形被称为最大子矩形。
例如,下列数组:

  1. 0 -2 -7 0
  2. 9 2 -6 2
  3. -4 1 -4 1
  4. -1 8 0 -2

其最大子矩形为:

  1. 9 2
  2. -4 1
  3. -1 8

它拥有最大和 0x08 总结 - 图172
输入格式
输入中将包含一个 0x08 总结 - 图173 的整数数组。
第一行只输入一个整数 0x08 总结 - 图174,表示方形二维数组的大小。
从第二行开始,输入由空格和换行符隔开的 0x08 总结 - 图175 个整数,它们即为二维数组中的 0x08 总结 - 图176 个元素,输入顺序从二维数组的第一行开始向下逐行输入,同一行数据从左向右逐个输入。
数组中的数字会保持在 0x08 总结 - 图177 的范围内。
输出格式
输出一个整数,代表最大子矩形的总和。
数据范围
0x08 总结 - 图178
输入样例:

  1. 4
  2. 0 -2 -7 0 9 2 -6 2
  3. -4 1 -4 1 -1
  4. 8 0 -2

输出样例:

  1. 15

如果枚举子矩阵的左上角和右下角,复杂度将是0x08 总结 - 图179#card=math&code=O%28n%5E4%29&id=bIsSg)。
假设固定了子矩阵的上边界和下边界,此时子矩阵可以压缩成一维,转换为对于一个一维数组求解连续子序列最大值。这个问题是个经典的DP入门题,可以在 0x08 总结 - 图180#card=math&code=O%28n%29&id=YUv7c) 时间内解决。方法是对于每个右边界,去找最满意的左边界。
再算上一开始枚举的上下边界,总体复杂度是 0x08 总结 - 图181#card=math&code=O%28n%5E3%29&id=ky2DS)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 101;
  4. int a[N][N],b[N][N],n,d[N];
  5. int main(){
  6. cin>>n;
  7. for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
  8. for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)b[i][j] = b[i-1][j] + a[i][j];
  9. int res = 0;
  10. for(int i=1;i<=n;i++){
  11. for(int j=i;j<=n;j++){
  12. // i 作为上边界,j 为下边界, 一维数组值为 b[j][k] - b[i-1][k]
  13. memset(d,0,sizeof d);
  14. for(int k=1;k<=n;k++){
  15. d[k] = max(d[k-1] + b[j][k] - b[i-1][k],b[j][k] - b[i-1][k]);
  16. res = max(d[k],res);
  17. }
  18. }
  19. }
  20. cout<<res<<endl;
  21. return 0;
  22. }

任务

今天某公司有 0x08 总结 - 图182 个任务需要完成。
每个任务都有相应的难度级别和完成任务所需时间。
0x08 总结 - 图183 个任务的难度级别为 0x08 总结 - 图184,完成任务所需时间为 0x08 总结 - 图185 分钟。
如果公司完成此任务,他们将获得(0x08 总结 - 图186)美元收入。
该公司有 0x08 总结 - 图187 台机器,每台机器都有最长工作时间和级别。
如果任务所需时间超过机器的最长工作时间,则机器无法完成此任务。
如果任务难度级别超过机器的级别,则机器无法完成次任务。
每台机器一天内只能完成一项任务。
每个任务只能由一台机器完成。
请为他们设计一个任务分配方案,使得该公司能够最大化他们今天可以完成的任务数量。
如果有多种解决方案,他们希望选取赚取利润最高的那种。
输入格式
输入包含几个测试用例。
对于每个测试用例,第一行包含两个整数 0x08 总结 - 图1880x08 总结 - 图189,分别代表机器数量和任务数量。
接下来 0x08 总结 - 图190 行,每行包含两个整数 0x08 总结 - 图191,分别代表机器最长工作时间和机器级别。
再接下来 0x08 总结 - 图192 行,每行包含两个整数 0x08 总结 - 图193,分别代表完成任务所需时间和任务难度级别。
输出格式
对于每个测试用例,输出两个整数,代表公司今天可以完成的最大任务数以及他们将获得的收入。
数据范围
0x08 总结 - 图194,
0x08 总结 - 图195,
0x08 总结 - 图196
输入样例:

  1. 1 2
  2. 100 3
  3. 100 2
  4. 100 1

输出样例:

  1. 1 50004

每个任务所需时间 0x08 总结 - 图197,所需级别 0x08 总结 - 图198,所获收益为 0x08 总结 - 图199,而 0x08 总结 - 图200 最大值仅为 100,所以 0x08 总结 - 图201 的差距即便最大,收益差距也不如 0x08 总结 - 图202 相差 1 所带来的结果更大。
对于每个任务,按照 0x08 总结 - 图203 从大到小考虑,对于 0x08 总结 - 图204 相同的,按照 0x08 总结 - 图205 从大到小考虑。
维护一个多重集合,集合内存放着所有还没有使用的机器,这些机器的最长工作时长都大于等于当前枚举的任务。
然后对每个任务,在多重集中查找级别大于等于该任务的最小机器。如果找不到,则该任务无法完成。

  1. #include <bits/std++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int inf = 0x3f3f3f3f;
  5. const int N = 100000 + 5;
  6. int n,m;
  7. pair<int, int> a[N], b[N];
  8. int main() {
  9. while(cin >> n >> m){
  10. for (int i = 1; i <= n;i++)
  11. scanf("%d%d", &a[i].first, &a[i].second);
  12. for (int i = 1; i <= m;i++)
  13. scanf("%d%d", &b[i].first, &b[i].second);
  14. sort(a + 1, a + 1 + n);
  15. sort(b + 1, b + 1 + m);
  16. multiset<int> st;
  17. ll res = 0, cnt = 0;
  18. for (int i = m, j = n; i >= 1;i--){
  19. while(j >= 1 && a[j].first >= b[i].first)
  20. st.insert(a[j--].second);
  21. auto it = st.lower_bound(b[i].second);
  22. if(it != st.end()){
  23. cnt++;
  24. res += 500 * b[i].first + 2 * b[i].second;
  25. st.erase(it);
  26. }
  27. }
  28. cout << cnt << ' ' << res << endl;
  29. }
  30. return 0;
  31. }

该解法以任务角度去匹配机器,如果反过来使用机器去匹配任务是不行的,无论从小到大还是从大到小去考虑机器,都无法保证每一步贪心选择的最优任务会使得全局最优。