模拟
NOIP2007 字符串的展开
第一道题目花费的时间是最多的,还wa了几次
需要特别注意的一个特殊情况时
1-a
这个时候a的ASCII是大于1的,需要满足的一个条件是’-'前后的符号属于同一个类型,要么都是数字,要么都是大写字母,要么都是小写字母
其他的情况可以通过测试样例发现
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
int n, m;
string ans;
char toLow(char x) {
if (x >= '0' && x <= '9') {
return x;
} else if (x >= 'a' && x <= 'z') {
return x;
} else if (x >= 'A' && x <= 'Z') {
return (x + 32);
}
return '0';
}
char toUp(char x) {
if (x >= '0' && x <= '9') {
return x;
} else if (x >= 'a' && x <= 'z') {
return (x - 32);
} else if (x >= 'A' && x <= 'Z') {
return x;
}
return '0';
}
// bool isValid(char x) {
// if ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'z') ||
// (x >= 'A' && x <= 'Z'))
// return 1;
// return 0;
// }
int main() {
int a, b, c;
cin >> a >> b >> c;
string s;
cin >> s;
n = s.size();
ans = "";
ans += s[0];
for (int i = 1; i <= n - 2; i++) {
if (s[i] == '-') {
char l = s[i - 1], r = s[i + 1];
if (!(((l >= '0' && l <= '9') && (r >= '0' && r <= '9')) ||
((l >= 'a' && l <= 'z') && (r >= 'a' && r <= 'z')) ||
((l >= 'A' && l <= 'Z') && (r >= 'A' && r <= 'Z')))) {
ans += '-';
continue;
}
string res = "";
// res += l;
if (l == r - 1) {
// ans += l;
// ans += r;
} else if (l >= r) {
// ans += l;
ans += '-';
// ans += r;
} else if (l < r - 1) {
if (a == 1) {
for (char j = l + 1; j <= r - 1; j++) {
for (int k = 1; k <= b; k++) {
res += toLow(j);
}
}
} else if (a == 2) {
for (char j = l + 1; j <= r - 1; j++) {
for (int k = 1; k <= b; k++) {
res += toUp(j);
}
}
} else if (a == 3) {
for (char j = l + 1; j <= r - 1; j++) {
for (int k = 1; k <= b; k++) {
res += '*';
}
}
}
if (c == 1) {
ans += res;
} else if (c == 2) {
reverse(res.begin(), res.end());
ans = ans + res;
}
}
} else {
ans += s[i];
}
}
ans += s[n - 1];
cout << ans << endl;
return 0;
}
NOIP2009 多项式输出
这个题目需要考虑的情况也是挺多的,最好的处理办法是对多项式的第一项和最后一项进行单独处理,而中间的很多项则可以使用一个for循环进行处理
特殊情况1:系数为1或者-1,注意此时如果不是常数项,则不输出;是常数项,直接输出
特殊情况2:总的多项式中只含有一个常数项,此时无论正负都可以直接输出;但是要注意-1的情况
特殊情况3:指数为1的项,多项式中只有一个x,这一点题目中没有给出
其他的情况可以通过测试样例发现,代码中有注释
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e5 + 10;
int n, m, ans;
string res = "";
int a[N];
int main() {
sf(n);
for (int i = 1; i <= n + 1; i++) {
cin >> a[i];
}
bool flg = true; // 注意这里有一个特殊情况,只有一个常数项
for (int i = 1; i <= n; i++) {
if (a[i] != 0) {
flg = false;
break;
}
}
if (a[1] > 0) { // 先判断第一个
if (a[1] != 1)
res += to_string(a[1]); // 注意系数为1的情况
res += "x^";
res += to_string(n);
} else if (a[1] < 0) {
// res += '-';
if (a[1] != -1)
res += to_string(a[1]);
else if (a[1] == -1)
res += '-';
res += "x^";
res += to_string(n);
}
m = n;
for (int i = 2; i <= n; i++) {
if (a[i] > 0) {
res += '+';
if (a[i] != 1)
res += to_string(a[i]);
if (m == 2) {
res += 'x';
continue;
}
res += "x^";
res += to_string(m - 1);
} else if (a[i] < 0) {
// res += '-';//如果是负数,则需要把这个给去掉
if (a[i] != -1)
res += to_string(a[i]);
else if (a[i] == -1)
res += '-';
if (m == 2) {
res += 'x';
continue;
}
res += "x^";
res += to_string(m - 1);
}
m--; // 注意对于每一个系数,都要减一
}
if (a[n + 1] > 0) { // 这是最后一个
if (flg == false) // 注意这里有一个特殊情况,只有一个常数项
res += '+';
res += to_string(a[n + 1]);
} else if (a[n + 1] < 0) {
// res += '-';
res += to_string(a[n + 1]);
}
de(res);
return 0;
}
NOIP2010 机器翻译
这个题目感觉比前面两个简单,因为可以使用队列先进先出的性质。但是需要注意的一点式,queue中没有像map或者set的find()函数,可以再开一个queue队列,在遍历其中一个队列的时候边遍历边pop()弹出,最后再把存储的元素按照原来的顺序弹入到queue中
代码中有注释
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e5 + 10;
int n, m, ans;
string res = "";
int a[N];
queue<int> q;
bool findMy(int x) {
bool flg = false;
int cnt = q.size();
queue<int> q1; // 另建立一个容器
for (int i = 0; i < cnt; i++) {
if (q.front() == x) {
flg = true;
}
q1.push(q.front());
q.pop();
}
// 再把里面的元素返还到queue中
for (int i = 0; i < cnt; i++) {
q.push(q1.front());
q1.pop();
}
return flg;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
sf(a[i]);
}
ans++;
q.push(a[1]);
for (int i = 2; i <= m; i++) {
if (findMy(a[i]) == true)
continue;
else {
ans++;
if (q.size() < n) { // 如果容量此时小于n,则可以继续往里面放
q.push(a[i]);
} else { // 否则,移除队首元素
q.pop();
q.push(a[i]);
}
}
}
de(ans);
return 0;
}
枚举
NC16593 [NOIP2011]铺地毯
一般二维上的题目,最终都会转换为一维来进行处理。
对于每一个地毯,可以根据他的左下角坐标和长宽,得到两个区间(一维):也就是地毯在x轴和y轴上能覆盖的范围。
只要某一个点的x坐标和y坐标同时在这两个区间中,说明一定被这个地毯所覆盖。
同时我们可以倒着进行遍历,因为题目中要找的是最上面的地毯。如果出现,则直接输出。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e4 + 10;
int n, m;
int ans;
struct E {
int u[5];
} e[N];
int main() {
cin >> n;
int x, y, l, r;
for (int i = 1; i <= n; i++) {
sf(x) sf(y) sf(l) sf(r);
e[i].u[1] = x;
e[i].u[2] = x + l;
e[i].u[3] = y;
e[i].u[4] = y + r;
}
cin >> x >> y;
ans = -1;
// 从后往前找,如果此时找到了,直接输出
for (int i = n; i >= 1; i--) {
if ((x >= e[i].u[1] && x <= e[i].u[2]) &&
(y >= e[i].u[3] && y <= e[i].u[4])) {
ans = i;
break;
}
}
de(ans);
return 0;
}
NC16438 回文日期
根据题目给出的两个范围,进行for循环遍历,每次加1:
1、需要判断是否为日期:month在[1,12],day在[1,每个月的天数],注意2月需要根据闰年和平年来进行判断
2、判断是否为回文数
代码中有详细注释
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e5 + 10;
// int n, m, ans;
int ans;
bool notDate(int x) { // 判断是否为题目输入的起始-终止之间的合法日期
int y, m, d; // 1、获取年月日
y = x / 10000;
m = (x / 100) % 100;
d = x % 100;
// 2、根据【年】得到每月的具体天数
int month[13] = {-1, 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) // 判断是否为闰年
month[2] = 29;
else
month[2] = 28;
if (m < 1 || m > 12) // 注意月份是1-12
return 0;
// 3、判断【日】是否符合【年】和【月】的限制条件
if (d < 1 || d > month[m])
return 0;
return 1;
}
bool fun(string x) { // 判断这个8位的字符串是否为回文数
for (int i = 0; i < 4; i++) {
if (x[i] != x[7 - i])
return 0;
}
return 1;
}
int main() {
int x, y;
cin >> x >> y;
string u;
ans = 0;
for (int i = x; i <= y; i++) {
if (!notDate(i))
continue;
u = to_string(i); // 把这个合法的日期转换为字符串
if (fun(u))
ans++;
}
de(ans);
return 0;
}
前缀和、差分
NC16649 校门外的树
这个题目需要用到前缀和思想,(暴力进行遍历的话不知道会不会超时,100*10000)
所有位置置0,在输入题目将要移除树木的区间时,将u[l]-1,将u[r]+1(这个地方不解释,前缀和思想)
最终一次遍历,如果遇到0的位置,说明这个地方的树木没有被移除;如果遇到小于0(可能是-1,-2,-3……)的情况,说明被移除了不只一次,我们不用管具体的数量,只要小于0,就说明被移除了
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e4 + 10;
// int n, m, ans;
int len, n;
int u[N];
int main() {
cin >> len >> n;
int l, r;
for (int i = 1; i <= n; i++) {
cin >> l >> r;
u[l]--;
u[r + 1]++;
}
int ans = 0;
if (u[0] == 0)
ans++;
for (int i = 1; i <= len; i++) {
u[i] += u[i - 1];
if (u[i] == 0)
ans++;
}
de(ans);
return 0;
}
NC24636 值周
这个题目注意范围是1e8,而不是1e9
我第一次做的时候看成了1e9,甚至用map会超时
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e8 + 5;
int n, m, ans;
// map<int, int> a;
ll a[N];
int main() {
cin >> n >> m;
int l, r;
for (int i = 1; i <= m; i++) {
sf(l) sf(r);
a[l]--;
a[r + 1]++;
}
ans = 0;
if (a[0] == 0)
ans++;
// de(99);
for (int i = 1; i <= n; i++) {
a[i] += a[i - 1];
if (a[i] == 0)
ans++;
}
de(ans);
return 0;
}
NC19913 [CQOI2009]中位数图
这个题目很巧妙的一点是,因为题目只关注相对大小这个概念,所以我们只需要把比k大的数置为1,把比k小的数置为-1
1、只看k左边的数:向k的左边进行遍历,求解“前缀和”,如果等于0,说明此时大于和小于的数量相等。
2、只看k左边的数:向k的右部进行遍历,同上一步
3、同时看左边和右边的数:同样的,在求解“前缀和(定义为sum)”的过程中,如果到右边某处时sum值为m,则我们只需要找左边sum值为-m的数量
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e5 + 5;
int n, m, ans;
int a[N];
int b[N * 2];
int main() {
cin >> n >> m;
int x, id;
for (int i = 1; i <= n; i++) {
sf(x);
if (x > m)
a[i] = 1;
else if (x < m)
a[i] = -1;
else if (x == m)
id = i;
}
ans = 0;
int sz = 0; // 向左
for (int i = id - 1; i >= 1; i--) {
sz += a[i];
b[n + sz]++; // 左右两边都有的情况
if (sz == 0)
ans++;
}
sz = 0; // 向右
for (int i = id + 1; i <= n; i++) {
sz += a[i];
ans += b[n - sz]; // 左右两边都有的情况
// 相当于n+(-sz),此时是找左边和它相等数量相反的数字
if (sz == 0)
ans++;
}
de(ans + 1);
return 0;
}
NC20032 激光炸弹
这个题目需要注意的是,对于覆盖范围为r的炸弹,其实它的四条边的覆盖长度为r+1,而我们在进行前缀和求解时,往前减去的面积正好是r(写代码的时候会注意到)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 5e3 + 5;
int n, m, ans;
int u[N][N];
int a[N][N];
int main() {
cin >> n >> m;
int x, y, z;
int len = m;
for (int i = 1; i <= n; i++) {
sf(x) sf(y) sf(z);
x++; // 注意这里的一步,把所有坐标从1开始
y++;
len = max(max(len, x), y); // 获得最大边界
a[x][y] = z;
}
for (int i = 1; i <= len; i++) { // 进入二维前缀和求解
for (int j = 1; j <= len; j++) {
u[i][j] = a[i][j] + u[i - 1][j] + u[i][j - 1] - u[i - 1][j - 1];
}
}
ans = 0;
int t;
// 注意这里的爆炸范围,为r时代表能覆盖(r+1)^2范围内的所有点
// 但是由于边界上不行,所以直接就是r
for (int i = m; i <= len; i++) {
for (int j = m; j <= len; j++) {
t = u[i][j] - u[i - m][j] - u[i][j - m] + u[i - m][j - m];
if (t > ans) // 最大值更新
ans = t;
}
}
de(ans);
return 0;
}
NC207053 二分
这个题目很巧妙的一点是,把每个猜数的结果式子,转换为对区间的操作,进一步可以转换为区间上的前缀和(即只需要操作区间起始的两个位置,而后直接累加)
具体细节和注释见代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 5e3 + 5;
int n, m, ans;
map<int, int> a;
int main() {
cin >> n;
int x;
char y;
for (int i = 1; i <= n; i++) {
cin >> x >> y;
if (y == '.') { // 转换为区间问题
a[x]++;
a[x + 1]--; // 等于,只有一个点满足
} else if (y == '+') {
a[0]++;
a[x]--; // 大于,这个点前面的所有点均满足这个条件
} else if (y == '-') {
a[x + 1]++; // 小于,这个点后面的所有点均满足这个条件
}
}
ans = 0;
int sz = 0;
for (auto t : a) {
sz += t.second; // 进行遍历,当前点的值表示当前最多有几个条件满足
if (sz > ans) {
ans = sz;
}
}
de(ans);
return 0;
}
状压枚举
NC235250 牛可乐的翻转游戏
这个题目需要用到状态压缩和枚举,详细思路见代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 5e2 + 5;
int n, m, ans;
int change[N], pointer[N];
int getNum1(int x) {
int res = 0;
while (x) {
if (x % 2 == 1)
res++;
x >>= 1;
}
return res;
}
int fun(int x[]) {
int res = (1 << 31) - 1;
// 第0行进行一遍全部可能的翻转
for (int i = 0; i < (1 << m); i++) {
change[0] = i; // 第0行即将进行的操作
int cnt = 0;
pointer[0] = x[0]; // 这一句不能少,因为后面需要用到第0行的起始状态
for (int j = 0; j < n; j++) {
// 第j行进行翻转
cnt += getNum1(change[j]);
// 第j行翻转后会影响这一行的棋子颜色:
// 因为change[j]数组为1的地方说明需要翻转,所以先处理当前操作
// 然后处理当前操作后所影响的【左】和【右】两种情况
// 之所以用异或,因为1-0得到1,1-1得到0,正好和棋子翻转的情况一致
pointer[j] = pointer[j] ^ change[j] ^ (change[j] >> 1) ^ ((change[j] << 1) & ((1 << m) - 1));
// 下一行所需要进行的操作位置:即为当前行中1的位置
change[j + 1] = pointer[j];
// 下一行的此时状态
pointer[j + 1] = x[j + 1] ^ change[j];
}
if (pointer[n - 1] == 0) {
res = min(res, cnt);
}
}
return res;
}
int black[N], white[N];
int main() {
cin >> n >> m;
char temp;
// 参考一个博主的思路,用两个数组进行存储
// 直接调用一个函数进行求解
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> temp;
if (temp == '1') {
black[i] = (black[i] << 1) + 1;
white[i] = white[i] << 1;
} else {
black[i] = black[i] << 1;
white[i] = (white[i] << 1) + 1;
}
}
}
ans = min(fun(black), fun(white));
if (ans > n * m) {
de("Impossible\n");
} else {
de(ans);
}
return 0;
}
指针优化
NC23053 月月查华华的手机
DP,巧妙的从后往前进行数组的赋值
需要注意的是字串,只要从主串中挑选出几个字符能够形成某个串即可
本题目参考思路链接:
https://zhuanlan.zhihu.com/p/616443324点击跳转
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e6 + 9;
int n, m, ans;
char s[N], u[N];
int dp[N][30], now[30];
int main() {
memset(dp, -1, sizeof(dp));
memset(now, -1, sizeof(now));
scanf("%s", s + 1);
for (int i = strlen(s + 1); i >= 0; i--) {
// 注意这里的下标临界点需要含有0(无实际意义)
// 因为dp[i][j]代表i之后的,不包括i
for (int j = 0; j < 26; j++) {
dp[i][j] = now[j];
// i之后距离最近的字符(j+'a')的下标
// 记得需要倒过来
}
now[s[i] - 'a'] = i;
}
int T;
cin >> T;
bool flag;
while (T--) {
scanf("%s", u);
flag = true;
int id = 0;
for (int i = 0; i < strlen(u); i++) {
if (dp[id][u[i] - 'a'] == -1) {
flag = false;
break;
} else {
id = dp[id][u[i] - 'a'];
}
}
cout << (flag ? "Yes" : "No") << endl;
}
return 0;
}
一般贪心
NC16783 拼数
原本思路是:冒泡排序,规则是将两个字符串按位比较,大的放前
如果较短的那个是较长的那个的子串,则把较长的放在前面
看下面这个样例
99911 999 87
99977 999 66
按照上面的思路,得到的结果应该是
99911 999 87(错误,应为 999 87 99911)
99977 999 66
所以去看了题解,原来可以直接对合成的两种情况进行判断
我当时怎么就没有想到呢,,,
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e6 + 9;
int n, m, ans;
string u[25];
bool fun(int x, int y) {
string l = "";
l = u[x] + u[y];
string r = "";
r = u[y] + u[x];
// return l < r;
for (int i = 0; i < u[x].size() + u[y].size(); i++) {
if (l[i] < r[i])
return true;
else if (l[i] > r[i]) { // 注意还有这个判断
// 因为如果第一个元素出现了l比r大,那么直接就说明
// l放在前面更大
return false;
}
}
return false;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> u[i];
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (fun(i, j)) { // 把x放在前面更小,所以进行换位
string t = u[i];
u[i] = u[j];
u[j] = t;
}
}
}
for (int i = 0; i < n; i++) {
cout << u[i];
}
return 0;
}
NC16618 排座椅
注意这个题目很离谱的一个地方是,输出结果时,需要按照1-n的顺序来
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 2e3 + 9;
int n, m, ans;
int k, l, d;
struct E {
int u = 0; // 能拆开的对数
int id; // 所处的位置
} f1[N], f2[N]; // 第0维度表示行,第1维度表示列
int u1[N], u2[N];
bool cmp(E a, E b) {
return a.u > b.u;
}
int main() {
cin >> m >> n >> k >> l >> d;
int x, y, w, z;
for (int i = 1; i <= d; i++) {
cin >> x >> y >> w >> z;
if (x == w) { // 在列开辟通道
f2[min(y, z)].u++;
f2[min(y, z)].id = min(y, z);
} else { // 在行开辟通道
f1[min(x, w)].u++;
f1[min(x, w)].id = min(x, w);
}
}
sort(f1 + 1, f1 + n + 1, cmp); // 优先放置拆散对数比较多的
sort(f2 + 1, f2 + m + 1, cmp);
// 注意必须输出k个行和l个列,是0也需要输出
int cnt = 0;
for (int i = 1; i <= n; i++) {
u1[++cnt] = f1[i].id;
k--;
if (k == 0) { // 或者此时次数已经使用完了
break;
}
}
sort(u1 + 1, u1 + cnt + 1);
for (int i = 1; i <= cnt; i++) {
de(u1[i]);
}
Pu;
cnt = 0;
for (int i = 1; i <= m; i++) {
u2[++cnt] = f2[i].id;
l--;
if (l == 0) {
break;
}
}
sort(u2 + 1, u2 + cnt + 1);
for (int i = 1; i <= cnt; i++) {
de(u2[i]);
}
Pu;
return 0;
}
NC200190 矩阵消除游戏
这个题目思路很巧妙
具体细节见代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e2 + 9, mod = 1e9 + 7;
int n, m;
int a[N][N];
int line[N];
int cnt_line;
int cal(int x) {
int res = 0;
for (int i = 0; i < n; i++) {
if (x % 2) {
cnt_line++; // 表示取了多少个行
for (int j = 0; j < m; j++) {
res += a[i][j];
}
} else {
// 这个时候这个行没有被取,所以加到列中
for (int j = 0; j < m; j++) {
line[j] += a[i][j];
}
}
x /= 2;
}
return res;
}
int main() {
int k;
cin >> n >> m >> k;
int sz = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
sz += a[i][j];
}
}
if (k >= m || k >= n) {
cout << sz; // 这个时候直接全部取
return 0;
}
int ans = 0;
int res;
for (int i = 0; i < (1 << n); i++) {
cnt_line = 0;
memset(line, 0, sizeof(line));
res = cal(i); // 取二进制表示的行
if (k - cnt_line < 0 || k - cnt_line > m)
continue; // 注意这一句必须要有,否则就会把所有的都会加上
sort(line, line + m, greater<int>());
// 得到除去这些行后的列
for (int j = 0; j < k - cnt_line; j++) {
res += line[j];
}
ans = max(ans, res);
}
cout << ans << endl;
return 0;
}
NC23036 华华听月月唱歌
注意这个题目很坑的一点是,有可能存在数据区间[l,r]
其中l或者r大于总长度n
(这个坑我调试了很久很久QAQ)
具体细节见代码
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e5 + 9;
int n, m, ans;
struct E {
int l, r;
} e[N];
bool cmp(E a, E b) {
if (a.l == b.l) {
return a.r > b.r;
}
return a.l < b.l;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> e[i].l >> e[i].r;
}
sort(e + 1, e + m + 1, cmp);
ans = 1;
// 注意这里需要判断一下,因为可能会出现没有从1开始的区间
if (e[1].l != 1) {
cout << "-1\n";
return 0;
}
int last = e[1].r; // 已经选了第一个,且根据排序规则,l相同的,r大的排在前面
int rTmp = last;
for (int i = 2; i <= m; i++) {
if (e[i].l <= last + 1) {
rTmp = max(e[i].r, rTmp);
} else {
if (rTmp <= last) { // 没有更新,说明断层了
cout << "-1\n";
return 0;
}
last = rTmp;
i--; // 后退一个
ans++;
}
// 第8个样例一直过不了,后来参考别人提交的AC代码后发现
// 存在给的数据区间[l,r],其中l或者r大于n
if (last >= n) {
cout << ans << endl;
return 0;
}
}
if (rTmp > last) {
ans++;
last = rTmp;
}
if (last == n) {
cout << ans << endl;
} else {
cout << "-1\n";
}
return 0;
}
推公式
NC16561 国王的游戏
这是第三次写这个题目了。具体思路看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 2e4 + 9, mod = 1e9 + 7;
int n, m;
int cheng[N], chu[N], ans[N];
struct E {
int l, r;
} e[N];
bool cmp(E a, E b) {
return (a.l * a.r < b.l * b.r);
}
void times(int x) { // 大数乘法
int t = 0;
for (int i = 1; i <= cheng[0]; i++) {
t = cheng[i] * x + t;
cheng[i] = t % 10;
t /= 10;
}
int id = cheng[0];
while (t) {
cheng[++id] = t % 10;
t /= 10;
}
cheng[0] = id;
}
void divition(int x) { // 大数除法(向下取整)
memset(chu, 0, sizeof(chu)); // 除数需要置为0
int t = 0;
for (int i = cheng[0]; i >= 1; i--) {
t = t * 10 + cheng[i];
chu[i] = t / x;
if (chu[0] == 0 && chu[i] != 0) {
chu[0] = i; // 如果当前商为0,并且得到的商不为0
// 此时i即为最后商的位数
}
t %= x;
}
}
bool compare() {
if (ans[0] == chu[0]) {
for (int i = chu[0]; i >= 1; i--) {
if (chu[i] > ans[i])
return 1;
if (chu[i] < ans[i]) {
return 0;
}
}
}
if (ans[0] < chu[0])
return 1;
if (ans[0] > chu[0]) {
return 0;
}
return -1;
}
void cp() {
ans[0] = chu[0];
for (int i = chu[0]; i >= 1; i--) {
ans[i] = chu[i];
}
}
int main() {
cin >> n;
n++;
for (int i = 1; i <= n; i++) {
cin >> e[i].l >> e[i].r;
}
sort(e + 2, e + n + 1, cmp); // 注意国王的位置不变
cheng[0] = 1; // 位数
cheng[1] = 1; // 因为是乘法,所以用1乘以其他的数
for (int i = 2; i <= n; i++) {
times(e[i - 1].l); // 乘以它前面人的左手数
divition(e[i].r); // 除以自己的右手数
if (compare()) { // 如果大于当前存储的最大值,则更换
cp();
}
}
for (int i = ans[0]; i >= 1; i--) {
printf("%d", ans[i]);
}
return 0;
}
NC25043 Protecting the Flower
刚开始我的思路就是对的,先假想只有两个
但是我在处理时间的时候,弄成了累积时间!
#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
struct E {
int l, r;
double ri;
} e[N];
bool cmp(E a, E b) {
return a.l * b.r < a.r * b.l;
}
int main() {
cin >> n;
ll u = 0;
for (int i = 1; i <= n; i++) {
sf(e[i].l) sf(e[i].r);
e[i].ri = 1.0 * e[i].l / e[i].r;
u += e[i].r;
}
sort(e + 1, e + n + 1, cmp);
ll res = 0;
u -= e[1].r;
for (int i = 2; i <= n; i++) {
res += e[i - 1].l * u * 2;
u -= e[i].r;
}
de(res);
return 0;
}
按位贪心
NC18979 毒瘤xor
这个题目我刚开始的思路也是对的,但是没有考虑到使用前缀和就卡住了
因为是异或。所以从每一位上来看,如果当前区间的所有数字在这一位上的1多
那么我们可能优先把这一位放0,因为是求和,所以异或后这一位为1的数字就会更多
反之如果是0多,那么放1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e5 + 9, mod = 1e9 + 7;
int n, m, ans;
int a[N][35]; // a[i][j]表示第i个数的第j位是否为1
int pre[N][35]; // pre[i][j]表示前i和数字的第j位为1的个数
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
sf(a[i][0]);
for (int j = 1; j <= 31; j++) {
a[i][j] = a[i][0] >> (j - 1) & 1;
// 求解第i个数字的第j位上是否为1
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 31; j++) {
pre[i][j] += a[i][j] + pre[i - 1][j];
// 求解前i个数字的第j位上为1的个数
}
}
int q, l, r;
cin >> q;
int cnt; // 统计这个区间上第i为是1的个数
int x;
while (q--) {
sf(l) sf(r);
x = 0;
for (int i = 1; i <= 31; i++) {
cnt = pre[r][i] - pre[l - 1][i];
if (cnt < (r - l + 1 - cnt)) { // 如果1的个数小于0的个数,当前位置1
x += 1 << (i - 1);
} // 如果1的个数大于0的个数,当前位置0
// 如果1的个数等于0的个数,因为题目要求同等条件下输出最小的,所以置0
}
de(x) Pu;
}
return 0;
}
NC20860 兔子的区间密码
这个题目很巧妙,是一种按位贪心。如果当前位不满足,则继续往下找。
#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
inline ll read() { // 快速快写
ll x = 0, y = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') {
y = -y;
}
c = getchar();
}
while (c >= '0' && c <= '9') {
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * y;
}
int main() {
int T;
cin >> T;
ll a, b;
ll a_cnt, b_cnt;
while (T--) {
a = read();
b = read();
// de(a) de(b) Pu;
if (a == b) { // 特判:如果a等于b,则直接输出0
printf("0\n");
continue;
}
a_cnt = (ll)log2(a); // 得到a转换为二进制有多少位-1
b_cnt = (ll)log2(b);
while (a_cnt == b_cnt && a != 0) {
a -= (1ll << a_cnt);
b -= (1ll << b_cnt);
a_cnt = (ll)log2(a); // 得到a转换为二进制有多少位-1
b_cnt = (ll)log2(b);
} // 找到第一个首位不同位置,如110和1101
// 那么此时111和1000一定包含在里面
// de(b_cnt) Pu;
printf("%lld\n", ((1ll << (b_cnt + 1)) - 1));
// 这里为什么是b_cnt呢?假想一种情况,l和r只有最后一位不一样,那么此时b_cnt还是1
// 但是a_cnt却是log2(0)!!!
}
return 0;
}
NC17857 起床困难综合症
因为题目只需要输出最终的最大值,所以我们设置两个全0和全1的数
从高到低位进行每行的操作,相当于遍历了所有的情况
如果某一位最终由0变为了1,那么最终的结果包含这一位
如果某一位最终由1变为了0,那么这一位在满足小于m的情况下,最终结果也包含这一位
#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
int u[2];
int main() {
cin >> n >> m;
u[1] = -1; // 全部位为1
string op; // 输入的每行操作
int x;
while (n--) {
cin >> op >> x;
// de(op) de(x) Pu;
if (op[0] == 'A') {
u[0] &= x;
u[1] &= x;
} else if (op[0] == 'O') {
u[0] |= x;
u[1] |= x;
} else {
u[0] ^= x;
u[1] ^= x;
}
}
ans = 0;
for (int i = 1 << 30; i; i >>= 1) {
if (u[0] & i) {
ans |= i;
} else if (u[1] & i && i <= m) { // 注意这里应该是else if
// 因为如果我们在0-->1满足的情况下,肯定优先使用这个
// 而不是使用1-->1,因为后者是使得m变小
ans |= i;
m ^= i;
// 这里比如有一种情况是1011……,那么此时我们处理完高位后
// 需要把最高位置0,防止第二位是1的二进制数满足条件
}
}
printf("%d\n", ans);
return 0;
}