题意:判断从左往右每列取最多一个字母,是否能够取出vika。
思路:贪心思想,如果一个字母能在前面取到,也可以在后面取到,我们应该选择在前面取到,这样给剩下的字母选择的空间更大,更能够取到剩下的字母,更能够取出这个子序列。注意,后面的字母必须在前面的字母取了才能取,这样依次贪心最早出现的位置,可以保证每个字母都是最优的选择,从而保证整体最优。
实现:
string s = "vika";
char mp[25][25];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> mp[i][j];
}
}
int p = 0;
for (int j = 0; j < m; j++) {
for (int i = 0; i < n; i++) {
if (mp[i][j] == s[p] && p < 4) {
p++;
break;
}
}
}
cout << (p == 4 ? "YES\n" : "NO\n");
}
补充说明:关于s[s.size()]的问题,stkovflow上面有解答。传送门,s[s.size()]必定是’\0’
题意:让你构造一个原数组,经过如下操作可以得到已知数组,首先写下第一个数,接下来,每次写下,大于等于前面的数的数。
思路:主要判断一下上一个写的数是不是1,如果不是1,我们添个1,这个1小于前面的数,不会被选中,再再这个1后面添加当前的数,当前的数必定会被选中;如果是1,不添1,直接填当前数即可,当前的数必定被选中。
实现:
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
vector<int> ans;
for (int i = 0; i < n; i++) {
if (i == 0) {
ans.push_back(a[i]);
} else {
if (a[i - 1] != 1) {
ans.push_back(1);
}
ans.push_back(a[i]);
}
}
int sz = ans.size();
cout << sz << '\n';
for (int i = 0; i < sz; i++) {
cout << ans[i] << " \n"[i == sz - 1];
}
}
题意:给出一个单调不升的数组,画成统计图,证明这个图是对称的
思路一:差分,前缀和,首先任何一个数不可能大于n,考虑每段对这些位置的高度的贡献。
实现一:
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
vector<int> sum(n + 1);
for (int i = 0; i < n; i++) {
if (a[i] > n) {
cout << "NO\n";
return;
}
sum[0]++;
sum[a[i]]--;
}
vector<int> b(n);
b[0] = sum[0];
int tmp = b[0];
for (int i = 1; i < n; i++) {
tmp += sum[i];
b[i] = tmp;
}
cout << (a == b ? "YES\n" : "NO\n");
}
思路二:双指针,数组是有序的,我们观察上面的差分情况,按照影响范围依次减去贡献,小于当前位置的结束的都要减去。
实现二:
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0, j = n; i < n; i++) {
while (j > 0 && a[j - 1] <= i) {
j--;
}
if (a[i] != j) {
cout << "NO\n";
return;
}
}
cout << "YES\n";
}
a ∗ b ⩽ x ⟺ a ⩽ x / b ( 均为正整数, / 是整数除法 ) 充分性: 如果 a ∗ b ⩽ x , 记 x = b k + r ( 其中 r < b ) , 那么 x / b = k 反证,如果 k < a ,那么 k + 1 ⩽ a 那么 a b ⩾ ( k + 1 ) b = k b + b > k b + r = x 矛盾,所以, k ⩾ a 即 , a ⩽ x / b 充分性成立 必要性: 若 a ⩽ x / b , 则有 k ⩾ a , 那么 x = b k + r ⩾ a b + r ⩾ a b 必要性成立 所以,整数的整数除法可以和浮点数除法一样移项 , 条件是等价的 \begin{align*} &a*b\leqslant x \iff a\leqslant x ~/~ b(均为正整数,/是整数除法)\\ &充分性:\\ &如果a*b\leqslant x,记x = bk+r(其中rkb+r=x\\ &矛盾,所以,k\geqslant a \\ &即,a\leqslant x ~/~ b\\ &充分性成立 \\ \\ &必要性:\\ &若a\leqslant x ~/~ b,则有k\geqslant{a},那么x=bk+r\geqslant{ab+r} \geqslant{ab}\\ &必要性成立 \\\\ &所以,整数的整数除法可以和浮点数除法一样移项,条件是等价的 \end{align*} a∗b⩽x⟺a⩽x / b(均为正整数,/是整数除法)充分性:如果a∗b⩽x,记x=bk+r(其中r<b),那么x/b=k反证,如果k<a,那么k+1⩽a那么ab⩾(k+1)b=kb+b>kb+r=x矛盾,所以,k⩾a即,a⩽x / b充分性成立必要性:若a⩽x / b,则有k⩾a,那么x=bk+r⩾ab+r⩾ab必要性成立所以,整数的整数除法可以和浮点数除法一样移项,条件是等价的
题意:如果你有若干个雪球,选两个组成一种雪糕(无序对),问组成n种雪糕最少要几种雪糕
思路:首先考虑最多几种雪球,二分答案求出来,再多一种雪球,就会超过。假定此时我们有x种雪球,那么再加一种崭新的雪球必定增加x种雪糕,就会超过。那么我们考虑部分重复,x种雪球0x-1种重复依次,就能得到x2x之间的数。
合法性:此时再少一种雪球无法到达,再少一个重复也无法到达,故是最优的
#include
using namespace std;
void solve() {
long long n;
cin >> n;
long long l = 1, r = 1e18 + 5;
while (l < r) {
long long mid = l + r + 1 >> 1;//查找最后一个符合条件mid * (mid - 1) / 2 <= n的值
if (mid - 1 <= 2 * n / mid) {
l = mid;
} else {
r = mid - 1;
}
}
cout << r + (n - r * (r - 1) / 2) << '\n';
}
题意:给定n, m, d和一个数组a,下标从1开始,初始位置是0,每移动一个位置,会消耗一个d的值。现在让你选择最多m个位置,使得从0出发,经过这五个位置的值的和最大。
思路:首先,我们可以观察到,无论我们选择哪几个位置,最终消耗的总量只和最后一个选择的位置有关。如果选择,1,3,5,那么我们是0->1,1->3,3->5最终消耗的总量就是 5 * d。我们考虑以每个点结束的情况,此时消耗的量是固定的,我们要求最大的值,只需要贪心选取最大的m个正值,当然,不一定有m个正值,就是当前节点的最优的情况。当我们向右移动一个单位后,我们要维护这个堆是当前前缀的最大的m个数,我们比较一下小顶堆堆顶的值是不是小于当前值,小于的话,就用这个新的值代替前面部分的最大的m个值,此时是最优的。
代码:
void solve() {
int n, m, d;
cin >> n >> m >> d;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
priority_queue<int, vector<int>, greater<int>> pq;
long long ans = 0;
long long sum = 0;
for (int i = 0; i < n; i++) {
if (a[i] < 0) continue;
if (pq.size() < m) {
pq.push(a[i]);
sum += a[i];
} else if (pq.size() == m) {
if (pq.top() < a[i]) {
sum -= pq.top();
pq.pop();
pq.push(a[i]);
sum += a[i];
}
}
ans = max(ans, sum - 1ll * d * (i + 1));
}
cout << ans << '\n';
}
题意:有n个怪兽,vika一秒可以生成w个水魔法,f个火魔法,初值均为0,第i个怪兽有si点力量,要想打败它需要至少si点火魔法或者水魔法。求出至少要多少分钟来打败这n个怪兽。
思路:在某一秒之后,水魔法和火魔法可以瞬间消灭所有的怪兽,其中水魔法消灭了若干只怪兽,火魔法消灭了若干怪兽,我们只要算出两种魔法分别消灭的怪兽的力量和,除以每秒产生的魔法数,分别向上取整,取大就能得到,当前怪兽分配的最小时间。我们要用填满背包求出所有可能的组合。
bitset<1000005> bt;
void solve() {
bt.reset();
int w, f;
cin >> w >> f;
int ans = 0x3f3f3f3f;
int n;
cin >> n;
bt[0] = 1;
vector<int> s(n);
int tot = 0;
for (int i = 0; i < n; i++) {
cin >> s[i];
tot += s[i];
}
for (int i = 0; i < n; i++) {
bt |= bt << s[i];
}
for (int i = 0; i <= tot; i++) {
if (bt[i]) {
ans = min(ans, max((i + w - 1) / w, (tot - i + f - 1) / f));
}
}
cout << ans << '\n';
}
题意:给你一个数组,此处有一个机器,输入一个数组,进行如下操作,排序,去重,假如有n个数,分别加上,n , n-1, … 1,再进行上述操作,直至只剩下一个数。现在有q次操作,每次把原数组的第i个数改成x,输出每次操作后的a数组放入机器得到的最后一个数。
思路:首先对于一个数组,最后的答案究竟是什么呢?我们首先排序,可以得到阶梯式上升不降的数组,我们把数划分成每个阶梯,这也就相当于去重,以阶梯为最小变化单位,从右到做分别上升1,2,3…,我们最后想要得到这些阶梯什么时候合并成一个,经过一次操作之后,相邻阶梯的差距都减小1,一些原来差距就为1的相邻阶梯此时就合并在一起了,所以最后操作的次数就是最开始阶梯的最大差值。那么答案是什么呢?我们注意到每次操作都会让最右侧阶梯抬高一格,所以最后答案就是当前原数组的最大值+当前原数组排序后的最大相邻差值。用set维护一下,修改相当于删除原来的数插入新的数,对应地,要删除,插入一些差和数。具体看代码,主要维护一个数的多重集合,一个差的多重集合。我写得应该挺清楚的。
代码:
void solve() {
int n;
cin >> n;
vector<int> a(n);
multiset<int> num;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
a[i] = x;
num.insert(x);
}
multiset<int> sub{0};//单个数的情况
auto last = num.begin();
for (auto it = num.begin(); it != num.end(); it++) {
int d = *it - *last;
if (d > 0) {
sub.insert(d);//存入差
}
last = it;
}
int q;
cin >> q;
for (int u = 0; u < q; u++) {
int i, x;
cin >> i >> x;
i--;
int bef = a[i];//before
a[i] = x;
auto it = num.find(bef);
//相当于lower_bound 若干相同的bef 取第一个的迭代器!
if (next(it) == num.end()) {//如果后面没有数了,删去最后的差
if (it != num.begin()) {//注意是否有上一个迭代器
sub.erase(sub.find(*it - *prev(it)));
}
} else {//后面有数
if (*next(it) != *it) {
//如果相同,阶梯间差的情况没有改变,不必考虑
//相同说明这里有多个bef,删一个不会影响
sub.erase(sub.find(*next(it) - *it));
//说明只有一个数,会改变
if (it != num.begin()) {
// prev it next
//删去it,要删去it相邻的差,增加prev和next的差
sub.erase(sub.find(*it - *prev(it)));
sub.insert(*next(it) - *prev(it));
}
}
}
if (it != num.end())
num.erase(it);//此时还不能插入x!
auto up = num.upper_bound(x);//第一个大于x的位置
if (up == num.end()) {//x最大的话
if (num.begin() != num.end()) {
//如果相等是就是单个数,没有差,如果prev的会越界
sub.insert(x - *prev(num.end()));
}
} else if (up == num.begin()) {
sub.insert(*num.begin() - x);
//x最小,就直接插入一个就行 x begin
} else {
if (*prev(up) != x) {//插中间
//prev x up
sub.erase(sub.find(*up - *prev(up)));
sub.insert(x - *prev(up));
sub.insert(*up - x);
}
}
num.insert(x);
cout << *sub.rbegin() + *num.rbegin() << " \n"[u == q - 1];
}
}