【暖*墟】各种OI常见小片段qwq

【setw的用法】

在C++中,setw(int n)用来控制输出间隔。
例如: cout<<'s'<<setw(8)<<'a'<<endl;
则在屏幕显示:s        a    //s与a之间有7个空格
setw()只对其后面紧跟的输出产生作用,如上例中,表示'a'共占8个位置,不足的用空格填充。
若输入的内容超过setw()设置的长度,则按实际长度输出。
setw()默认填充的内容为空格,可以setfill()配合使用设置其他字符填充。
如 cout<<setfill('*')<<setw(5)<<'a'<<endl;
则输出:****a //4个*和字符a共占5个位置。

【stringstream的用法】

C语言处理字符串并不方便。C++提供了一个新的string类型,用来替代C语言中的字符数组。

string类型往往是更好的选择。例如,C++的cin/cout可以直接读写string类型,却不能读写字符数组

string类型还可以像整数那样“相加”,而在C语言里只能使用strcat函数。但速度有些慢。

考虑这样一个题目:输入数据的每行包含若干个(至少一个)以空格隔开的整数,输出每行中所有整数之和。

一般有两种方案:一是使用getchar( )边读边算,代码较短,但容易写错,并且相对较难理解;

二是每次读取一行,然后再扫描该行的字符,同时计算结果。如果使用C++,代码可以很简单。

#include<iostream>
#include<string>
#include<sstream>
using namespace std;

int main() {
  string line;
  while(getline(cin, line)) { //读一行
    int sum = 0, x;
    stringstream ss(line); //创建一个“字符串流”——ss
    while(ss >> x) sum += x; //按类似cin的流的方式读取
    cout << sum << "\n";
  }
  return 0;
}

string类在string头文件中,而stringstream在sstream头文件中。

首先用getline函数读一行数据(相当于C语言中的fgets,但由于使用string类,无须指定字符串的最大长度),

然后用这一行创建一个“字符串流”——ss。接下来只需像读取cin那样读取ss即可。


【size_t】

size_t在C语言中就有了。它是一种“整型”类型,里面保存的是一个整数,就像int, long那样。

这种整数用来记录一个大小(当做int使用)。size_t的全称应该是size type,就是说“一种用来记录大小的数据类型”。

通常我们用sizeof(XXX)操作,这个操作所得到的结果就是size_t类型。

因为size_t类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为int并赋值给int类型的变量。

size_t就是unsigned int或者unsigned long;ptrdiff_t就是int或者long

int i;                   // 定义一个int类型的变量i
size_t size = sizeof(i); // 用sizeof操作得到变量i的大小,这是一个size_t类型的值
// 可以用来对一个size_t类型的变量做初始化
i = (int)size;           // size_t类型的值可以转化为int类型的值

char c = 'a';       // c保存了字符a,占一个字节
wchar_t wc = L'a';  // wc保存了宽字符a,占两个字节。注意'a'表示字符a,L'a'表示宽字符a

int arr[] = {1, 2, 3, 4, 5}; // 定义一个数组
int* p1 = &arr[0];    int* p2 = &arr[3];       // 取得数组中元素的地址,赋值给指针
ptrdiff_t diff = p2 - p1;    // 指针的减法可以计算两个指针之间相隔的元素个数
// 所得结果是一个ptrdiff_t类型
i = (int)diff;               // ptrdiff_t类型的值可以转化为int类型的值

【头文件qwq】

#include <bits/stdc++.h>//编译器GCC 4.8支持的万能头文件,基本包含所有头文件

#include <algorithm>//STL通用算法
#include <bitset>//STL位集容器
#include <cctype>//C字符处理
#include <cerrno>//C的错误报告机制
#include <cmath>//兼容C语言数学库
#include <complex>//复数类

#include <cstdio>//C语言输入输出工具
#include <fstream>//文件输入输出流
#include <iostream>//基本输入输出流
#include <streambuf>//底层输入/输出支持
#include <ios>//基本输入/输出支持
#include <iosfwd>//输入/输出系统使用的前置声明

#include <cstdlib>//C语言通用工具
#include <cstring>//C字符串
#include <ctime>//用于随机
#include <deque>//STL双端队列容器
#include <exception>//异常处理流
#include <fstream>//文件输入输出流
#include <functional>//STL定义运算函数(代替运算符)
#include <limits>
#include <iomanip>

#include <list>//STL线性列表容器
#include <map>//STL映射容器
#include <queue>//STL队列容器
#include <set>//STL集合容器
#include <sstream>//基于字符串的流
#include <stack>//STL堆栈容器
#include <string>//字符串类
#include <utility>//STL通用模板类
#include <vector>//STL动态数组容器
using namespace std;


【欧几里得算法】

给出一个这样的除法表达式:X1 / X2 / X3 / …/ Xk,其中Xi是正整数。除法表达式应当按照从左到右的顺序求,例如,表达式1/2/1/2的值为1/4。但可以在表达式中嵌入括号以改变计算顺序,例如,表达式(1/2)/(1/2)的值为1。输入X1, X2, …, Xk,判断是否可以通过添加括号,使表达式的值为整数。K≤10000,Xi≤109

分析》表达式的值一定可以写成A/B的形式:A是其中一些Xi的乘积,而B是其他数的乘积。

X2必须放在分母位置,幸运的是,其他数都可以在分子位置。

接下来的问题就变成了:如何判断ans是否为整数。

第1种方法是利用前面介绍的高精度运算:k次乘法加一次除法。显然,这个方法是正确的,但却比较麻烦。

第2种方法是利用唯一分解定理,把X2写成若干素数相乘的形式,

依次判断每个是否是X1X3X4…Xk的约数。只需把所有Xipi的指数加起来。

如果结果比ai小,说明还会有pi约不掉,因此ans不是整数。这种方法在第5章中已经用过,这里不再赘述。

第3种方法是直接约分:每次约掉XiX2的最大公约数gcd(Xi, X2),则当且仅当约分结束后X2=1时ans为整数:

int judge(int* X) {
      X[2] /= gcd(X[2], X[1]);
      for(int i = 3; i <= k; i++) X[2] /= gcd(X[i], X[2]);
      return X[2] == 1;
}

整个算法的时间效率取决于这里的gcd算法——辗转相除法。它也许是最广为人知的数论算法。

辗转相除法的关键在于如下恒等式:gcd(a,b) = gcd(b, a mod b)。它和边界条件gcd(a, 0)=a一起构成了下面的程序:

int gcd(int a, int b) {    
    return b == 0 ? a : gcd(b, a%b);
}

这个算法称为欧几里德算法(Euclid algorithm)。既然是递归,免不了问一句:会栈溢出吗?答案是不会。

可以证明,gcd函数的递归层数不超过4.785lgN + 1.6723,其中N=max{a,b}。

值得一提的是,让gcd递归层数最多的是gcd(Fn,Fn-1),其中Fn是后文要介绍的Fibonacci数。

利用gcd还可以求出两个整数ab最小公倍数lcm(a,b)。这个结论很容易由唯一分解定理得到。

可以验证gcd(a,b)*lcm(a,b)=a*b。不过即使有了公式也不要大意。——a*b可能会溢出!

正确的写法是先除后乘,即a/gcd(a,b) * b。这样一来,只要题面上保证最终结果在int范围之内,函数就不会出错。


【扩展欧几里得算法】

求直线ax+by+c=0上有多少个整点(x,y)满足x∈[x1, x2], y∈[y1, y2]。

分析》在解决这个问题之前,首先学习扩展欧几里德算法——找出一对整数(x,y),使得ax+by= gcd(a,b)

注意,这里的xy不一定是正数,也可能是负数或者0。

下面是扩展欧几里德算法的程序:

void gcd(int a, int b, int& d, int& x, int& y) {
  if(!b){ d = a; x = 1; y = 0; }
  else{ gcd(b, a%b, d, y, x); y -= x*(a/b); }
}

注意在递归调用时,xy的顺序变了,而边界也是不难得出的:gcd(a,0)=1*a-0*0=a

这样,唯一需要记忆的是y-=x*(a/b),哪怕暂时不懂得其中的原因也不要紧。

上面求出了ax+by=gcd(a,b)的一组解(x1,y1),那么其他解呢?

任取另外一组解(x2,y2),则ax1+by1=ax2+by2(它们都等于gcd(a,b)),变形得a(x1-x2)=b(y2-y1)。

假设gcd(a,b)=g,方程左右两边同时除以g(1),得a'(x1-x2)=b' (y2-y1),其中a'=a/gb'=b/g

注意,此时a'b'互素,因此x1-x2一定是b'的整数倍。设它为kb',计算得y2-y1=ka'

注意,上面的推导过程并没有用到“ax+by的右边是什么”,因此得出如下结论。

a, b, c为任意整数。若方程ax+by=c的一组整数解为(x0,y0),则它的任意整数解都可以写成(x0+kb', y0-ka'),

其中a'=a/gcd(a,b),b'=b/gcd(a,b),k取任意整数。

有了这个结论,移项得ax+by=-c,然后求出一组解即可。例如:

例1:6x+15y=9。根据欧几里德算法,6×(-2)+15×1=3,两边同时乘以3得6×(-6)+15×3=9,即x=-6,y=3时6x+15y=9。

例2:6x+15=8,两边除以3得2x+5=8/3。左边是整数,右边不是整数,显然无解。综合起来,有下面的结论。

a, b, c为任意整数,g=gcd(a,b),方程ax+by=g的一组解是(x0,y0),

则当cg的倍数时ax+by=c的一组解是(x0*c/g, y0*c/g);当c不是g的倍数时无整数解。


【函数(tolower/toupper)实现字母的大小写转换】

tolower/toupper函数实现原型:

    int tolower(int c) {  //大写转小写
        if ((c >= 'A') && (c <= 'Z'))  
            return c + ('a' - 'A');  
        return c;  
    }  
      
    int toupper(int c) {  //小写转大写
        if ((c >= 'a') && (c <= 'z'))  
            return c + ('A' - 'a');  
        return c;  
    }  

接下来用两个小demo来演示一下。

    #include<string.h>   //strlen  
    #include<stdio.h>    //printf  
    #include<ctype.h>    //tolower  
    int main() {  
        int i;  
        char string[] = "THIS IS A STRING";  
        printf("%s\n", string);  
        for (i = 0; i < strlen(string); i++) {  
            string[i] = tolower(string[i]);  
        }  
        printf("%s\n", string);  
        printf("\n");  
    }  

以上是C 的实现,同样的,在C++下的实现如下:

    #include <iostream>  
    #include <string>  
    #include <cctype>  
    using namespace std;  
    int main() {  
        string str= "THIS IS A STRING";  
        for (int i=0; i <str.size(); i++)  
           str[i] = tolower(str[i]);  
        cout<<str<<endl;  
        return 0;  
    }  



【二分图的判定】

给定一个具有n个顶点的图。要给图上每个顶点染色,并且要使相邻的顶点颜色不同。

判断是否能最多用两种颜色进行染色。题目保证没有重边和自环。

概念:把相邻顶点染成不同颜色的问题叫做图的着色问题。对图进行染色所需要的最小颜色数称为最小着色度。

最小着色度为2的图称作二分图。

 分析√:如果只用两种颜色,那么确定一个顶点的颜色之后,和它相邻的顶点的颜色也就确定了。

    因此,选择任意一个顶点出发,依次确定相邻顶点的颜色,就可以判断是否可以被2种颜色染色了。

    这个问题用深度优先搜索可以简单实现。

#include <bits\stdc++.h>
using namespace std;
#define MAX_V 1000 //输入 
vector<int> G[MAX_V];  //
int V;   //顶点数 
int color[MAX_V];  //顶点的颜色 (1 or -1
//顶点v,颜色c 
bool dfs(int v,int c){
    color[v] = c; //把当前顶点相邻的顶点扫一遍 
    for(int i = 0;i < G[v].size(); i++)  //如果相邻顶点已经被染成同色了,说明不是二分图 
        if(color[G[v][i]] == c) return false;
        //如果相邻顶点没有被染色,染成-c,看相邻顶点是否满足要求 
        if(color[G[v][i]] == 0 && !dfs(G[v][i],-c)) return false;
    } //如果都没问题,说明当前顶点能访问到的顶点可以形成二分图 
    return true;
}
void solve(){
    //可能是不连通图,所以每个顶点都要dfs一次 
    for(int i = 0;i < V; i++){
        if(color[i] == 0){
            //第一个点颜色为 1 
            if(!dfs(i,1)){
                cout << "No" << endl;
                return;
            }
        }
    }
}
int main() { //输入 } 
 
【拓展欧几里得】
1.求解不定方程a*x+b*y==gcd(a,b)
先给个解法推导吧:∵a=[a/b]*b+a%b;
又∵欧几里得知:gcd(a,b)==gcd(b,a%b);
∴([a/b]*b+a%b)*x+b*y=gcd(a,b);
∴[a/b]*b*x+(a%b)*x+b*y=gcd(a,b);
∴b*([a/b]*x+y)+(a%b)*x=gcd(b,a%b);
看到这里,我们不难发现:令:a'=b,x'=[a/b]*x+y,b'=a%b,y'=x;
整理后原式又变成了:a'*x'+b'*y'==gcd(a',b');
得出的解虽然最小,有可能是负数,所以只要加个b/gcd(a,b)再模上b/gcd(a,b)就行了; 
2.同余方程ax≡b(mod c)的求解
   (1)有解条件:当且仅当:gcd(a,c)|b;  即:b能被gcd(a,c)整除;
   (2)例:洛谷1082 同余方程ax≡1(mod b)
      分析:先根据有解的必要条件,显而易见: gcd(a,b)|1  ==>  gcd(a,b)==1;
      题目给的a,b一定互质;
      再化开:(a*x)%b==1%b
         ==>  (a*x-1)%b==0 ==>  a*x==k*b+1  (k∈Z)
         ==>  a*x-k*b==1 ==>  a*x-k*b==gcd(a,b)

【各个评测状态】

AC:Accept,程序通过。

CE:Compile Error,编译错误。

PC:Partially Correct,部分正确。

WA:Wrong Answer,答案错误。

RE:Runtime Error,运行时错误。

TLE:Time Limit Exceeded,超出时间限制。

MLE:Memory LimitExceeded,超出内存限制。

OLE:Output LimitExceeded,输出超过限制。

UKE:Unknown Error,出现未知错误。

【STL 中队列的使用(queue)】

一.基本操作:

push(x) 将x压入队列的末端  //因为堆,是无序的,只有顶部元素,不能push_back

pop() 弹出队列的第一个元素(队顶元素),注意此函数并不返回任何值

front() 返回第一个元素(队顶元素)

back() 返回最后被压入的元素(队尾元素)

empty() 当队列为空时,返回true

size() 返回队列的长度

头文件:#include <queue>

声明方法: 1、普通声明queue<int>q;

2、结构体 struct node{ int x, y; };  queue<node>q;

二.STL 中优先队列的使用方法(priority_queue)

优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素。但是它有一个特性,就是队列中最大的元素总是位于队首,所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大的元素出队。这点类似于给队列里的元素进行了由大互小的顺序排序。元素的比较规则默认按元素值由大到小排序,可以重载“<”操作符来重新定义比较规则。  

基本操作:empty() 如果队列为空返回真

pop() 删除对顶元素  push() 加入一个元素

size()        ??????         top() 返回优先队列对顶元素

在默认的优先队列中,优先级高的先出队。在默认的int型中先出队的为较大的数。

头文件:#include <queue>

1.普通方法:priority_queue<int>q;  //通过操作,按照元素从大到小的顺序出队

priority_queue<int,vector<int>,greater<int> >q;  //通过操作,按照元素从小到大的顺序出队

2、自定义优先级: priority_queue<int, vector<int>, cmp>q; //定义方法

//其中,第二个参数为容器类型。第三个参数为比较函数。

【cmath】cmath中常用库函数:

int abs(int i); 返回整型参数i的绝对值

double fabs(double x); 返回双精度参数x的绝对值

longlabs(long n); 返回长整型参数n的绝对值

double exp(double x); 返回指数函数e^x的值
doublelog(double x); 返回logex的值
doublelog10(double x) 返回log10x的值
doublepow(double x,double y) 返回x^y的值
doublepow10(int p) 返回10^p的值

double sqrt(double x) 返回+√x的值

double acos(double x) 返回x的反余弦arccos(x)值,x为弧度
doubleasin(double x) 返回x的反正弦arcsin(x)值,x为弧度
doubleatan(double x) 返回x的反正切arctan(x)值,x为弧度
doublecos(double x) 返回x的余弦cos(x)值,x为弧度
doublesin(double x) 返回x的正弦sin(x)值,x为弧度
doubletan(double x) 返回x的正切tan(x)值,x为弧度
double hypot(double x,double y) 返回直角三角形斜边的长度(z)
double ceil(double x) 返回不小于x的最小整数
double floor(double x) 返回不大于x的最大整数

int rand() 产生一个随机数并返回这个数

double atof(char *nptr) 将字符串nptr转换成浮点数并返回这个浮点数
doubleatol(char *nptr) 将字符串nptr转换成长整数并返回这个整数
doubleatof(char *nptr) 将字符串nptr转换成双精度数,并返回这个数,错误返回0
intatoi(char *nptr) 将字符串nptr转换成整型数, 并返回这个数,错误返回0
longatol(char *nptr) 将字符串nptr转换成长整型数,并返回这个数,错误返回0

【读入优化】

void read(int &x)  //'&'表示引用,x值会被一起改变

{   int f=1;//标记正负

    x=0;//归零(这就是潜在bug,有可能传进来时x没有归零)

    char s=getchar();//读入第一个字符

    while(s<'0'||s>'9'){  //不是数字字符

        if(s=='-')//有可能输入的不是'-'而是其他乱七八糟的东西

            f=-1;

        s=getchar();//继续读

    }

    while(s>='0'&&s<='9'){ //是数字(一旦不是字符就意味着输入结束了)

        x=x*10+s-'0';  s=getchar();

    }

    x*=f; } //改变正负(重新初始化)

简洁一些:

void read(int &x){

    int f=1;x=0;char s=getchar();

   while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} //判正负

   while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} //判停止

    x*=f; }    //重新初始化

这就是完整的读入优化了,直接使用: int N; read(N);

 

【unique函数的作用】

需要#include<iostream>。unique的作用是去掉容器中相邻元素的重复元素,这里去掉要加一个引号,为什么呢,是因为它实质上是一个伪去除,它会{重复的元素添加到容器末尾,而返回值是去重之后的尾地址}(是地址!!),举个例子:

int num[10]={1,1,2,2,2,3,4,5,5,5};

Int   ans=unique(num,num+10)-num;

返回的ans是5,而num中前5项就是1,2,3,4,5,一般使用前需要对容器进行排序,这样才能实现对整个数组去重。另:如果要对结构体进行这一操作,需要重载运算符"==",具体要根据自己需要重载。

【求一个数列的逆序数】

a[1],a[2],a[3]…中的任意两个数a[i],a[j] (i<j),如果a[i]>a[j],那么我们就说这两个数构成了一个逆序对

逆序数:一个数列中逆序对的总数    如数列 3 5 4 8 2 6 9中,(5,4)是一个逆序对,同样还有(3,2),(5,2),(4,2)等等

那么如何求得一个数列的逆序数呢?

方法1:一个一个的数

最简单也是最容易想到的方法就是,对于数列中的每一个数a[i],遍历数列中的数a[j](其中j<i),若a[i]<a[j],则逆序数加1,这样就能统计出该数列的逆序数总和。该方法的时间复杂度为O(n^2),具体过程就不细说了。

方法2:归并的思想

有一种排序的方法是归并排序,归并排序的主要思想是将整个序列分成两部分,

分别递归将这两部分排好序之后,再和并为一个有序的序列,核心代码如下:

MergeSort(first,last){

If(first==last) return

Int med=(first+last)/2;

MergeSort(first,med);

MergeSort(med+1,last);

Merge(first,last);

}

在合并的过程中是将两个相邻并且有序的序列合并成一个有序序列,如以下两个有序序列

Seq1:3  4  5

Seq2:2  6  8  9

合并成一个有序序:

Seq:2  3  4  5  6  8  9

对于序列seq1中的某个数a[i],序列seq2中的某个数a[j],如果a[i]<a[j],没有逆序数,如果a[i]>a[j],那么逆序数为seq1中a[i]后边元素的个数(包括a[i]),即len1-i+1,

这样累加每次递归过程的逆序数,在完成整个递归过程之后,最后的累加和就是逆序的总数

实现代码如下:

    const int LENGTH=100;
    int temp[LENGTH];  //额外的辅助数组
    int count=0;  
      
    void Merge(int * array,int first,int med,int last)  
    {  
        int i=first,j=med+1;  
        int cur=0;  
        while (i<=med&&j<=last)  
        {  
            if (array[i]<array[j])  
            {  
                temp[cur++]=array[i++];  
            }  
            else  
            {  
                temp[cur++]=array[j++];  
                <span style="color:#ff0000;">count+=med-i+1</span>;  //核心代码,逆序数增加  
            }  
        }  
        while (i<=med)  
        {  
            temp[cur++]=array[i++];  
        }  
        while (j<=last)  
        {  
            temp[cur++]=array[j++];  
        }  
        for (int m=0;m<cur;m++)  
        {  
            array[first++]=temp[m++];  
        }  
    }  
    void MergeSort(int *array,int first,int last)  
    {  
        if (first==last)  
        {  
            return ;  
        }  
        int med=first+(last-first)/2;  
        MergeSort(array,first,med);  
        MergeSort(array,med+1,last);  
        Merge(array,first,med,last);  
    } 

归并排序的复杂度为O(nlogn),当然此方法的复杂度也为O(nlogn)

方法3:用树状数组

还是以刚才的序列

3  5  4  8  2  6  9

大体思路为:新建一个数组,将数组中每个元素置0

0  0  0  0  0  0  0

取数列中最大的元素,将该元素所在位置置1

0  0  0  0  0  0  1

统计该位置前放置元素的个数,为0

接着放第二大元素8,将第四个位置置1

0  0  0  1  0  0  1

统计该位置前放置元素的个数,为0

继续放第三大元素6,将第六个位置置1

0  0  0  1  0  1  1

统计该位置前放置元素的个数,为1

这样直到把最小元素放完,累加每次放元素是该元素前边已放元素的个数,这样就算出总的逆序数来了

在统计和计算每次放某个元素时,该元素前边已放元素的个数时如果一个一个地数,那么一趟复杂度为O(n),总共操作n趟,复杂度为O(n^2),和第一种方法的复杂度一样了,那我们为什么还用这么复杂的方法

当然,在每次统计的过程中用树状数组可以把每一趟计数个数的复杂度降为O(logn),这样整个复杂度就变为O(nlogn)

 

树状数组是一种很好的数据结构,这有一篇专门描述树状数组的文章

将序列中的每个数按照从大到小的顺序插入到树状数组中,给当前插入节点及其父节点的个数加1,然后统计该节点下边及右边放置元素的个数

猜你喜欢

转载自blog.csdn.net/flora715/article/details/80375956