A. Ilya and a Colorful Walk
题意
给n个不同颜色的房子,求出两个不同颜色的房子的最远距离是多少。
关键词
贪心
思路
对于每一个房子,用来和第一个、最后一个房子比较颜色并求距离,用这个距离来更新最大距离就是答案。
代码
#include <bits/stdc++.h> #define Debug 0 #define MAXN 300056 #define MAXM 200 #define MOD 1000000007 #define INF 0x3f3f3f3f #define PI 3.1415926535 #define pb push_back #define SYNC ios::sync_with_stdio(false); //#define FILE_IN "I:\\acm-workspace\\CLion-Project\\in.txt" typedef long long ll; typedef std::pair<int, int> Pair; using namespace std; int arr[MAXN]; int pos[MAXN]; vector<Pair> vt; int ans = 0; int main () { #ifdef FILE_IN freopen(FILE_IN, "r", stdin); #endif SYNC int n; cin>>n; memset(pos, -1,sizeof(pos)); for (int i = 0; i < n; ++i) { cin>>arr[i]; } for (int i = 0; i < n; ++i) { if(arr[i]!=arr[0]) { ans = max(ans, i); } if(arr[i] != arr[n-1]) { ans = max(ans, n-1-i); } } cout<<ans<<endl; #ifdef FILE_IN fclose(stdin); #endif return 0; }
B. Alyona and a Narrow Fridge
题意
给一个宽为2,高为h冰箱,以及n个宽为1,高为ai的瓶子。
可以在冰箱中添加不占空间的夹板,问最多能放多少个瓶子。
关键词
贪心
思路
对瓶子高度降序排序,每次选取最近的两个,以两个中较高的一个的高度放置隔板,直到无法再放下任何瓶子或者瓶子放完。
代码
#include <bits/stdc++.h> #define Debug 0 #define MAXN 300056 #define MAXM 200 #define MOD 1000000007 #define INF 0x3f3f3f3f #define PI 3.1415926535 #define pb push_back #define SYNC ios::sync_with_stdio(false); //#define FILE_IN "I:\\acm-workspace\\CLion-Project\\in.txt" typedef long long ll; typedef std::pair<int, int> Pair; using namespace std; int ans = 0; vector<int> pq; int n,h; int arr[MAXN]; int main () { #ifdef FILE_IN freopen(FILE_IN, "r", stdin); #endif // SYNC cin>>n>>h; int tag = 1; for (int i = 0; i < n; ++i) { cin>>arr[i]; pq.pb(arr[i]); sort(pq.begin(), pq.end(), greater<int>()); int hh = h; for (int j = 0; j <= i; ++j) { int t1 = pq[j]; if(j<i) { t1 = max(t1, pq[j+1]); ++j; } if(hh<t1) { tag = 0; break; }else hh -= t1; } if(!tag) { break; } else { ans = i+1; } } cout<<ans<<endl; #ifdef FILE_IN fclose(stdin); #endif return 0; }
C. Ramesses and Corner Inversion
题意
给定两个n*m的矩阵A和B,问能否在矩阵A中,通过若干次变换一个子矩阵的四个角来得到B矩阵。
关键词
构造、贪心、奇偶
思路
计算每行每列中,有多少个不同的地方,一旦某行或某列出现奇数个不同的,就是No,必须都是偶数才是Yes。
显然,每次变换一个子矩阵的四个角,意味着四个角所在行或者列都会改变2个元素。
进行若干次变换,每行每列改变的元素个数必然也是偶数个。
所以,当某一行或者某一列出现奇数个不同的地方,就无法从矩阵A得到矩阵B。
代码
#include <bits/stdc++.h> #define Debug 0 #define MAXN 556 #define MAXM 200 #define MOD 1000000007 #define INF 0x3f3f3f3f #define PI 3.1415926535 #define pb push_back #define SYNC ios::sync_with_stdio(false); //#define FILE_IN "I:\\acm-workspace\\CLion-Project\\in.txt" typedef long long ll; typedef std::pair<int, int> Pair; using namespace std; bool ans = 1; int n,m; int a[MAXN][MAXN],b[MAXN][MAXN],df[MAXN][MAXN]; int r[MAXN] = {0}, c[MAXN] = {0}; int main () { #ifdef FILE_IN freopen(FILE_IN, "r", stdin); #endif // SYNC cin>>n>>m; for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { cin>>a[i][j]; } } for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { cin>>b[i][j]; df[i][j] = b[i][j] == a[i][j]; if(!df[i][j]) { r[i]++; c[j]++; } } } for (int i = 0; i < n; ++i) { if(r[i] %2== 1) { ans = 0; break; } } for (int i = 0; i < m; ++i) { if(c[i] %2== 1) { ans= 0; break; } } if(ans) cout<<"Yes"<<endl; else cout<<"No"<<endl; #ifdef FILE_IN fclose(stdin); #endif return 0; }
D. Frets On Fire
题意
给定n个序列的起始值。
设第i个序列的起始值为si,该序列之后的每一个数等于前一个数加1(即序列i可描述为:[si,si+1,si+2,……,∞])。
再给出q个询问,每个询问中包含两个整数l和r。
需要求出,在每个序列中,选取[l,r]这个区间中的值,求出现过的数字种类和,即:重复的数字只算一次。
关键词
排序、二分。
思路
对si排序,求差值,再排序,求和。二分查找输入区间长度所在的位置。
在这道题目中,每一个区间内具体选中了哪些数字其实并不重要。
首先对si排序,求出相邻两个si的差值。
对于每一段区间,去分析它在[l,r]这个查询中贡献的数字数量。记[l,r]的区间长度为w,w=r-l+1。对如下三种情况进行分析:
- 如果si+1-si>w,si所代表的区间最多只能贡献w个数。
- 否则如果w>si+1-si,si所代表的区间最多只能贡献si+1-si个数。超过的部分算作si+1所代表的区间所贡献的部分。
- 且,最后一个序列所贡献的数字数量为w,因为它后面没有了其他区间,可以假设其与后面一项差值为∞。
所以,对于区间长度w,可以对之前求的差值进行排序,使用二分快速求出有多少个区间满足第一种情况,多少个满足第二种情况。对第二种情况累加求和,加上第一种情况的数量*w即为答案
用第一个案例的第二个查询举例子:
按照这个思路去编程即可。
代码
#include <bits/stdc++.h> #define Debug 0 #define MAXN 100005 #define MAXM 200 #define MAXK 10000010 #define MOD 6000 #define INF 0x3f3f3f3f #define PI 3.1415926535 #define pb push_back #define SYNC ios::sync_with_stdio(false); //#define FILE_IN "I:\\acm-workspace\\CLion-Project\\in.txt" typedef long long ll; typedef std::pair<int, int> Pair; using namespace std; ll arr[MAXN]; ll num[MAXN]; // 区间差值 ll sum[MAXN] = {0}; // 差值求和 int main () { #ifdef FILE_IN freopen(FILE_IN, "r", stdin); #endif // SYNC int n; cin >> n; for (int i = 0; i < n; ++i) { cin >> arr[i]; } sort(arr, arr + n); // 对输入排序 for (int i = 0; i < n - 1; ++i) { num[i] = arr[i + 1] - arr[i]; } sort(num, num + n - 1); // 对差值排序 for (int i = 0; i < n - 1; ++i) { // 对排序后的差值求和,方便后续直接取值 if (i) sum[i] = sum[i - 1] + num[i]; else sum[i] = num[i]; } int q; cin >> q; for (int i = 0; i < q; ++i) { ll l, r; cin >> l >> r; ll t = r - l + 1; // 找到分界的位置,在这个位置之前都是小于r-l+1的 int ind = upper_bound(num, num + n - 1, t) - num; // 由于使用的upper_bound,所以分别处理分界位置ind为0 // 和ind为n-1(求差值后只剩n-1个数且从0开始,所以n-1为所有区间长度都小于r-l+1)的情况 ll ans = 0; if (ind < n - 1) { if(ind) { // 通常情况 ind--; ans = sum[ind] + (n - 1 - ind) * t; } else { ans = n*t; } } else ans = t + sum[n-2]; cout << ans << endl; } #ifdef FILE_IN fclose(stdin); #endif return 0; }
E. Pavel and Triangles
题意
给定n种长度的棍子,接下来的n个整数,第i个整数表示长度为2i的棍子有多少个。
求出使用这些木棍最多能构成多少个三角形。
每个木棍都只能使用一次,且不可折断。
关键词
贪心
思路
对于当前长度的棍子,优先考虑使用两根当前长度的加一根较短的棍子去构成等腰三角形。无法构成等腰三角形时,再考虑使用三根当前长度的棍子去构成等边三角形。剩下的留给后面构造等腰三角形用。
代码
#include <bits/stdc++.h> #define Debug 0 #define MAXN 300005 #define MAXM 200 #define MAXK 10000010 #define MOD 6000 #define INF 0x3f3f3f3f #define PI 3.1415926535 #define pb push_back #define SYNC ios::sync_with_stdio(false); //#define FILE_IN "I:\\acm-workspace\\CLion-Project\\in.txt" typedef long long ll; typedef std::pair<int, int> Pair; using namespace std; int main () { #ifdef FILE_IN freopen(FILE_IN, "r", stdin); #endif // SYNC int n; cin >> n; ll r = 0; // The remainder ll ans = 0; for (int i = 0; i < n; ++i) { ll t; cin >> t; ll t2 = min(t / 2, r); t -= t2 * 2; r -= t2; ans += t2; ans += t/3; r += t%3; } cout << ans << endl; #ifdef FILE_IN fclose(stdin); #endif return 0; }
G. Get Ready for the Battle
题意
给定n个士兵,可以划分到m个组中,其中有些组可以为空。
再给定m组个敌人,每组敌人生命值为输入的该组人数。
每一轮可以选择自己的士兵去攻击敌人,每个组在每一轮中只可以攻击一次,但可以多个组攻击同一组敌人。每一组的攻击可以造成相当于组中人数的伤害,敌人生命值为零时,这个组就算被击败了(下一轮就不能再攻击这个组了)。
并且自己的士兵分组都不会受到伤害(也就是只有我们打敌人)。
问最少需要多少轮才能将敌人全部击败。
需要输出自己士兵的分组情况和每一轮攻击的安排。
关键词
构造、排序
思路
对敌人的生命值累加求和,并且将前 i 项之和对n求余并保存在一个数组中。将对这个数组最后一项修改为n,然后排序后求差值。这个数组即为己方士兵的分组情况。按照这个分组,依照敌人输入的次序一轮一轮去杀敌即可。
-
最优解法:
- 假设和已知前提:用 hpi 表示第i个敌人的生命值,ki 表示第i个敌人在生命值低于n时的生命值(可能是攻击后的结果,也可能是初始值),sum表示敌人生命之和。我方士兵一轮攻击最高伤害为n。
- 攻击策略:从第一个敌人开始(i=1),在hpi 高于n的时候,显然,这一轮攻击无法击败这个敌人,需要所有伤害都打到它身上;在生命值小于n的时候,即生命值为ki的时候,最好的情况就是我方士兵存在一个伤害恰好为ki的组合,其他的士兵开始针对下一个敌人展开攻击,这样就不会在这个一轮攻击中可以击败的敌人身上浪费伤害,尽可能提高了攻击的效率。当然,在面对最后一个敌人的时候,就不需要考虑这么多了。
- 结论:在最优的攻击策略下,每一轮都能打满n的伤害,所以需要攻击的轮数就是对sum/n向上取整。满足最优的攻击策略只需要对于前m-1组敌人中的每一个ki,在我方士兵的分组中都能找到一个满足的组合即可。
- 求解:
-
补充解释:
- 累加求余:求出上面定义的ki。因为是对累加求余,所以处理了在某一轮攻击中,按照上述的攻击策略将前面的敌人击败后,还攻击了第i个敌人使其生命值低于n的这种情况对ki影响。
可以更形象地理解为,配合后面一步的排序将超过一轮攻击的情况合并到一轮攻击中来。 - 排序:让我们通过ki求出我们需要的分组方案。按照上面的策略,我们只需要知道一共有多少种ki需要我们去考虑,具体出现的位置并不会影响最终的答案,因为我们的策略就是只考虑如何能让我们的士兵分组组合出ki来。
对接下来下一步相减求差值的理解:对于排序后的ki,i为1时,是最少一种敌人情况,直接按照这个分组即可;i>1时(可以拿i=2代入),说明前面的ki-1已经考虑过,只需要考虑从ki-1的组合增加一个小的分组使其得到ki即可,这个小的分组的人数就是ki-ki-1了。 - 末项为n:显然对已排序序列的求差值后,其中各项之和一定等于最后一项。一轮攻击下来最多能造成n的伤害。这里将最后一项修改为n,就是为了保证在后面的步骤中,一轮下来最多有n点伤害。
- 不足补0:一共有m(7)个分组,且题意说明了可以存在空的组,没有分配士兵的组就是0。
- 累加求余:求出上面定义的ki。因为是对累加求余,所以处理了在某一轮攻击中,按照上述的攻击策略将前面的敌人击败后,还攻击了第i个敌人使其生命值低于n的这种情况对ki影响。
代码
#include <bits/stdc++.h> #define Debug 0 #define MAXN 1000005 #define MAXM 200 #define MAXK 10000010 #define MOD 6000 #define INF 0x3f3f3f3f #define PI 3.1415926535 #define pb push_back #define SYNC ios::sync_with_stdio(false); //#define FILE_IN "I:\\acm-workspace\\CLion-Project\\in.txt" typedef long long ll; typedef std::pair<int, int> Pair; using namespace std; int hp[MAXN] = {0}; int b[MAXN] = {0}; ll sum = 0; int main () { #ifdef FILE_IN freopen(FILE_IN, "r", stdin); #endif // SYNC int n, m; scanf("%d %d", &n, &m); for (int i = 1; i <= m; ++i) { scanf("%d", &hp[i]); sum += hp[i]; b[i] = sum % n; } b[m] = n; sort(b + 1, b + 1 + m); for (int i = m; i >= 0; --i) { b[i] = b[i] - b[i - 1]; } printf("%lld\n", (sum - 1) / n + 1); for (int i = 1; i <= m; ++i) { printf("%d%c", b[i], (i==m?'\n':' ')); } int cur = 1; // Evlampy's army group for (int i = 1; i <= m; ++i) { // The enemy currently under attack int t = hp[i]; while (t > 0) { t -= b[cur]; ++cur; printf("%d%c", i, (cur>m?'\n':' ')); if (cur > m) { cur = 1; } } } while (cur > 1 && cur <= m) { cout << 1 << (cur == m ? "\n" : " "); cur++; } #ifdef FILE_IN fclose(stdin); #endif return 0; }