目录
连号区间数
递增三元组(枚举+二分+前缀和)
二分法:O(N*logN)
前缀和:O(N)!!!
特别数的和(特别简单)
错误票据
回文日期
移动距离
日期问题
航班时间
外卖店优先级
归并排序(模板)
逆序对的数量(归并排序的应用)(待回看)
1210. 连号区间数 - AcWing题库
暴力思想:
(1)外层循环枚举左端点
(2)内层循环枚举右端点
(3)并进行判断,判断此时集合的左右端点内的集合是否满足条件
条件为:判断集合中的元素是否排序后的元素之间的差值均为1
注意:集合内只有一个数的情况是满足题目条件的
判断的朴素写法:将数组进行排序,遍历数组,判断前一个数是否与后一个数的差值为1,不难知道该暴力方法的时间复杂度为O(N^3*logN) 快速排序的时间复杂度为N*O(logN)
又因为:1≤N≤10000(1e5)必然会超出时间限制(超过了1e8,如果代码量比较大)
关于优化:
由于注意到排序后,每个响铃元素的差值为1,那么就有这样一个性质:在集合中
最大值-最小值==j-i(集合的右端点的索引-集合左端点的索引)
不难推出来吧^ ^,因为差值为1这个特殊的性质
y总的简明代码:
#include
#include
#include
using namespace std;
const int N = 10010, INF = 100000000;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i];
int res = 0;
for (int i = 0; i < n; i ++ ) // 枚举区间左端点
{
int minv = INF, maxv = -INF;
for (int j = i; j < n; j ++ ) // 枚举区间右端点
{
minv = min(minv, a[j]);
maxv = max(maxv, a[j]);
if (maxv - minv == j - i) res ++ ;
}
}
cout << res << endl;
return 0;
}
注意一下:minv和maxv的位置即可;
1236. 递增三元组 - AcWing题库
暴力解法:
第一层for循环枚举a数组,第二层for循环枚举b数组,第三层for循环枚举c数组
判断条件:a[i]
优化方案:
发现枚举三个数组感觉有点蠢,能不能只枚举一个数组呢?如果可以,那应该是枚举哪一个数组?
直觉上应该选择一个中间的数组,即数组b,因为它可以间接影响到a,c数组
思想:
书接上回,由上已经推出了只遍历b数组,那么我们就应该在a数组中寻找比b[i]小的数,在c数组中寻找比b[i]大的数,那么满足题意的数量就为(a中比b[i]小的数)*(c中比b[i]大的数)
寻找:不难会想到二分法,那么很明显,需要先对数组进行排序,因为我们要搜索的只是a,c数组,所以只需将a,c数组进行排序即可
端点:对于a数组我们要寻找的比目标值小的那个数,所以必然是二段性中的第一段的末尾,因为是从小到大进行排序的
同理,对于c数组,必然是二段性中的第二段的首段,因为是从小到大进行排序
那么就可以知道,在a数组中在第一段的末尾前的数均满足要求,所以个数为left+1
同理,在c数组中第二段首段之后的数均满足要求,所以个数为n-right
特殊情况:当在a数组中找不到比b[i]小的数,就应该将left=-1,同理将right=n
至于为什么?看看上面的个数就知道了
对于满足个数要求,要开long long res,因为特别理想情况会爆int
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], b[N], c[N];
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < n; i++) scanf("%d", &b[i]);
for (int i = 0; i < n; i++) scanf("%d", &c[i]);
sort(a, a + n); //二分需要满足单调性
//sort(b, b + n);
sort(c, c + n);
LL res = 0; //答案可能会很大,会爆int
for (int i = 0; i < n; i++)
{
int l = 0, r = n - 1; //二分查找a数组中最后一个小于b[i]的数的下标
while (l < r)
{
int mid = (l + r + 1) / 2;
if (a[mid] < b[i]) l = mid;
else r = mid - 1;
}
if (a[l] >= b[i]) //如果未找到小于b[i]的数,将x标记为-1,后续计算时 x+1==0
{
l = -1;
}
int x = l;
l = 0, r = n - 1;
while (l < r)
{
int mid = (l + r) / 2;
if (c[mid] > b[i]) r = mid;
else l = mid + 1;
}
if (c[l] <= b[i]) //如果未找到大于b[i]的数,将y标记为n,后续计算时 n-y==0;
{
r = n;
}
int y = r;
res += (LL)(x + 1)*(n - y);
}
printf("%lld\n", res);
return 0;
}
看注释
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 100010;//N为数组中元素的最大值
int n;
int a[N], b[N], c[N];
int as[N];//as[i]表示在a[]中有多少个数小于b[i]
int cs[N];//cs[i]表示在c[]中有多少个数大于b[i]
int cnt[N], s[N];//
int main()
{
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i], a[i]++;
for (int i = 0; i < n; i++) cin >> b[i], b[i]++;
for (int i = 0; i < n; i++) cin >> c[i], c[i]++;
for (int i = 0; i < n; i++) cnt[a[i]]++;//存储a大小对应出现的次数
for (int i = 1; i < N; i++) s[i] = s[i - 1] + cnt[i];//次数的前缀和
for (int i = 0; i < n; i++) as[i] = s[b[i] - 1];//a小于b[i]对应的总次数
memset(cnt, 0, sizeof cnt);
memset(s, 0, sizeof s);
for (int i = 0; i < n; i++) cnt[c[i]]++;
for (int i = 1; i < N; i++) s[i] = s[i - 1] + cnt[i];
for (int i = 0; i < n; i++) cs[i] = s[N - 1] - s[b[i]];//(前缀和)最后面的数-中间数
ll res = 0;
for (int i = 0; i < n; i++) res += (ll)as[i] * cs[i];
cout << res << endl;
return 0;
}
1245. 特别数的和 - AcWing题库
#include
using namespace std;
bool check(int num)
{
bool flag=false;
while(num)
{
int j=num%10;
if(j==2 || j==1 || j==9 || j==0)
{
flag=true;
break;
}
num=num/10;
}
return flag;
}
int main()
{
int num;
cin>>num;
int res=0;
for(int i=1;i<=num;i++)
{
if(check(i)) res+=i;
}
cout<
1204. 错误票据 - AcWing题库
这道题本身不难的:只是简单的要求重数和漏的数而已,难的是输入
思路核心:
记录最大值和最小值,由于题目说了,漏的数不为最小值和最大值,所以漏的数和重数必然在最小数和最大数之间,遍历即可,哈希表的思想
#include
#include
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int ha[N];
int main()
{
int n;
cin>>n;
int minv=INF;
int maxv=-INF;
int tp;
while(cin>>tp)
{
if(tpmaxv) maxv=tp;
ha[tp]++;
}
int ans1=0,ans2=0;
for(int i=minv;i<=maxv;i++)
{
if(ha[i]==0) ans1=i;
if(ha[i]==2) ans2=i;
}
cout<
466. 回文日期 - AcWing题库
题目概述:给定一个起始日期和一个终止日期,输出所有满足回文性质的日期个数
思路一:遍历所有日期,判断其是否为回文串
思路二:由于字符串的格式为8个,所以先遍历前4个数字,再将其构造为回文串,再判断其是否为合法日期
注意:因为字符串的格式为8个数字,所以遍历+调用是否判断回文串又要遍历一遍,很可能导致超时,所以思路二才是我们所采取的较优解
#include
#include
#include
#include
using namespace std;
int months[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
bool check(int date)
{
int year = date / 10000;
int month = date % 10000 / 100;
int day = date % 100;
if (!month || month >= 13 || !day) return false;
if (month != 2 && day > months[month]) return false;
if (month == 2)
{
bool leap = year % 4 == 0 && year % 100 || year % 400 == 0;
if (day > 28 + leap) return false;
}
return true;
}
int main()
{
int date1, date2;
cin >> date1 >> date2;
int res = 0;
for (int i = 1000; i < 10000; i++)
{
int x = i, r = i;//构造后四个:先构造回文串,再判断其是否合法
for (int j = 0; j < 4; j++) r = r * 10 + x % 10, x /= 10;
if (r >= date1 && r <= date2 && check(r)) res++;
}
printf("%d\n", res);
return 0;
}
1219. 移动距离 - AcWing题库
这个叫啥距离公式来着,忘了,但是没关系,很简单,只要找到这个图与C++的二维数组的关系即可,
#include
#include
#include
using namespace std;
int main()
{
int w, m, n;
cin >> w >> m >> n;
m --, n -- ;//为了对应
int x1 = m / w, x2 = n / w;
int y1 = m % w, y2 = n % w;
if (x1 % 2) y1 = w - 1 - y1;//奇偶情况
if (x2 % 2) y2 = w - 1 - y2;
cout << abs(x1 - x2) + abs(y1 - y2) << endl;//公式
return 0;
}
1229. 日期问题 - AcWing题库
普通枚举,没啥可说
#include
#include
#include
#include
using namespace std;
int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool check_valid(int year, int month, int day)
{
if (month == 0 || month > 12) return false;
if (day == 0) return false;
if (month != 2)
{
if (day > days[month]) return false;
}
else
{
int leap = year % 100 && year % 4 == 0 || year % 400 == 0;
if (day > 28 + leap) return false;
}
return true;
}
int main()
{
int a, b, c;
scanf("%d/%d/%d", &a, &b, &c);
for (int date = 19600101; date <= 20591231; date ++ )
{
int year = date / 10000, month = date % 10000 / 100, day = date % 100;
if (check_valid(year, month, day))
{
if (year % 100 == a && month == b && day == c || // 年/月/日
month == a && day == b && year % 100 == c || // 月/日/年
day == a && month == b &&year % 100 == c) // 日/月/年
printf("%d-%02d-%02d\n", year, month, day);
}
}
return 0;
}
1231. 航班时间 - AcWing题库
别把自己给绕进去了:要求的是在飞机上的时间,相当于只是用落地时刻减去起飞时刻,为什么要除以2,因为是两地的时间,(其实用1地的时间即可…………这题目好奇怪)
#include
using namespace std;
int getTime(void)
{
int h1,m1,s1,h2,m2,s2,d=0;
scanf("%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d);
int time=d*24*3600+h2*3600+m2*60+s2-(h1*3600+m1*60+s1);
return time;
}
int main()
{
int t;
scanf("%d",&t);
for(int i = 0; i < t; i++)
{
int time1=getTime();
int time2=getTime();
int t=(time1+time2)/2;
printf("%02d:%02d:%02d\n", t/3600, t/60%60, t%60);
}
return 0;
}
/*
//去乘起飞时间+航行时间+时差=去乘降落时间 (公式一)
//回程起飞时间+航行时间-时差=回程降落时间 (公式二)
//求:航行时间
//已知:去乘起飞时间,回程起飞时间,去乘降落时间,回程降落时间
//根据公式一+公式二:
//去乘起飞时间+回程起飞时间+2*航行时间=去乘降落时间+回程降落时间
//航行时间=(去乘降落时间-去乘起飞时间+回程降落时间-回程起飞时间)/2
*/
1241. 外卖店优先级 - AcWing题库
模拟:注释写得很清楚了
#include
#include
#include
#include
using namespace std;
#define x first
#define y second
using namespace std;
typedef pair PII;
const int N = 100010;
int n, m, T;
int score[N], last[N];
bool st[N];
PII order[N];//订单的时刻和编号
int main()
{
cin >> n >> m >> T;//n是外卖店的数量,m是订单数,T为时刻
//第一关键字:时刻 第二关键字:店铺编号
for (int i = 0; i < m; i++) cin >> order[i].x >> order[i].y;
sort(order, order + m);//按照第一关键字进行排序
for (int i = 0; i < m;)//枚举订单数量
{
int j = i;//判断之后有没有相同的订单数(即时刻和店铺编号相同)
while (j < m && order[j] == order[i]) j++;
int t = order[i].x;
int id = order[i].y;
int cnt = j - i;//相同的订单的数量
i = j;//下一次for循环从j处开始遍历
score[id] -= t - last[id] - 1;//上一个拿到订单的时间last[id]和t之间
//也就是假设上一次的订单时刻是3,此时的订单时刻是6,那么没有订单的时刻就是4,5==6-3-1==2
if (score[id] < 0) score[id] = 0;
if (score[id] <= 3) st[id] = false;//如果为负值更新为0。如果小于等于3
score[id] += cnt * 2;//t时刻拿到订单,并且拿到的数量为cnt
if (score[id] > 5) st[id] = true; //如果大于5,更新优先缓存st[id] = true
last[id] = t;//用这次的得到订单的时间存入上次获取订单的时间
}
for(int i=1;i<=n;i++)//遍历外卖店的数量
if (last[i] < T)//如果最后一次获取订单的时间小于<规定时间
{//如果说最后拿到订单的时刻是5 由于已经确定了最后一次没有订单所以:6-5=1:也就是时刻6没有订单
score[i] -= T - last[i];//那么最后一次计算得分将不会进行-1的操作
if (score[i] <= 3) st[i] = false;
}
int res = 0;
for (int i = 1; i <= n; i++) res += st[i];//遍历符合的外卖店即可
cout << res << endl;
return 0;
}
活动 - AcWing
#include
#include
#include
using namespace std;
const int N = 100010;
int n;
int q[N], w[N];
void merge_sort(int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(l, mid), merge_sort(mid + 1, r);//分治
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)//结束条件:有一个到达边界
if (q[i] < q[j]) w[k++] = q[i++];//小的放入
else w[k++] = q[j++];
while (i <= mid) w[k++] = q[i++];//使它们都到达边界
while (j <= r) w[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++) q[i] = w[j];
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &q[i]);
merge_sort(0, n - 1);
for (int i = 0; i < n; i++) printf("%d ", q[i]);
return 0;
}
活动 - AcWing
#include
using namespace std;
const int N = 1e6 + 10;
int cmp[N];
long long int res = 0;
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int i = l, j = mid + 1,k = 0;
while (i <= mid && j <= r)
{
if (q[i] <= q[j])
{
cmp[k ++] = q[i ++];
}
else
{
res += mid - i + 1;//结果为:从从i到mid的这些数(注意已经类似于有序)
cmp[k ++] = q[j ++];
}
}
//循环出来必有一个到达了端点
while (i <= mid) cmp[k ++] = q[i ++];
while (j <= r) cmp[k ++] = q[j ++];
for (int i = l, j = 0; i <= r; i ++, j ++) q[i] = cmp[j];
}
int main()
{
int n;
cin >> n;
int q[n];
for (int i = 0; i < n; i ++) scanf("%d", &q[i]);
merge_sort(q, 0, n - 1);
cout << res << endl;
return 0;
}