ninth
A - Kids Seating
题意:
n个数,大小范围不超过4n,他们必须要满足:
任意两个数的最大公约数不能是1
任意一个数不能被另一个数整除
思路:
由于题目描述有 If there are multiple answers, print any of them. 这样的题一般都是直接输出。如果要满足题意的两个条件,可以想到输出的这一串数必须全都是偶数,这个题从前往后是不如从后往前好想的,所以我们直接从4n倒着依次输出。而从4n开始,第一次输出4n,第二次输出4n-2,在输出4n-4 ··· ··· , 所以输出n个数之后,输出的最小的那个数为2n+2,比4n的一半2n还要大2,所以一定不存在可以被整除的情况。
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; int main() { int t; cin >> t; while (t--) { int n; cin >> n; for (int i = 4 * n; i > 2 * n; i -= 2) cout << i << ' '; cout << endl; } return 0; }
B - Saving the City
题目翻译:
Bertown是一个由n个建筑组成的城市,且这n个城市成一条直线。
城市的保安部门发现一些建筑下面有炸弹。有一张地图可以显示哪些建筑下面有炸弹,该地图是一个长度为n的字符串,如果第 i 个字符是 “1” ,这表示第 i 个建筑下面有炸弹,如果是 “0” ,表示第 i 个建筑下面没有炸弹。
Bertown最厉害的士兵知道怎么在不毁坏建筑的前提下引爆炸弹。当第x个建筑下面的炸弹被引爆时,将会引爆相邻的两个建筑(x - 1和x + 1)下面的炸弹(如果相邻的建筑下面没有炸弹,就不会发生什么)。因此,只要引爆一段连续的炸弹中的任意一个炸弹,就足以引爆这一整段连续的炸弹。引爆一颗炸弹,需要花费a个硬币,你可以执行任意多次这样的操作。
你也可以在一个没有炸弹的建筑下面放置一个炸弹,这需要花费b个硬币,你也可以执行任意多次这样的操作。
你可以以任意顺序执行上面的操作。
你想要引爆所有的炸弹,计算最少需要花费多少硬币。
解题思路:
对于一段连续的炸弹,有两种选择:直接引爆或者连接别的炸弹,再一起爆炸。比如“00111000110”,a=5,b=1时,如果两段炸弹各自引爆,需要花费5+5=10个硬币。如果连接后再引爆,需要花费3+5=8个硬币,显然后者的选择更省钱。因此可以得出结论,如果两段炸弹之间的距离*b≤a,则连接后引爆,否则直接引爆。
我们可以指定引爆第一段连续的炸弹,而第二段炸弹如何引爆取决于其与前一段炸弹之间的距离,之后同样的道理。
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; int a, b , res; string s; void solve() { cin >> a >> b; cin >> s; res = 0; int flag = 0, len = 0;//flag表示第一次遇到1的情况 for (int i = 0; i < s.size(); i++) { if (s[i] == '1') { if (flag == 0)//如果是第一次遇到1,就直接加上a { flag = 1;//之后就不是第一次遇到1了 res += a; } else//之后再遇到1的情况 { //继上一段的爆炸,看看是把这一段直接引爆还是连接两端之间距离 res += min(a, len * b); } len = 0;//这一段引爆后len变为0 看下一段 } if (s[i] == '0') len++; } cout << res << endl; return ; } int main() { int t; cin >> t; while (t--) { solve(); } return 0; }
C - The Delivery Dilemma
题意:
n份菜,可以选择饭店送,也可以自己拿,求总的花费时间最小
思路:
方法一:二分,定下上界是全部菜自己去拿,在此基础上二分,如果当前菜的外卖时间<mid,就让他送,不必花费时间,但是如果>mid,就要自己去拿,算出自己去拿的时间,和mid比较。
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; int a[200010], b[200010]; int t, n; bool check(long long mid) { long long s = 0; for (int i = 0; i < n; i++) if (a[i] > mid) s += b[i]; return mid >= s; } int main() { cin >> t; while (t--) { cin >> n; long long l = 0, r = 0; for (int i = 0; i < n; i++) cin >> a[i]; for (int i = 0; i < n; i++) { cin >> b[i]; r += b[i]; } while (l < r) { long long mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } cout << r << endl; } return 0; }
顺便总结一下关于二分模板及向上取整还是向下取整的问题:
两个模板的区别在于mid的取整(向上还是向下) 即mid = l + r >> 1 还是 mid = l + r + 1 >> 1; 取决于后两行的写法,而后两行的写法取决于题目分析的过程
注意:关于上取整还是下取整:
如果题目中要求最大值,就用上取整,如果要求最小值,就用下取整 模板一:(向下取整) while(l < r) { int mid = l + r >> 1; if(check(mid)) r = mid;//右边界向下 此时mid向下取整(记忆方法) else l = mid + 1; } 模板二:(向上取整) while(l < r) { int mid = l + r + 1 >> 2; if(check(mid)) l = mid;//左边界向上 此时mid向上取整(记忆方法) else r = mid - 1; }
方法二:前缀和,直接枚举哪些需要配送
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; typedef long long ll; const int maxn = 2e5 + 10; struct Node { int a, b; }a[maxn]; ll sum[maxn]; bool cmp(Node a, Node b) { return a.a < b.a; } int main() { int T; cin >> T; while (T--) { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i].a; for (int i = 1; i <= n; i++) cin >> a[i].b; sort(a + 1, a + 1 + n, cmp); for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i].b; ll ans = sum[n]; for (int i = 1; i <= n; i++) ans = min(ans, max(sum[n] - sum[i], 1ll * a[i].a)); cout << ans << endl; } return 0; }
方法三:贪心 + 优先队列
首先,如果配送时间少于自己取的时间,则肯定选择配送
其次,第一步中的配送时间中有一个最大值 M, 而所有配送时间少于 M 的店家都可以选择配送, 将剩下的
配送时间丢进一个小根堆里
最后,把所有剩下的店家自己取的时间加起来得到 SUM, 如果 SUM 比小根堆的堆顶大,则选择堆顶的店家配送,
一定比当前更优,然后更新一下最大时间,直到 SUM 小于堆顶,此时即为最优答案
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<cmath> #include<stack> #include<queue> using namespace std; typedef long long ll; typedef pair<ll, int> P; const int maxn = 200010; int T, n; ll a[maxn], b[maxn]; priority_queue<P, vector<P>, greater<P> > Q; #include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; ll read() { ll s = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { s = s * 10 + ch - '0'; ch = getchar(); } return s * f; } int main(){ T = read(); while(T--){ while(!Q.empty()) Q.pop(); n = read(); for(int i = 1 ; i <= n ; ++i) b[i] = read(); for(int i = 1 ; i <= n ; ++i) a[i] = read(); ll suma = 0, sumb = 0, time = 0; for(int i = 1 ; i <= n ; ++i){ if(a[i] >= b[i]) time = max(time, b[i]); } for(int i = 1 ; i <= n ; ++i){ if(b[i] > time){ suma += a[i]; Q.push(make_pair(b[i], i)); } } if(time >= suma){ printf("%lld\n", time); } else{ while(!Q.empty()){ P p = Q.top(); if(suma >= p.first){ time = max(time, p.first); suma -= a[p.second]; Q.pop(); } else break; } time = max(time, suma); printf("%lld\n", time); } } return 0; }
D - Extreme Subtraction
题目大意:
有一个长度为n的数组a。你可以进行无数次如下操作:
1.a1~ai 减1
2.ai~an减1
问能否使数组中的元素全部变成0;
思路:
转化成一个差分问题。(假设差分数组为ans)要使数组中的全部数都为0,那么差分数组也必须为0且ans[1]=0。
那么我们来看两种操作对于差分数组有何影响:
操作1:ans[1]-1 且ans[i+1]+1。那么我们就可以凭借操作1,将差分数组中的负数变成0,同时减小ans[1]。
操作2:ans[i+1]-1且ans[n+1]+1。那么我们就可以凭借操作2,将差分数组中的正数变成0,而ans[n+1]为多少和我们并没有关系。
在最开始时ans[1]=a[1],所以只要差分数组的负数和的绝对值小于等于a[1],即输出YES,否则输出NO。
因为如果差分数组的负数和的绝对值大于a[1],那么要使后面的数都变成0,ans[1]就会变成负数,而并没有使ans[1]由负数变成0的操作,操作2只能使ans[1]由正数变成0,所以这样并不满足要求。
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N = 3e4 + 10; int a[N]; int ans[N]; int main() { int t; cin >> t; while (t--) { int n; cin >> n; a[0] = 0; for (int i = 1; i <= n; i++) { cin >> a[i]; ans[i] = a[i] - a[i - 1]; } int res = 0; for (int i = 2; i <= n; i++) if (ans[i] < 0) res = res + (-1) * ans[i]; if (res <= a[1]) puts("YES"); else puts("NO"); } return 0; }

浙公网安备 33010602011771号