C++程序基础
C++程序基础
头文件
引入标准库
C语言中的math.h,stdio.h(输入输出标准库)在c++中被命名为cmath,cstdio.
#include<cmath>
#include<csdio>
int main(){
double a=1.2;
a=sin(a);
printf("%lf\n",a);//%lf代表a的类型\n是换行
//用格式符%lf输出a,lf表示是double型的
return 0;
}
名字空间namespace
#include<cstdio>
namespace first
{
int a;
void f(){/*...*/}// /*...*/这是省略没有详情不能运行
int g(){/*...*/}
}
namespace second
{
double a;
double f(){/*...*/}
char g;
}
int main() {
first::a=2;
second::a=9.333;
first::a=first::g+second::f();
second::a=first::g()+6.345;
printf("%d\n",first::a);
printf("%lf\n",second::a);
}
具体案例:
#include<cmath>
#include<cstdio>
namespace first
{
int a;
int g(int a){a=a*a;
return a;}
}
namespace second
{
double a;
char g;
}
int main() {
first::a=2;
first::a=first::g(first::a);
second::a=first::g(first::a)+6.345;
printf("%d\n",first::a);
printf("%lf\n",second::a);
}
数值常量
整型常量
int
十进制
八进制:在常数的开头加个0,就表示这是八进制数形式表示的常数
十六进制:在常数的开头加一个数字0和英文字母x,就表示这是十六进制的的常数
浮点数
double
十进制小数形式。如21.233等
指数形式(即浮点形式)3.14e-2=0.0314=3.14*10的-2次方
%8.3f, 表示这个浮点数的最小宽度为8,保留3位小数,当宽度不足时在前面补空格。
%-8.3f,表示最小宽度为8,保留3位小数,当宽度不足时在后面补上空格
%08.3f, 表示最小宽度为8,保留3位小数,当宽度不足时在前面补上0
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int a = 3;
float b = 3.12345678;
double c = 3.12345678;
printf("%05d\n", a);
printf("%8.4f\n", b);
printf("%-8.4f\n",b);
printf("%07.3lf\n", c);
return 0;}
字符
字符型常量
char ch='a'
一个字节,只有一个字符
字符串型常量
字符数组与字符串
C : char str[]="hello world";
C++:string str="hello world";
include< string >头文件
字符串就是字符数组加上结束符'\0'会加\0表示结束标志
可以使用字符串来初始化字符数组,但此时要注意,每个字符串结尾会暗含一个'\0'字符,因此字符数组的长度至少要比字符串的长度多 1 !
\n转义字符占一个字节
常用ASCII值:'A'- 'Z'是65 ~ 90,'a' - 'z'是97 - 122,0 - 9是 48 - 57。
字符可以参与运算,运算时会将其当做整数
#include <iostream>
using namespace std;
int main()
{
char a1[] = {'C', '+', '+'}; // 列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'}; // 列表初始化,含有显示的空字符
char a3[] = "C++"; // 自动添加表示字符串结尾的空字符
char a4[6] = "Daniel"; // 错误:没有空间可以存放空字符
return 0;
}
字符数组的输出
#include <iostream>
using namespace std;
int main()
{
char str[100];
cin >> str; // 输入字符串时,遇到空格或者回车就会停止
cout << str << endl; // 输出字符串时,遇到空格或者回车不会停止,遇到'\0'时停止
printf("%s\n", str);
return 0;
}
对字符数组的常用操作
下面几个函数需要引入头文件:
#include <string.h>
(1) strlen(str)求字符串的长度
(2) strcmp(a, b),比较两个字符串的大小,a < b返回-1,a == b返回0,a > b返回1。这里的比较方式是字典序!
(3) strcpy(a, b),将字符串b复制给从a开始的字符数组。
标准库类型string
字符串的初始化
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1; // 默认初始化,s1是一个空字符串
string s2 = s1; // s2是s1的副本,注意s2只是与s1的值相同,并不指向同一段地址
string s3 = "hiya"; // s3是该字符串字面值的副本
string s4(10, 'c'); // s4的内容是 "cccccccccc"
return 0;
}
注意:不能用printf直接输出string,需要写成:printf(“%s”, s.c_str());
使用getline读取一整行
int main()
{
string s;
string s1(10, 'c'), s2; // s1的内容是 cccccccccc;s2是一个空字符串
getline(cin, s);//输入一整行
cin >> s;
cout << s << endl;
return 0;
}
cin是标准输入流对象,它通常代表从键盘输入数据的流。
当使用getline(cin, s);时,它从标准输入(通常是键盘)读取一行文本,并将其存储到字符串s中。
例如,当你在运行程序时,在命令行中输入一些文本并按下回车键,cin就会接收这些输入的数据。
string的empty和size操作
empty判断字符串是否为空
size返回字符串长度
int main{
string s1, s2 = "abc";
cout << s1.empty() << endl;
cout << s2.empty() << endl;
cout << s2.size() << endl;
return 0;
}
String 的相加
string s4 = s1 + ", "; // 正确:把一个string对象和有一个字面值相加
string s5 = "hello" + ", "; // 错误:两个运算对象都不是string
string s6 = s1 + ", " + "world"; // 正确,每个加法运算都有一个运算符是string
string s7 = "hello" + ", " + s2; // 错误:不能把字面值直接相加,运算是从左到右进行的
std::string 的 resize 成员函数用于改变字符串的大小
#include <iostream>
#include <string>
int main() {
std::string str = "Hello";
str.resize(8);
std::cout << str << std::endl;
// 输出 "Hello ",如果新大小大于当前大小,会用默认字符(通常是 '\0')填充新分配的空间。
str.resize(3);
std::cout << str << std::endl;
// 输出 "Hel",如果新大小小于当前大小,会截断字符串。
return 0;
}
for的迭代器对string的使用
for (char& c: s) c = 'a';
for(auto c:str)c='w';
char& c表示定义了一个字符引用c。使用引用可以直接修改容器中的元素,而不是仅仅创建一个副本。在这里,通过引用c可以直接修改字符串s中的字符。s是要遍历的字符串对象
ASCII值
常用ASCII值:'A'- 'Z'是65 ~ 90,'a' - 'z'是97 - 122,0 - 9是 48 - 57。
数据类型
整型
以二进制的形式表示,每8位一个字节
int 4个字节 -+2的31次方-1
short int 2个字节 -+2的15次方-1
long int 8个字节 -+2的63次方-1
unsigned int 无符号整型,4个字节 0~2的32次方-1
浮点型
float:4个字节
double:8个字节
long double:16个字节
布尔
true
false
一个字节
变量
字母数字和下划线
运算符
算术运算符
C++在运算时对所有的float型数据都按double型数据处理
/:直接舍弃小数点
%:只有整型变量可以进行取模运算,小数可以取模
++
--
==:比较
&&:逻辑与
||:逻辑或
!
位运算符
&:按位与操作按二进制进行“与”运算
|:按位或运算符,按二进制位进行“或”运算
^:异或运算符,按二进制进行异或运算
~:取反运算,按二进制进行取反 1=-2,0=-1
<<:二进制左移运算符,将一个运算对象的各二进制位全部左移若干个(左边的二进制位丢弃,右边补0)
/>>:二进制右移运算符,将一个运算对象的各二进制位全部右移若干个(正数左边补,负数左边补1,右边丢弃)
杂项运算符
? :
,:取一逗号分隔的列表的最后一个表达式的值
sizeof:返回变量的大小(字节数)
优先级
头文件
include < iostream >:输入输出
include < string >:字符串
include<bits/stdc++.h>:万能的头文件
using namespace std;简化代码在头文件的下一行
输入输出
输出
printf
printf使用时最好添加头文件#include<cstdio>
Int、float、double、char等类型的输出格式:
(1) int:%d
(2) float: %f, 默认保留6位小数
(3) double: %lf, 默认保留6位小数
(4) char: %c, 回车也是一个字符,用'\n'表示
cout
cout << 输出内容;
cout << a;
cout << "a\n";
换行
endl:表示换行
cout << endl;
cout << a << endl;
输入
cin >> 输入的变量;
cin >> a;
scanf("%b",&a)
&a代表的是a的地址
#include<iostream>
using namespace std;
int main(){
int a,b;
cin >>a >>b;
cout << a+b << endl;
scanf("%b",&a);
}
循环结构
continue并没有是整个循环终止,break会跳出当前循环,进入下一个循环
选择结果
switch语句
switch(a)
{
case 结果1:执行语句;break
default:
//如果a不与所有的case的值相等那就是default执行此代码
}
break和continue
break:是再次跳转到switch语句开头
break是退出循环
continue是可以直接跳到当前循环体的结尾。作用与if语句类似
#include<iostream>
using namespace std;
int main(){
int a;
scanf("%d",&a);
switch(a){
case 0:
a=-a;
break;
case -1:
break;
case 1:
a++;
break;
}
cout << a<<endl;
return 0;
}
函数
在函数形参
对变量的改动不会影响初始值
#include<iostream>
using namespace std;
int f(int x){
x=100;
}
int main(){
int x=2;
f(x);
cout << x << endl;//会出现2不变
return 0;
}
传引用参数
可以改变实参
int f(int &x){}
#include<iostream>
using namespace std;
int f(int &x){
x=100;
}
int main(){
int x=2;
f(x);
cout << x << endl;//会出现100,改变
return 0;
}
数组
数组形参
在函数中对数组中的值的修改,会影响函数外面的数组。
void print(int *a){}
void print(int a[]){}
void print(int a[10]){}
数组初始化
int b[3][4] = { // 三个元素,每个元素都是大小为4的数组
{0, 1, 2, 3}, // 第1行的初始值
{4, 5, 6, 7}, // 第2行的初始值
{8, 9, 10, 11} // 第3行的初始值
数组的解析
函数的返回值
大多数类型都能用作函数的返回类型。一种特殊的返回类型是void,它表示函数不返回任何值。函数的返回类型不能是数组类型或函数类型,但可以是指向数组或者函数的指针
局部变量和全局变量
局部变量只可以在函数内部使用,全局变量可以在所有函数内使用。当局部变量与全局变量重名时,会优先使用局部变量。
多维数组
多维数组中,除了第一维之外,其余维度的大小必须指定
函数中的常用方法
数组中的函数方法
swap(a[i], a[j]);用于交换数组中的数据
数组字符串转换
转换为字符串
to_string(i)
转换为数字
int res=stoi(str)
long num =stol(str)
long long num=stoll(str)
stol(string to long)用于将字符串转换为long类型,stoll(string to long long)
string中的函数方法
引入头文件:#include <string.h>
char c[]="l love china";
char 数组
(1) strlen(str)求字符串的长度
(2) strcmp(a, b),比较两个字符串的大小,a < b返回-1,a == b返回0,a > b返回1。这里的比较方式是字典序!int strcmp(const char *str1, const char *str2)
(3) strcpy(a, b),将字符串b复制给从a开始的字符数组
string类
(4) str.empty()判断字符串是否为空
(5) str.size()返回字符串长度
(6)std::string 的 resize 成员函数用于改变字符串的大小str.resize(7)
(7)size_t pos = s1.find("world");:在字符串中查找指定的子串,返回第一次出现的位置,如果未找到则返回 std::string::npos。
(8)size_t pos = s1.find_first_of('o');:查找字符串中第一个与指定字符匹配的位置。
(8)插入和删除
s1.insert(5, " beautiful");:在指定位置插入一个字符串。s1.erase(5, 10);:删除从指定位置开始的指定数量的字符。
(9)替换子串
s1.replace(5, 10, " amazing");:将指定范围内的子串替换为另一个字符串
(10)std::string sub = s1.substr(5, 10);:提取从指定位置开始的指定长度的子串
size_t是无符号整数类型通常用于表示对象的大小(例如数组的长度、字符串的长度等)和索引值。
sort排序
sort用于对给定范围内的元素进行排序sort(first, last);
#include<bit/stdc++.h>
using namespace std;
int main(){
int x[22]={22,3,53,21,3};
vector<int> number={2,34,33};
sort(x,x+5);
sort(number.begin(),number.end());
for(int i=0;i<5;i++)cout <<x+i<<endl;
for(int num:number)cout <<num<<endl;
return 0;
}
类和结构体
类
class ps class 名字
类中的变量和函数被统一称为类的成员变量。
使用class来定义
private后面的内容是私有成员变量,在类的外部不能访问;public后面的内容是公有成员变量,在类的外部可以访问。
class Person
{
private:
int age, height;
double money;
string books[100];
public:
string name;
void say()
{
cout << "I'm " << name << endl;
}
int get_age()
{
return age;
}
void add_money(double x)
{
money += x;
}
};
int main()
{
Person c;
c.name = "yxc"; // 正确!访问公有变量
c.age = 18; // 错误!访问私有变量
c.set_age(18); // 正确!set_age()是共有成员变量
c.add_money(100);
c.say();
cout << c.get_age() << endl;
return 0;
}
和java的类是一样使用,person.getname()调用方法
结构体和类的作用是一样的。不同点在于类默认是private,结构体默认是public
结构体
使用struct来定义
结构体和类的作用是一样的。不同点在于类默认是private,结构体默认是public
struct Person
{
private:
int age, height;
double money;
string books[100];
public:
string name;
void say()
{
cout << "I'm " << name << endl;
}
int set_age(int a)
{
age = a;
}
int get_age()
{
return age;
}
void add_money(double x)
{
money += x;
}
} person_a, person_b, persons[100];
指针
指针指向存放变量的值的地址。因此我们可以通过指针来修改变量的值
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int *p = &a;
*p += 5;
cout << a << endl;
return 0;
}
数组名是一种特殊的指针。指针可以做运算:
#include <iostream>
using namespace std;
int main()
{
int a[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i ++ )
cout << *(a + i) << endl;
return 0;
}
引用和指针类似,相当于给变量起了个别名。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int &p = a;
p += 5;
cout << a << endl;
return 0;
}
链表
是一个有指针的结构体
struct Node
{
int val;
Node* next;
} *head;
STL
#include <vector>
vector是变长数组,支持随机访问,不支持在任意位置插入。为了保证效率,元素的增删一般应该在末尾进行。
#include <vector> // 头文件
vector<int> a; // 相当于一个长度动态变化的int数组
vector<int> b[233]; // 相当于第一维长233,第二位长度动态变化的int数组
struct rec{…};
vector<rec> c; // 自定义的结构体类型也可以保存在vector中
声明
#include<vector>
vector<int> a;
vector<int> b[233];
struct rec{
int a;
char b;
};
vector<rec> c;
函数
size和empty
size函数返回vector的实际长度(包含的元素个数),empty函数返回一个bool类型,表明vector是否为空。二者的时间复杂度都是 $O(1)$。
所有的STL容器都支持这两个方法,含义也相同,之后我们就不再重复给出
vector
随机
设置随机种子void srand(int seed);
用种子产生随机数int rand()
时间函数
time(NULL);返回从1970年1月1日到现在的秒数,表示时间
#include<cstdlib>
#include<ctime>
#include<iostream>
using namespace std;
int main(){
srand(time(NULL));
cout << rand() <<endl
}
排序
快速排序
确定分界点:q[1],q[(1+r)/2],q[r]随机
调整范围:小于x的在左边,大于x的在右边
递归处理左右两边
第一个方法
小于的放在一个新的a[]数组中,大于的放在b[]数组中,在把a[]数组先放在p[]数组的前面,在把b[]数组放在后面;
第二个方法
左右各一个指针,从左边开始,小于x右移直到大于等于x不动,右指针开始左移,直到小于等于x,这两个指针所指的数互换,再开始移动直到左右指针相遇
二分
背包问题
问题1
有N件物品和一个容量为V的背包,每件物品只能使用一次,第i件物品的体积为v[i]价值为w[i].求解这些物品装入背包,是这些物品的总体积不超过背包容量且总价值最大
第一行输入两个整数N,V用空格隔开,分别表示物品数量和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值
输出一个整数,表示最大价值。
输入样式
4 5
1 2
2 4
3 4
4 5
输出样式
8
#include<bits/stdc++.h>
using namespace std;
const int n=1010;
int N,V;
int v[n],w[n];
int f[n][n];
int main(){
int res=0;
cin >>N >>V;
for(int i=1;i<=N;i++)cin >>v[i] >>w[i];
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
f[i][j]=f[i-1][j];
if(j>=v[i])
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
for(int i=0;i<=V;i++)
res=max(res,f[N][i]);
cout <<res<<endl;
return 0;
}
i为第几行,j 为还剩多少体积还可用的体积
二维数组,在与第i个选择
不选第i个为f[i][j]=f[i-1][j],选第i个f[i][j]=f[i-1][j-v[i]]
在所有剩余的体积中从0到最大体积,从 f[N][i](即考虑完所有 N 种物品后,不同背包容量 i 下能获得的最大价值)中找出最大值作为最终结果
第一层循环从第 1 种物品开始,逐步遍历到第 N 种物品。在每一次循环中,我们都在尝试将当前物品加入到背包的考虑范围内,以此来逐步更新在不同背包容量下能够获得的最大价值。通过这种方式,我们可以系统地考虑每一种物品对最终结果的影响,从只考虑第 1 种物品开始,一直到考虑完所有的 N 种物品。
#include<bits/stdc++.h>
using namespace std;
const int n=1010;
int N,V;
int v[n],w[n];
int f[n];
int main(){
int res=0;
cin >>N >>V;
for(int i=1;i<=N;i++)cin >>v[i] >>w[i];
for(int i=1;i<=N;i++)
for(int j=V;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
for(int i=0;i<=V;i++)
res=max(res,f[i]);
cout <<res<<endl;
return 0;
}
STL
标准模板库
广义分为:容器,算法,迭代器
容器和算法之间通过迭代器连接
STL所用代码都用模版类和模版函数
STL六大组件:容器,算法,迭代器,仿函数,适配器,空间配置器
容器:数据结构,vector,list,deque,map等
算法:常用算法,find,sort,copy,for_each等
迭代器:容器和算法之间的胶合剂
仿函数:类似函数,为算法的某种策略
适配器:一种修饰容器会仿函数或迭代器接口的东西
空间配置器:负责空间的配置和管理
容器算法和迭代器的初识
容器:
分为序列容器解和关联容器
序列容器:强调值的排序
关联式容器:二叉树结构,没有严格的物理上的顺序关系
算法:
质变算法:会更改区间内的元素,如替换,删除
不质变算法:不更改
迭代器:
读取数据
双向迭代器和随机访问迭代器
#include<vector>
#include<algorithm>
#include<iostream>
#include<stdio.h>
using namespace std;
void Mysort(int it) {
cout << it << endl;
}
void Myvector()
{
vector<int> v;
//vector<>容器
v.push_back(19);
v.push_back(29);
v.push_back(35);
v.push_back(49);
//倒查加入vector中
vector<int>::iterator itBegin = v.begin();//起始迭代器,指向第一个元素
vector<int>::iterator itEnd = v.end();//结束迭代器,指向最后一个元素的下一个位置
//迭代器iterator
//第一种遍历
while (itBegin != itEnd) {
cout << *itBegin << endl;
itBegin++;
}
//第二种遍历方式
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
}
//第三种遍历
for_each(v.begin(), v.end(), Mysort);
//开始区间,结束区间,和运行的函数方法
}
int main() {
Myvector();
}
自定义类型
#include "one.h"
using namespace std;
class pespon {
public:
pespon(string name, int age) {
this->name = name;
this->age = age;
}
public:
string name;
int age;
};
inline void Myvector1() {
vector<pespon> v;
pespon p1 = pespon("aaa", 22);
pespon p2 = pespon("bbb", 23);
pespon p3 = pespon("ccc", 24);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
for (vector<pespon>::iterator it = v.begin(); it != v.end(); it++) {
cout << "姓名:" << it->name << "年龄:" << (*it).age << endl;
}
}
inline void Myvector2() {
vector<pespon*> v;
pespon p1("aaa", 22);
pespon p2("bbb", 23);
pespon p3("ccc", 24);
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
for (vector<pespon *>::iterator it = v.begin(); it != v.end(); it++) {
cout << "姓名:" << (*it)->name << "年龄:" << (*(*it)).age << endl;
}
}
容器叠加容器
inline void Myvector3() {
vector<vector<int>> v;
vector<int> v1;
vector<int> v2;
vector<int> v3;
for (int i = 1; i < 6; i++) {
v1.push_back(i + 1);
v2.push_back(i + 5);
v3.push_back(i + 7);
}
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
for (vector<int>::iterator im = (*it).begin(); im != (*it).end(); im++) {
cout << *im << " ";
}
cout << endl;
}
}
string函数
构造函数
string();空的
string(const char* s);使用字符串s初始化
string(const string& str);使用string对象初始化另一个string对象
string(int n,char c);使用n个c初始化
当 const 修饰普通变量时,它表示该变量是一个常量,其值在初始化后不能被修改。
赋值
s=s1,s.assign()
const char* c = "hellow";
char a = 'a';
string s(c);
string s1(s);
string s2(3, a);
string s3;
string s4;
string s5;
string s6;
s3 = "hellow";
s4.assign("hellow");
s5.assign(s3);
s6.assign(s3,4);//s3中的前4个
//取前几个
追加
s+=s1;s.append()
s1 += "ss";
s1 += s2;
s3.append(s2);
s3.append("www");
s4.append(s2, 2);//截取前两个
s5.append(s3, 2, 1);//从第二个位子开始截取1个字符
查找和代替
s.find("ss");s.rfind()
s.replace(1,3,"222")
string s = "abcdefg";
//find;查找从左往右找
int a = s.find("de");
if (a = -1) {
cout << "未找到" << endl;
}
else {
cout << "位置为:" << a << endl;
}
s.append(s);
//rfind;从右往左查找
int b = s.rfind("de");
if (b = -1) {
cout << "未找到" << endl;
}
else {
cout << "位置为:" << b << endl;
}
s.replace(1, 3, "1111");
//从1号位置起替换3个字符,替换为“1111”;
比较
s.compare(s1);
string s1 = s;
if (s1.compare(s)==0) {
cout << "相等" << endl;
}
else {
cout << "不相等" << endl;
}
读取和写入
string s="hellow";
s[1]='3';
s.at(1)='2'
插入和删除
s.insert();
s.erase();
//插入从1的位置插入111;
s.insert(1, "111");
s.erase(1, 3);
//从1的位置删除3个字符
取子串
s.substr()
string s3 = s.substr(1, 4);
//从s中的1位置截取4个字符;
vector
空间会动态扩展,从这个空间到复制到一个新的空间
构造函数
vector<int> a;
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_back(4);
//区间构造
vector<int> b(a.begin(), a.end());
//10个100,前面是几个
vector<int> c(10, 100);
//复制c的数值
vector<int>d(c);
赋值
//赋值=
vector<int> e;
e= d;
vector<int> f;
//赋值assgin
f.assign(e.begin(), e.end());
f.assign(10, 100);
//10个100
容量和大小
empty():判断容器是否为空
capacity():容器的容量
size():容器中的元素的个数
resize(int num):重新指定容器长度为num
resize(int num,elem):重新指定容器长度为num,若变长以elem填充后续的位置
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
if (v.empty()) {
cout << "为空" << endl;
}
else {
printvector(v);
cout << "不为空" << endl;
cout << "容量为:" << v.capacity() << endl;
cout << "大小为:" << v.size() << endl;
}
v.resize(10, 5);
printvector(v);
cout << "不为空" << endl;
cout << "容量为:" << v.capacity() << endl;
cout << "大小为:" << v.size() << endl;
插入和删除
push_back(ele);添加元素ele
pop_back();删除最后一个元素
inser(const_iterator pos,ele);迭代器指向位置pos插入元素ele
inser(const_iterator pos,int count,ele);迭代器指向位置pos插入count个元素ele
erase(const_iterator pos);删除迭代器指定元素
erase(const_iterator start,const_iterator end)删除迭代器指定区间元素
clear()删除所有元素
v.push_back(2);
v.pop_back();
//尾删
v.insert(v.begin(), 2, 32);
//插入指定位置指定元素
v.erase(v.begin());
//删除指定位置的元素
v.erase(v.begin(),v.end());
v.clear();
//删除所有元素
vector数据的存取
at(int i)返回索引为i的数据
operator[]:返回索引为所指的数据
front():返回第一个数据
back():返回容器中最后一个数据
v[1];
v.at(1);
//第一个元素
v.front();
//最后一个元素
v.back();
vector容器的互换
swap(vec):将vec与本身的元素互换,实质上是指向各自的指针交换
v1.swap(v2);将v1与v2的内容互换
vector<int>(v).swap(v);从而使容量和大小一样匿名内部对象,实现收缩内存大小
预留空间
reserve(int len);容器预留len个空间,预留位置不初始化不可访问
以减少vector在动态扩展容量时的扩展次数
deque容器
基本概念
双端数组,可以对头端进行插入删除
vector对头部的插入和删除效率低,数据越大效率越低
deque:对头部的插入删除比vector快
vector访问元素的速度比deque快
构造函数
deque<int> a;
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_back(4);
//区间构造
deque<int> b(a.begin(), a.end());
//10个100,前面是几个
deque<int> c(10, 100);
//复制c的数值
deque<int>d(c);
赋值
s=s1,s.assign()
//赋值=
deque<int> e;
e= d;
deque<int> f;
//赋值assgin
f.assign(e.begin(), e.end());
f.assign(10, 100);
//10个100
deque的大小操作
empty():判断容器是否为空
size():容器中的元素的个数
resize(int num):重新指定容器长度为num
resize(int num,elem):重新指定容器长度为num,若变长以elem填充后续的位置
没有容量,可以无限放数据
deque<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
if (v.empty()) {
cout << "为空" << endl;
}
else {
printvector(v);
cout << "不为空" << endl;
cout << "大小为:" << v.size() << endl;
}
v.resize(10, 5);
printvector(v);
cout << "不为空" << endl;
cout << "大小为:" << v.size() << endl
插入和删除
两端插入:
push_back(ele);添加元素ele
push_front(elem):在头部添加一个数据
pop_back();删除最后一个元素
pop_front():删除第一个元素
指定位置:
insert( pos,ele);迭代器指向位置pos插入元素ele
insert( pos,n,ele);迭代器指向位置pos插入n个元素ele
insert(pos,beg,end):在pos的位置插入beg到end的数据
erase(const_iterator pos);删除迭代器指定元素
erase(const_iterator start,const_iterator end)删除迭代器指定区间元素
clear()删除所有元素
v.push_back(2);
v.push_front(3);
v.pop_back();
v.pop_front();
//尾删
v.insert(v.begin(), 2, 32);
v.insert(v.begin(),v1.begin(),v1.end());
//插入指定位置指定元素
v.erase(v.begin());
//删除指定位置的元素
v.erase(v.begin(),v.end());
v.clear();
//删除所有元素
插入和删除提供的一定是一个迭代器
数据的存取
d[1],d.at(1),front(),back()
[1];
v.at(1);
//第一个元素
v.front();
//最后一个元素
v.back();
排序
sort(iterator beg,iterator end)//对beg和end区间内的元素进行划分
#include<algorithm>
//标准算法头文件
//排序 默认排序规则 从小到大 升序
//对于支持随机访问的迭代器,都可使用
//vector容器也可以利用sort进行排序
sort(d.begin(),d.end());
rand()%41:
rand():生成一个伪随机数,范围通常是 0 到RAND_MAX(RAND_MAX是一个预定义的常量,通常是 32767)。% 41:取模运算,将rand()生成的随机数对 41 取模,结果是一个 0 到 40 之间的整数。
为伪随机数每次的随机数是相同的
stack容器
栈的结构
先进后出,到栈底从栈顶出不允许有遍历行为,只有栈顶元素能被访问
进栈push出栈pop
stack<T>默认构造
stack(const stack &stk)
数据读取
push(elem)添加元素
pop()从栈顶移除元素
top()返回栈顶元素
大小操作
empty()判断栈是否为空
size()返回栈的大小
#include"one.h"
using namespace std;
void mystack() {
stack<int> s;
for (int i = 1; i < 9; i++) {
s.push(i * 10);
}
stack<int> t = s;
while (!t.empty()) {
cout <<"栈顶为:" << t.top()<<"大小为:" <<t.size()<< endl;
t.pop();
}
cout<<t.size();
}
int main() {
mystack();
}
queue容器
队列容器
先进先出,只能从队尾进队从队头出队
只有队头和队尾才能被访问,不能被遍历
进队添加元素:push(elem)
出队移除元素:pop()
返回队头元素:front()
返回队尾元素:back()
是否为空:empty();
大小:size()
#include"one.h"
using namespace std;
class person {
public:
person(string name, int age) {
this->name = name, this->age = age;
}
string name;
int age;
};
void myqueue() {
queue<person> s;
for (int i = 1; i < 9; i++) {
person p ("王" + i, i * 10);
s.push(p);
}
queue<person> t = s;
while (!t.empty()) {
cout << "队为:"<<t.front().age << "大小为:" << t.size() << endl;
t.pop();
}
cout << t.size();
}
int main() {
myqueue();
}
list容器
链式存储数据,有结点组成,存储数据的指针域
可以对任意的位置快速的插入和删除
遍历容器速度慢,没有数组快,占空间比数组大
STL中的链表是双向循环链表
list<T>
list(beg,end)从beg到end区间的元素
list(n,elem)n个elem
list(const list &lst)拷贝函数
assign(beg,end)从beg到end的数据
assign(n,elem)n个elem
=等号赋值
swap(lst)将lst与本身元素互换
size()元素的个数
empty()判断容器是否为空
resize(num)重新指定容器的长度为num
resize(num,elem)容器长度num,若变长以elem填充
插入和删除
push_back(elem)在容器尾部添加一个元素
pop_back()删除容器中的最后一个元素
push_front(elem)在开头添加一个元素
pop_front()在开头移除第一个元素
insert(pos,elem)在pos的位置添加elem元素数据,返回新数据的位置
insert(pos,n,elem)在pos位置插入n个elem数据无放回值
insert(pos,beg,end)在pos的位置插入[beg,end]的数据无返回值
clear()删除所有数据
erase(beg,end)删除区间数据,返回下一个数据的位置
erase(pos)删除pos位置的数据放回下个数据的位置
remove(elem)删除所有与elem值匹配的数据
front()返回第一个元素
back()返回最后一个元素
不能用Li[0],Li.at()方式访问容器内的元素
本质是链表,不能用连续线性空间存储数据,迭代器也不支持随机访问
reverse()反转链表
sort()链表排序
所有不支持随机访问的迭代器的容器不能用标准算法,其内部会提供一些算法
List<int> Li;
Li.push_back(20);
Li.push_back(30);
Li.push_back(10);
//20 30 10
Li.reverse();
//10 30 20
//排序 升序
Li.sort();
//降序
bool myCompare(int v1,int v2){
return v1>v2;
}
Li.sort(myCompare);
尾插 --push_back
尾删 --pop_back
头插 --push_front
头删 --pop_front
插入 --insert
删除 --erase
清空 --clear
移除 --remove
第一元素 --front()
最后元素 --back()
swap()交换两个集合没有链表
reverse()反转链表
sort()链表排序
set容器
简介
所有元素都会在插入时自动被排顺序
本质
set/multiset属于关联式容器,底层是二叉树实现
set没有重复的元素
multiset允许有重复的元素
用法
swap(lst)将lst与本身元素互换
size()元素的个数
empty()判断容器是否为空
insert()插入数据
clear()删除所有数据
erase(beg,end)删除区间数据,返回下一个迭代器的位置
erase(pos)删除pos位置的数据,返回下个迭代器的位置
erase(ea)删除为ea的元素
find(key)查找key是否存在,存在返回该元素的迭代器
count(key)统计key的元素个数
set不能插入重复的数据,multiset可以
set<int> st;
//插入数据只有insert
st.insert(10);
st.insert(1);
st.insert(10);
//1 10 自动排序 没有重复的元素
set<int> s(st);
set<int> l;
l=s;
cout <<st.size()<<endl;
cout <<st.empty()<<endl;
st.swap(s);
set<int>::iterator
//查找
pos=st.find(30);
if(pos=st.end()){
cout<<"未找到"<<endl;
}
//统计
int num=st.count(30);
set内的自动排序规则
普通数据倒序
自定义数据类型排序
#include"one.h"
using namespace std;
//仿函数
class Mysort{
public:
bool operator()(int v1, int v2)const
{
return v1 > v2;
}
};
void Myset() {
set<int,Mysort>s;
s.insert(20);
s.insert(49);
s.insert(21);
for (set<int, Mysort>::iterator it = s.begin(); it != s.end(); it++) {
cout << *it << " ";
}
}
#include"one.h"
using namespace std;
class person {
public:
person(string name, int add) {
this->name = name;
this->add = add;
}
string name;
int add;
};
class Mysort{
public:
bool operator()(const person &v1,const person &v2)const
{
return v1.add > v2.add;
}
};
void Myset() {
set<person,Mysort>s;
person p1("www", 20);
person p2("fcx", 49);
person p3("fgsdf", 21);
s.insert(p1);
s.insert(p2);
s.insert(p3);
for (set<person, Mysort>::iterator it = s.begin(); it != s.end(); it++) {
cout << it->add << " ";
}
}
pair对组
pair<string,int>p("tom",20);
cout<<p.first<<p.second<<endl;
pair<string,int>p2=make_pair("jerry",30);
map容器
概念
map中的所以元素都是pair对组
pair中的第一个是key(键值),第二个是value(实值)
所有元素都会根据键值自动排序
map/multimap是关联式容器,是二叉树实现的
map中没有重复的key值
multimap中能有重复的key值
用法
swap(lst)将lst与本身元素互换
size()元素的个数
empty()判断容器是否为空
insert()插入数据
clear()删除所有数据
erase(beg,end)删除区间数据,返回下一个迭代器的位置
erase(pos)删除pos位置的数据,返回下个迭代器的位置
erase(ea)删除为ea的元素
find(key)查找key是否存在(查找到的是key值),存在返回该元素的迭代器,找不到就会返回最后一个值
count(key)统计key的元素个数,对map是0或1
map<int,int>m;
m.insert(pair<int,int>(1,30)));
m.insert(make_pair(2,34));
m.insert(map<int,int>::value_type(2,30));
m[4]=40;
map<int,int>n(m);
cout<<m.size()<<m.empty()<<endl;
n.swap(m);
n.clear();
m.erase(m.begin());
//
m.erese(40);
m.erase(m.begin(),m.end());
m.insert(pair<int,int>(1,30)));
m.insert(make_pair(2,34));
m.insert(map<int,int>::value_type(2,30));
m[4]=40;
map<int,int>::iterator pos=m.find(3);
cout<<pos->frist()<<pos->second<<endl;
int i=m.count(2);
改变排序同set一样
#include"one.h"using namespace std;
//仿函数
class Mysort{
public:
bool operator()(int v1, int v2)const {
return v1 > v2;
}
};
void Myset() {
map<int,int,Mysort>s;
s.insert(20);
s.insert(49);
s.insert(21);
for (map<int,int,Mysort>::iterator it = s.begin(); it != s.end(); it++) {
cout << *it << " ";
}
}
算法
模版
例题
三维立体
例1:
农夫约翰有一块立方体形状的奶酪,它位于三维坐标空间中,从 (0,0,0)(0,0,0) 延伸至 (N,N,N)(N,N,N)。
农夫约翰将对他的奶酪块执行一系列 Q 次更新操作。
对于每次更新操作,农夫约翰将从整数坐标 (x,y,z)(x,y,z) 到 (x+1,y+1,z+1)(x+1,y+1,z+1) 处切割出一个 1×1×11×1×1 的奶酪块,其中 0≤x,y,z<N0≤x,y,z<N。
输入保证在农夫约翰切割的位置上存在一个 1×1×1的奶酪块。
由于农夫约翰正在玩牛的世界,当下方的奶酪被切割后,重力不会导致上方的奶酪掉落。
在每次更新后,输出农夫约翰可以将一个 1×1×N 的砖块插入奶酪块中的方案数,使得砖块的任何部分都不与剩余的奶酪重叠。
砖块的每个顶点在全部三个坐标轴上均必须具有整数坐标,范围为 (0.N)
农夫约翰可以随意旋转砖块。
输入格式
输入的第一行包含 N 和 Q
以下 QQ 行包含 xx,yy 和 zz,为要切割的位置的坐标。
输出格式
在每次更新操作后,输出一个整数,为所求的方案数。
每个面每个格有N才会有一个,用二维数组时间复杂度是线性的
#include<bits/stdc++.h>
using namespace std;
int gety=0;
int passnumber(vector<vector<int>> &number,vector<vector<int>> &n,vector<vector<int>> &m,int N,int a,int b,int c){
int r=0;
number[a][c]++;
if(number[a][c]==N)
r++;
n[b][c]++;
if(n[b][c]==N)
r++;
m[a][b]++;
if(m[a][b]==N)
r++;
gety+=r;
return gety;
}
int main(){
int N,Q;
int f[200005];
vector<vector<int>> n(1005, vector<int>(1005, 0));
vector<vector<int>> m(1005, vector<int>(1005, 0));
vector<vector<int>> q(1005, vector<int>(1005, 0));
cin >>N>>Q;
for(int i=0;i<Q;i++){
int a,b,c;
cin >>a>>b>>c;
f[i]=passnumber(n,m,q,N,a,b,c);
}
for(int i=0;i<Q;i++){
cout<<f[i]<<endl;
}
return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int a[N][N], b[N][N], c[N][N];
int main()
{
scanf("%d%d", &n, &m);
int res = 0;
while (m -- )
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (++ a[x][y] == n) res ++ ;
if (++ b[x][z] == n) res ++ ;
if (++ c[y][z] == n) res ++ ;
printf("%d\n", res);
}
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/9222058/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
哞哞叫
枚举
查找符合条件的子串
农夫约翰正在试图向埃尔茜描述他最喜欢的 USACO 竞赛,但她很难理解为什么他这么喜欢它。
他说「竞赛中我最喜欢的部分是贝茜说 『现在是哞哞时间』并在整个竞赛中一直哞哞叫」。
埃尔茜仍然不理解,所以农夫约翰将竞赛以文本文件形式下载,并试图解释他的意思。
竞赛被定义为一个长度为 NN的小写字母字符串。
一种哞叫一般地定义为子串 cicjcj,其中某字符 ci之后紧跟着 22 个某字符 cj,且 ci≠cj。
根据农夫约翰的说法,贝茜哞叫了很多,所以如果某种哞叫在竞赛中出现了至少 F次,那可能就是贝茜发出的。
然而,农夫约翰的下载可能损坏,文本文件可能存在至多一个字符与原始文件不同。
将可能的误差考虑在内,输出所有可能是贝茜发出的哞叫,按字母顺序排序。
输入格式
输入的第一行包含 N 和 F,表示字符串的长度以及贝茜的哞叫的频次下限。
第二行包含一个长度为N 的小写字母字符串,表示竞赛。
输出格式
输出可能是贝茜发出的哞叫的数量,以下是按字典序排序的哞叫列表。
每行输出一种哞叫。
数据范围
3≤N≤20000,1≤F≤N
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=26,N=20001;
int ch[M][M];
char st[N];
bool b[M][M];
int n,f;
void demo(int l,int q,int v){
l=max(l,0);
q=min(q,n-1);
for(int i=l;i<=q-2;i++){
char c=st[i];
char h=st[i+1];
char r=st[i+2];
int num1=static_cast<int>(c);
int num2=static_cast<int>(h);
if(c!=h&&h==r)
ch[num1][num2]+=v;
if(ch[num1][num2]>=f)b[num1][num2]=true;
}
}
int main(){
cin>>n>>f;
cin>>st;
int res=0;
for(int i=0;i<n;i++)st[i]-='a';
demo(0,n-1,1);
for(int i=0;i<n;i++){
char ar=st[i];
demo(i-2,i+2,-1);
for(int e=0;e<26;e++){
if(st[i]!=e)
st[i]=static_cast<char>(e);
demo(i-2,i+2,1);
demo(i-2,i+2,-1);
}
st[i]=ar;
demo(i-2,i+2,1);
}
for(int i=0;i<26;i++){
for(int t=0;t<26;t++){
if(b[i][t])res++;
}
}
cout <<res<<endl;;
for(int i=0;i<26;i++){
for(int t=0;t<26;t++){
if(b[i][t])
printf("%c%c%c\n",i+'a',t+'a',t+'a');
}
}
}
蛋糕游戏
枚举,贪心,前缀和
贝茜和埃尔茜发现了一行 NN 个蛋糕(NN 为偶数),大小依次为 a1,a2,…,aNa1,a2,…,aN。
两头奶牛都想吃到尽可能多的蛋糕。
但是,作为非常文明的奶牛,她们决定玩一个游戏来分割蛋糕!
游戏在两头奶牛之间轮流进行回合。
每个回合进行以下两者之一:
- 贝茜选择两个相邻的蛋糕并将它们堆叠起来,制造大小为两者大小之和的一个新蛋糕。
- 埃尔茜选择最左边或最右边的蛋糕藏起来。
当只剩下一个蛋糕时,贝茜吃掉它,而埃尔茜吃掉她藏起来的所有蛋糕。
如果两头奶牛都采取最优策略以最大化她们吃到的蛋糕量,并且贝茜先进行回合,那么每头奶牛将会吃到多少蛋糕?
输入格式
每个测试点包含 TT 个独立的测试用例。
每个测试用例的格式如下。
第一行包含 NN。
下一行包含 NN 个空格分隔的整数 a1,a2,…,aNa1,a2,…,aN。
输出格式
对于每个测试用例,输出一行,包含 bb 和 ee,表示贝茜和埃尔茜在两头奶牛都采取最优策略的情况下分别吃到的蛋糕量。
数据范围
1≤T≤101≤T≤10,
2≤N≤5×1052≤N≤5×105,
1≤ai≤1091≤ai≤109,
输入保证一个测试点中的所有 NN 之和不超过 106106。
输入样例:
2
4
40 30 20 10
4
10 20 30 40
输出样例:
60 40
60 40
结果
超时了
#include<bits/stdc++.h>
using namespace std;
int N;
void demo1(vector<int> &a){
if (a.size() < 2) return;
int t;
t=N/2;
a[t-1]=a[t-1]+a[t];
a.erase(a.begin()+t);
N--;
}
void demo2(vector<int> &a,int &art){
if (a.empty()) return;
if(*a.begin()>*(a.end()-1))
{art+=*a.begin();
a.erase(a.begin());
}
else{
art+=*(a.end()-1);
a.pop_back();}
N--;
}
int main(){
int t;
cin>>t;
for(int q=1;q<=t;q++){
cin>>N;
vector<int> a;
int art=0;
int biexi=0;
for(int i=0;i<N;i++){
int num;
cin>>num;
a.push_back(num);
}
while (a.size() != 4) {
if (a.size() >= 2) demo1(a);
if (!a.empty()) demo2(a,art);
}
demo1(a);
demo2(a,art);
biexi=a[0]+a[1];
a.erase(a.begin());
a.erase(a.begin());
cout<<biexi<<" "<<art<<endl;
}
}
从整体看只看前后缀数和就可以,反正就a和b两个人吃,总数是不变的
若b不吃a的蛋糕那a=N/2+1,b=N/2-1;
b可以在任意前后缀和为N/2-1的任意数里面吃,所以b>总和T-中间的N/2+1的和的最小值sw
当a永远吃中间并让叠加的那个蛋糕处于中间,那a>sw,所以b<T-sw
所有b=T-sw
只要算a的中间前缀和最小就可以,T是不变的
#include<bits/stdc++.h>
using namespace std;
const int N=500010;
int main(){
int t;
int n;
long long s[N];
cin>>t;
while(t--){
scanf("%d",&n);
long long lo=1e15;//10*15次方
int l=n/2+1;
for(int i=1;i<=n;i++){
int m;
scanf("%d",&m);
s[i]=s[i-1]+m;
if(i>=l)lo=min(lo,s[i]-s[i-l]);
//算a的最小前缀和
}
cout<<lo<<" "<<s[n]-lo<<endl;
}
}
检查奶牛
枚举
我们枚举每个区间的中点,由内向外拓展枚举,这样,区间内的贡献形成递推关系
农夫约翰的 NN 头奶牛站成一行,奶牛 11 在队伍的最前面,奶牛 NN 在队伍的最后面。
农夫约翰的奶牛也有许多不同的品种。
他用从 11 到 NN 的整数来表示每一品种。
队伍从前到后第 ii 头奶牛的品种是 aiai。
农夫约翰正在带他的奶牛们去当地的奶牛医院进行体检。
然而,奶牛兽医非常挑剔,仅愿意当队伍中第 ii 头奶牛为品种 bibi 时对其进行体检。
农夫约翰很懒惰,不想完全重新排列他的奶牛。
他将执行以下操作恰好一次。
- 选择两个整数 ll 和 rr,使得 1≤l≤r≤N1≤l≤r≤N。反转队伍中第 ll 头奶牛到第 rr 头奶牛之间的奶牛的顺序。
农夫约翰想要衡量这种方法有多少效果。
对于每一个 c=0…Nc=0…N,请帮助农夫约翰求出使得恰好 cc 头奶牛被检查的不同操作 (l,r)(l,r) 的数量。
两种操作 (l1,r1)(l1,r1) 和 (l2,r2)(l2,r2) 不同,如果 l1≠l2l1≠l2 或者 r1≠r2r1≠r2。
输入格式
输入的第一行包含 NN。
第二行包含 a1,a2,…,aNa1,a2,…,aN。
第三行包含 b1,b2,…,bNb1,b2,…,bN。
输出格式
输出 N+1N+1 行,第 ii 行包含使得 i−1i−1 头奶牛被检查的不同操作 (l,r)(l,r) 的数量。
数据范围
1≤N≤75001≤N≤7500,
1≤ai,bi≤N
#include<bits/stdc++.h>
using namespace std;
const int n=7510;
vector<int> a,b;
int d[n]={0};
int sum=0,res=0;
int main(){
int N;
cin>>N;
for(int i=0;i<N;i++){
int c;
cin>>c;
a.push_back(c);
}
for(int i=0;i<N;i++){
int c;
cin>>c;
b.push_back(c);
if(b[i]==a[i])sum++;
}
for(int i=0;i<n;i++){
for(int j=0;j<2;j++){
res=sum;
for(int l=i,r=i+j;l>=0&&r<N;l--,r++){
if(a[l]==b[l])res--;
if(a[r]==b[r])res--;
if(a[l]==b[r])res++;
if(a[r]==b[l])res++;
d[res]++;
}
}
}
for(int i=0;i<=N;i++){
cout<<d[i]<<endl;
}
}
哞哞叫2
枚举
农夫约翰正在试图向埃尔茜描述他最喜欢的 USACO 竞赛,但她很难理解为什么他这么喜欢它。
他说「竞赛中我最喜欢的部分是贝茜说『现在是哞哞时间』并在整个竞赛中一直哞哞叫」。
埃尔茜仍然不理解,所以农夫约翰将竞赛以文本文件形式下载,并试图解释他的意思。
竞赛被定义为一个包含 NN 个整数的数组 a1,a2,…,aNa1,a2,…,aN。
农夫约翰定义哞叫为一个包含三个整数的数组,其中第二个整数等于第三个整数,但不等于第一个整数。
一种哞叫被称为在竞赛中发生,如果可以从数组中移除整数,直到只剩下这一哞叫。
由于贝茜据称「在整个竞赛中一直哞哞叫」,请帮助埃尔茜计算竞赛中发生的不同哞叫的数量!
两种哞叫是不同的,如果它们并非由相同的整数以相同的顺序组成。
输入格式
输入的第一行包含 NN。
第二行包含 NN 个空格分隔的整数 a1,a2,…,aNa1,a2,…,aN。
输出格式
输出竞赛中发生的不同哞叫的数量。
注意这个问题涉及到的整数可能需要使用 64 位整数型(例如,Java 中的 “long”,C/C++ 中的 “long long”)。
数据范围
1≤N≤1061≤N≤106,
1≤ai≤N
#include<bits/stdc++.h>
using namespace std;
long long N;
int mes=0;
int res=0;
const int n=1000001;
long long le[n]={0},rig[n]={0},a[n];
void demo(int i){
if(!le[i]){
if(rig[i]>=2)
res+=(mes-1);
else {res+=mes;}}
le[i]=1;
}
int main(){
cin>>N;
for(int i=0;i<=N;i++){
cin>>a[i];
if(++rig[a[i]]==2)++mes;
}
for(int i=0;i<=N;i++){
if(--rig[a[i]]==1)mes--;
demo(a[i]);
}
cout<<res<<endl;
return 0;
}
农夫约翰最喜欢的操作
同余 破环成链 前缀和 枚举
农夫约翰有一个包含 N 个非负整数的数组 a 和一个整数 M。
然后,农夫约翰会请贝茜给出一个整数 x。
在一次操作中,农夫约翰可以选择一个索引 i,并对 ai 加 1 或减 1。
农夫约翰的无聊值是他必须执行的最小操作次数,以使得对于所有的 1≤i≤N,ai−x 均可被 M整除。
对于所有可能的 xx,输出农夫约翰的最小无聊值。
输入格式
输入的第一行包含 T,为需要求解的独立的测试用例数量。
每一个测试用例的第一行包含 N和 M。
第二行包含 a1,a2,…,aN
输出格式:
对于每一个测试用例输出一行,包含对于所有可能的 xx,农夫约翰的最小无聊值。
数据范围:
1≤T≤10
1≤N≤2×105≤N≤2×105,
1≤M≤1091≤M≤109,
0≤ai≤10^9
输入保证所有测试用例的 N 之和不超过 5×10^5
给定一个序列 pi,每次可以让 pi 加一或者减一,求让所有元素相等的最小操作次数,而是一个经典的题型,只需要将序列从小到大排序后取中位数(中点)就好
破环成链:
将序列 ai拷贝一份接在后面,形如 a1a2a3⋯ana1a2⋯an,这样我们从 i枚举到 n,其中区间 [i,i+n−1] 就是一个环对应的区间了。
#include<bits/stdc++.h>
using namespace std;
int T,N,M,sum;
long long res;
vector<long long> b(400010);
long long a[500010];
int main(){
cin>>T;
for(int i=0;i<T;i++){
// 清空数组,防止多次测试用例干扰
b.clear();
res=1e15;
cin>>N>>M;
for(int j=0;j<N;j++){
int q;
cin>>q;
b.push_back(q%M);
}
sort(b.begin(),b.end());
for(int j=0;j<N;j++){
b[N+j]=b[j]+M;
}
for(int j=0;j<N+N;j++){
if(j==0)a[j]=b[j];
else a[j]=a[j-1]+b[j];
}
for(int l=0;l<N;l++){
int r=(l+N-1);
int p=(r+l)/2;
long long left=b[p]*(p-l+1)-(a[p]-a[l-1]);
long long right=a[r]-a[p]-b[p]*(r-p);
res=min(res,left+right);
}
cout<<res<<endl;
}
}
传染病
贪心
农夫约翰有 NN 头奶牛排成一排,从左到右依次编号为 1∼N1∼N。
不幸的是,有一种传染病正在蔓延。
最开始时,只有一部分奶牛受到感染。
每经过一个晚上,受感染的牛就会将病毒传染给它左右两侧的牛(如果有的话)。
一旦奶牛被感染,它就会一直被感染,无法自愈。
给定一个经过若干个夜晚后的奶牛的整体状态,其中哪些奶牛已经被感染,哪些奶牛尚未被感染统统已知。
请你计算,最开始时就受到感染的奶牛的最小可能数量。
输入格式
第一行包含整数 NN。
第二行包含一个长度为 NN 的 0101 序列,用来表示给定的奶牛的整体状态,其中第 ii 个字符如果是 11 则表示第 ii 头奶牛已经被感染,如果是 00 则表示第 ii 头奶牛尚未被感染。
输出格式:
一个整数,表示最开始时就受到感染的奶牛的最小可能数量。
数据范围:
1≤N≤3×105
每个1可以覆盖最长 2R−1的区间,那么当前区间需要的最少1的数量就是 ⌈N2R−1⌉,那么可以改写成 ⌊N−12R−1⌋+1
#include<bits/stdc++.h>
using namespace std;
int N,m=0,res;
const int n=300010;
vector<int> b;
string a;
int main(){
cin>>N;
cin>>a;
int res,t;
t=N;
a=a+'0';
for(int i=0;i<N;i++){
if(a[i]=='0')continue;
int j=i+1;
while(a[j]=='1'&&j<N)j++;
m=j-i;
b.push_back(m);
int d=(m-1)/2;
if(j==N||!i)d=m-1;
t=min(t,d);m=0;
i=j;
}
res=0;
for(int c:b){
res+=(c+t*2)/(t*2+1);
}
printf("%d",res);
return 0;
}
抽屉原理
某串长度为len,该串众数X的数量需要严格大于len/2,就能将整个串转化为X
注:Xi表示下标
充分性证明
(1)Xi+1-Xi<=2的情况,串只能是XXY XYX YXX
(2)对于XXY XYX YXX的情况都能将串转化为XXX
(2)对于XXX的串,我们只需选取XXXY,就能不断的把相邻Y的转化为X,直到全串
必要性证明
(1)假设串长度为偶数
假设每个X间隔为1,以X[]的形式填充串,根据抽屉原理,至少有两个串是相邻的。
(2)假设串长度为奇数
假设每个X间隔为2,以X[][]的形式填充串,根据抽屉原理,至少有两个串是间隔为1的。
(3)即对于任意串,如果其要符合题意,至少有两个串的间距是s<=1(即Xi+1-Xi<=2)
作者:Lnker
链接:https://www.acwing.com/solution/content/268748/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include<bits/stdc++.h>
using namespace std;
int T,N;
const int n=100010;
vector<int> a,c;
int main(){
cin>>T;
while(T--){
a.clear();
c.clear();
a.resize(n,0);
cin>>N;
for(int i=0;i<N;i++){
int c;
cin>>c;
a[i]=c;
}
int t=0;
int b[n]={0};
for(int i=0;i<N;i++){
if(a[i]==a[i+1]||a[i]==a[i+2]){
if(b[a[i]]==0){
c.push_back(a[i]);
b[a[i]]++;
t=1; }
}
}
sort(c.begin(),c.end());
for(vector<int>::iterator it=c.begin();it!=c.end();it++){
cout<<*it<<" ";
}
if(t==0)cout<<-1<<endl;
else cout<<endl;
}
}
差分
给定一个序列 ai,每次操作可以选择一个位置 p,令从 ap
开始的每个数都加上一个以 1 或者 -1 为公差的从 1/-1 开始的等差数列。最小化让序列归零的操作次数。
解题思路
差分模板题:我们从差分角度观察操作的本质:
给一段区间加上:1,2,3,4,5…
在一阶差分数组上:1,1,1,1,1…
在二阶差分数组上:1,0,0,0,0…
所以每次修改的本质实际上是在二阶差分数组上+1或者-1。我们要让原序列变成0序列等价于要他的二阶差分数组变成0序列,因此答案就是二阶差分数组中所有数的绝对值之和。
钦定:di=ai−ai−1,ddi=di−di−1
ans=∑i=1n|ddi|
作者:OrangeOvO
链接:https://www.acwing.com/solution/content/269535/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include <bits/stdc++.h>
using namespace std;
#define IOS ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using i64 = long long;
signed main()
{
IOS;
int n;
cin >> n;
vector<i64> a(n + 1), d(n + 1), dd(n + 1);
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++) d[i] = a[i] - a[i - 1];
for(int i = 1; i <= n; i ++) dd[i] = d[i] - d[i - 1];
i64 ans = 0;
for(int i = 1; i <= n; i ++) ans += abs(dd[i]);
cout << ans << '\n';
return 0;
}
作者:OrangeOvO
链接:https://www.acwing.com/solution/content/269535/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
不等式
给定 n个整数 hi,每次操作会使 hi←hi+ai,求能否在若干次操作后使得 hi 的排名(从大到小)为 ti+1,如果可以,求最小操作次数,否则输出-1。
题解思路
首先,我们假设现在经过了 k次操作,则 h′i=hi+k×ai,此时,我们钦定 h′i
的排名为 rank[i],那么对于排名为 rank[i]+1的 h′i+1,应该满足:hi>h′i+1,我们将上式代入可以得到:x+ky>u+kv
化简可得:
k>(u−x)/(y−v) (y−v>0)
k<(u−x)/(y−v) (y−v<0)
同时,需要注意,若 y=v 的话,说明它们的增长速度相等,也就是 h′i和 h′i+1的大小关系,就是 hi和 hi+1的大小关系,如果 hi<=hi+1,得无解。对于上述不等式组,我们只需要按照排名顺序,同时维护 k的值域的上下界 [L,R]即可,对于 y−v>0的,更新 L=max(L,u−xy−v),否则更新 R=min(R,u−xy−v)
最后,还需要注意,值域中是否存在,也就是 L<=R,如果存在,答案就是最小值 L,否则就是无解。一些代码实现上的细节:对于维护 L,由于我们的运算都是在整数下的,更新 L时,我们可以写成 L=max(L,⌊u−xy−v⌋+1),更新 R 时,R=min(R,⌈u−xy−v⌉−1)需要注意的是,这里的 L更新的下取整,应该采用 floor 函数,而不是默认的整数除法,因为cpp运算时,整数除法是向0取整的,如果出现负数,则会发生错误,而向上取整可以采用技巧 分子+分母-1 / 分母 来实现。
作者:OrangeOvO
链接:https://www.acwing.com/solution/content/268447/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include<bits/stdc++.h>
using namespace std;
const int n=200010;
int a[n],h[n],r[n];
int t,N,l,re;
int main(){
cin>>t;
while(t--){
scanf("%d",&N);
for(int i=0;i<N;i++)cin>>h[i];
for(int i=0;i<N;i++)cin>>a[i];
for(int i=0;i<N;i++){
int b;
cin>>b;
r[b]=i;
}
re=1000000010;l=0;
for(int i=0;i<N-1;i++){
int A=a[r[i+1]]-a[r[i]];
int B=h[r[i]]-h[r[i+1]];
if(A>0)re=min(re,(int)ceil((double)B/A)-1);
else if(A<0)l=max(l,((int)floor((double)B/A)+1));
else if(B<=0){
re=-1;
break;
}
}
if(l>re)l=-1;
printf("%d\n",l);
}
}
博弈论
题:
两人取石头,每次可以取走 x个石头,当且仅当 x是一个回文数,轮到谁石头被取完则输,问是否先手必胜
解:
规律题+数学公式证明
细心的我们不难发现,当石头总数为19时先手必胜,因为可以一次性拿完,当石头总数为10时,先手必败,因为先手要拿回文数,而此时回文数是19,所以先手不能一次性拿完,而后手在先手拿完的情况下,石头总数转换为了19,所以此时操作者必胜。而我们在从1-10的基础上推广到1120,不难发现1119,先手可以把石堆个数转换到10,那此时后手操作的是个数为10的石堆,我们先前已经知道了个数为10的石堆,先手操作的会输,所以石堆个数为1119先手必胜,同理20也可以证明先手必败,以此类推。基于此我们可以猜想是不是只要石头总数是10的倍数那么先手一定必败呢?这是显然的,下面我们就用公式推导的方式来证明。
本题的难点:证明只要石头总数是10的倍数,那么一定先手必败。
证明如下
我们先假设石头S总数是10的倍数,即S = 10k,(k = 1,2,…n),因为贝茜先手操作,且每次都必须拿回文数,又因为我们的总数S结尾是0,所以贝茜不可能一次拿完,因为S不是回文数,我们假设贝茜拿了x(x为回文数)个石头,剩余S = 10k - x,因为x为回文数,所以个位数一定是1~9,我们假设个位数是d,那么S的个位数一定是10 - d,埃尔茜可以再次取10 - d个数的石子使得我们的S再次成为10的倍数,因为S = 10k - x - 10 + d = 10(k-1)-(x-d),d是x的个位数,所以x - d是10的倍数,10*(k-1)也是10的倍数,即S是10的倍数。所以无论如何贝茜面对的都是10倍数的石堆,最终石子个数减到0,贝茜会输掉比赛。
所以只要石子个数是10的倍数,贝茜一定会输:反之一定会赢,因为贝茜可以先手操作把石子总数变成10的倍数,让对手输掉比赛。
以此证明(如果有错希望指正)
作者:晚风轻拂
链接:https://www.acwing.com/solution/content/269798/
来源:AcWing
#include<bits/stdc++.h>
using namespace std;
int T;
int main(){
cin>>T;
for(int i=1;i<=T;i++){
string s;
cin>>s;
cout<<(s.back()=='0' ? "E" : "B")<<'\n';
} }

浙公网安备 33010602011771号