### Day2:1.202006-1 2.202009-1 3.202012-1 4.202209-1 5.202212-1
#### 1.202006-1:线性分类器(计算几何,小模拟)
(1)题目:
判断给定的直线能否将所有的 A 类点和 B 类点分隔在平面上的不同区域。判断的方法是:
1. 对于每条查询直线,计算每个点在直线上的位置。
2. 如果所有 A 类点和 B 类点分别位于直线的两侧(即符号不同),则输出 `Yes`;否则输出 `No`。
(2)知识点:
存储一堆点的二维坐标时,常见的选择是使用如下几种数据结构:
1.**简单场景(常用)**:如果只是简单存储一组点,推荐使用 `vector<pair<int, int>>`。
- **通过索引访问**:`points[i].first` 和 `points[i].second`。
- **通过范围循环访问**:`for (const auto& point : points) {...}`。
- **通过迭代器访问**:`it->first` 和 `it->second`。
2.**有额外属性**:如果需要存储额外信息,推荐使用自定义结构体 `Point(可推广)` 或者 `map<pair<int, int>, ValueType>`。
3.**需要排序或去重**:使用 set<pair<int, int>>,如果不需要顺序但需要去重,则使用 `unordered_set`。
(3)代码(100):
```
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
vector<pair<int,int>> va;
vector<pair<int,int>> vb;
for(int i=0;i<n;i++){
int a,b;
char c;
cin>>a>>b>>c;
if(c=='A'){
va.push_back({a,b});
}
else{
vb.push_back({a,b});
}
}
for(int i=0;i<m;i++){
int t0,t1,t2;
cin>>t0>>t1>>t2;
if(t1==0 && t2==0){
cout<<"No"<<endl;
continue;
}
bool tmp1;
bool tmp2;
bool isExit1=false;
bool isExit2=false;
for(int i=0;i<va.size();i++){
int x=va[i].first;
int y=va[i].second;
int sum=t0+t1*x+t2*y;
if(i==0){
if(sum<0){
tmp1=false;
}
else{
tmp1=true;
}
}
else{
bool tmp;
if(sum<0){
tmp=false;
}
else{
tmp=true;
}
if(tmp1!=tmp){
cout<<"No"<<endl;
isExit1=true;
break;
}
}
}
if(isExit1){
continue;
}
else{
for(int i=0;i<vb.size();i++){
int x=vb[i].first;
int y=vb[i].second;
int sum=t0+t1*x+t2*y;
if(sum<0){
tmp2=false;
}
else{
tmp2=true;
}
if(tmp1==tmp2){
cout<<"No"<<endl;
isExit2=true;
break;
}
}
}
if(isExit2){
continue;
}
else{
cout<<"Yes"<<endl;
}
}
return 0;
}
```
(4)改进:
- **简化了循环结构**:将判断是否所有点都在同一侧的逻辑整合到一个循环中,减少了代码冗余。
- **合并条件判断**:通过合并相同逻辑的条件判断,简化了代码结构。
- **更直观的判断方法**:通过直接对比每个点的符号来决定是否输出 "Yes" 或 "No",代码变得更易读。
```
for (int i = 0; i < m; i++) {
int t0, t1, t2;
cin >> t0 >> t1 >> t2;
if (t1 == 0 && t2 == 0) {
cout << "No" << endl;
continue;
}
//以下简化判断逻辑
bool sideA = (t0 + t1 * va[0].first + t2 * va[0].second) > 0;
bool isSeparated = true;
for (auto& p : va) {
int sum = t0 + t1 * p.first + t2 * p.second;
if ((sum > 0) != sideA) { //同样的条件比较
isSeparated = false;
break;
}
}
if (isSeparated) {
for (auto& p : vb) {
int sum = t0 + t1 * p.first + t2 * p.second;
if ((sum > 0) == sideA) {
isSeparated = false;
break;
}
}
}
cout << (isSeparated ? "Yes" : "No") << endl;//判断输出
}
```
#### 2.202009-1:称检测点查询(部分排序,小模拟)
(1)题目:找出最近的三个检测点
(2)代码(100):
```
#include <bits/stdc++.h>
using namespace std;
struct Point{
int id;
int x;
int y;
};
int X,Y;
bool cmp(const Point& a,const Point& b){
int d1=(X-a.x)*(X-a.x)+(Y-a.y)*(Y-a.y);
int d2=(X-b.x)*(X-b.x)+(Y-b.y)*(Y-b.y);
if(d1==d2){
return a.id<b.id;
}
else{
return d1<d2;
}
}
int main(){
int n;
cin>>n>>X>>Y;
vector<Point> v(n);
for(int i=0;i<n;i++){
v[i].id=i+1;
cin>>v[i].x>>v[i].y;
}
sort(v.begin(),v.end(),cmp);
cout<<v[0].id<<endl<<v[1].id<<endl<<v[2].id<<endl;
return 0;
}
```
(3)改进:
1.可以在输入时直接计算出每个检测点的距离平方,并将其存储在结构体中,然后在比较时直接使用存储的距离。
struct Point{
int id;
int x;
int y;
int dist; // 直接存储距离的平方
};
bool cmp(const Point& a, const Point& b){
if (a.dist == b.dist){
return a.id < b.id;
}
return a.dist < b.dist;
}
2.部分排序(nth_element):如果只需要找出最近的三个检测点,可以使用 `nth_element` 来代替 `sort`。`nth_element` 可以在 O(n) 的时间复杂度内找到第 k 小的元素,并将它们放在容器的前 k 个位置,不需要完全排序。
// 使用 nth_element 找到前三个最近的点
nth_element(points.begin(), points.begin() + 3, points.end(), cmp);
// 将前3个元素排序,得到最近的3个点
sort(points.begin(), points.begin() + 3, cmp);
[[排序#^9b53cb|nth_element部分排序]]
3.[CSP202009-1 称检测点查询(100分)【数学】_csp 202009-1-CSDN博客](https://blog.csdn.net/tigerisland45/article/details/110773636?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172433720516800185883492%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=172433720516800185883492&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~hot_rank-22-110773636-null-null.142^v100^pc_search_result_base3&utm_term=202009-1%3A%E7%A7%B0%E6%A3%80%E6%B5%8B%E7%82%B9%E6%9F%A5%E8%AF%A2&spm=1018.2226.3001.4187)
对前3个点部分排序(手动排序,小规模)
#### 3.202012-1:期末预测之安全指数(小模拟)
简单,过
最后输出可以改为cout<<(sum>0?sum:0)或者cout<<max(0LL,sum)(**注意:sum为long long型,前面要改成0LL,不能写0**);
#### 4.202209-1:如此编码(小模拟,数学)
数组存储加数学计算
(1)代码(100):
```
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
vector<int> va(n+1);
vector<long long> vc(n+1,1);
vector<int> vb(n+1);
for(int i=1;i<n+1;i++){
cin>>va[i];
}
for(int i=1;i<n+1;i++){
for(int j=1;j<=i;j++){
vc[i]*=va[j];
}
}
for(int i=1;i<n+1;i++){
vb[i]=(m%vc[i]-m%vc[i-1])/vc[i-1];
}
for(int i=1;i<n+1;i++){
cout<<vb[i]<<" ";
}
return 0;
}
```
(2)优化:
**前缀乘积的计算**:ci=a1×a2×⋯×ai
在计算 `vc[i]` 时使用了嵌套的循环,这会导致每个 `vc[i]` 都重新计算前面的乘积,时间复杂度为 O(n^2)。实际上,`vc[i]` 可以通过 `vc[i-1]` 直接计算,时间复杂度为 O(n)。
// 优化前缀乘积的计算
for(int i=1;i<n+1;i++){
cin>>va[i];
vc[i]=vc[i-1]*va[i];//前提保证vc[0]=1
}
**数论**中有关进制的概念(较难):
- 在十进制中,一个数可以表示为 `个位数 + 十位数 * 10 + 百位数 * 100`,等等。类似地,编码公式中的每一项 `b_i * vc[i-1]` 类似于进制系统中的“位”,`vc[i-1]` 类似于基数。
- - **基数(Radix)**:决定了每个数位的取值范围和权重,常见的基数有二进制(2)、十进制(10)、十六进制(16)等。
- **位(Digit)**:是表示数字的基本单位,每个位在不同的基数下有不同的可能取值。
- **在这段代码中的体现**:`m` 可以看作一个“多位数”,其中每一位的基数由前缀乘积 `vc[i-1]` 决定。我们通过**除法提取每一位,然后通过模运算去掉已经提取的部分**。这种方式与我们**将一个十进制数逐位提取出各位的数字是相似的**。
for (int i = n; i >= 1; i--) {
vb[i] = m / vc[i];
m = m % vc[i];
}
#### 5.202212-1:现值计算(小模拟,格式化输出)
(1)题目理解(出现一些偏差):基于上述分析,我们使用如下的模型来衡量时间价值:假设银行的年利率为 i,当前(第 0 年)的 x 元就等价于第 k 年的 x*(1+i)^k 元;相应的,第 k 年的 x 元的当前价值实际为 x*(1+i)^(−k) 元。**遍历k应该使用第二句话**
**pow函数的使用**
(2)代码(100):
```
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
double i;
cin>>n>>i;
double res=0;
for(int k=0;k<=n;k++){
int x;
cin>>x;
res+=x*pow(1+i,-k);
}
printf("%lf",res);
return 0;
}
```