20180718

Posted perfy576

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20180718相关的知识,希望对你有一定的参考价值。

1 算法

1.1 有限制条件的排列组合

? 难点在于分析从[i-u-1,i-1]是G的这一段排列组合如何求

Attack on Titans

给n个士兵排队,每个士兵三种G、R、P可选,求至少有m个连续G士兵,最多有k个连续R士兵的排列的种数.最终结果对1000000007取模.

因为两种士兵的限制条件不一样,一个是至多,一个是至少.并且至多的情况好考虑,至少的情况复杂

因此对其中一个进行转化:

至少由m个G士兵连续=至多由n个G士兵连续-至多由m-1个G士兵连续:这个的意思就是,至少由m个G士兵连续可以转化:士兵中任意个G士兵连续的情况个数,减去,至多m-1个G士兵连续的情况,那么剩下的就是[m,n]个士兵连续的情况了,这样就是至少由m个G士兵连续了.

因此需要一个函数,能够计算,在一队由n个士兵的时候,至多g个G,至多r个R的排列组合数.P个数任意.

// dp[i][0] 表示,当第i个是G的时候,满足上述要求的队列的个数
// dp[i][1] 表示,当第i个是R的时候,满足上述要求的队列的个数
// dp[i][2] 表示,当第i个是P的时候,满足上述要求的队列的个数

然后,当计算第i的士兵的时候排完以后,符合要求的排队的个数应该是有

total =dp[i][0]+dp[i][1]+dp[i][2]

因为,三个dp[i][]的值,分别是当第i个为不同的士兵的时候,满足要求的排列的个数,所以总的满足要求的排列的个数,就是三个dp的和.

然后对于dp[i][0]dp[i][1]的求法,是一样的(都在求一个至多),以dp[i][0]为例.也就是至多有g个G,i从1开始

i<g的时候,因为此时总共插入的G的个数也不足g个,所以怎么排列,都不会超过g.此时dp[i][0]=上一次符合要求的排队个数

i=g+1的时候,要排除前面插入的都是G的那一种排列,也就是dp[i][0]=上一次符合要求的排队个数-1,因为前面i个都是G的时候,如果第i个也是G,那么就不满足,至多有g个G,所以要减去这种情况

i>=g+1,此时就要考虑,第i-g到第i个都是g的情况dp[i][0]=上一次符合要求的排队个数-(i-u-1到i-1都是G的情况)

如果从i-1都i-u都是G,那么i-u-1就一定不是G,如果是G,那么在i-1的时候就不符合了.因此,在i-u-1的时候需要是P和R.然后在计算i-u的情况时,就是dp[i-u-1][1]+dp[i-u-1][2]就是i-1i-u都是G的情况,也就是说i-u-1不能是G,所以i-u只是前两个是P和R的情况,然后i-u+1只能是i-u的值因为i-u固定为G

const int mod =1000000007;
int cal(int n,int g,int r)
{
            //memset(dp,0,sizeof dp);
    dp[0][2]=1;                 //初始化时,对于第0个位置,整体为1即可,也可理解为对第0个位置放P,并没有什么影响。
    dp[0][0]=dp[0][1]=0;

    for(int i=1;i<=n;i++)
    {
        ll sum=((dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%mod+mod)%mod;

        dp[i][2]=sum;

        if(i<=g) dp[i][0]=sum;
        if(i==g+1) dp[i][0]=((sum-1)%mod+mod)%mod;
        if(i>ug+1)  dp[i][0]=((sum-dp[i-u-1][1]-dp[i-u-1][2])%mod+mod)%mod;

        if(i<=r) dp[i][1]=sum;
        if(i==r+1) dp[i][1]=((sum-1)%mod+mod)%mod;
        if(i>vr+1)  dp[i][1]=((sum-dp[i-v-1][2]-dp[i-v-1][0])%mod+mod)%mod;
    }
    return (dp[n][0]+dp[n][1]+dp[n][2])%mod;
}

就可以了,主要在于如何计算i-1到i-u都是G的情况

所以总的代码为:

#include <iostream>
#include <cstdio>
typedef long long LL;
const int MAXN=1100000;
const int M=1000000007;
LL dp[MAXN][3];
//dp[i][0]表示第i个为G,至多有u个连续G,至多有v个连续R的个数
//dp[i][1]表示第i个为R,....
//dp[i][2]表示第i个为P,....
LL n,m,k,u,v;
 
LL Cal()
{
    dp[0][0]=1; //初始状态
    dp[0][1]=0;
    dp[0][2]=0;
    for(int i=1;i<=n;i++)
    {
        LL sum=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%M;
 
        dp[i][2]=sum;
 
        if(i<=u) dp[i][0]=sum;
        else if(i==u+1) dp[i][0]=(sum-1)%M;
        else dp[i][0]=(sum-dp[i-u-1][1]-dp[i-u-1][2])%M;
 
        if(i<=v) dp[i][1]=sum;
        else if(i==v+1) dp[i][1]=(sum-1)%M;
        else dp[i][1]=(sum-dp[i-v-1][0]-dp[i-v-1][2])%M;
    }
    return (dp[n][0]+dp[n][1]+dp[n][2])%M;
}
 
int main()
{
    while(~scanf("%lld%lld%lld",&n,&m,&k))
    {
        LL ans;
        u=n,v=k;
        ans=Cal();
        u=m-1,v=k;
        ans=((ans-Cal())%M+M)%M;
        printf("%lld
",ans);
    }
    return 0;
}

1.2 扑克牌的正反组合

? 如果理解了[i-u-1,i-1]都是G的排列数,那么难点在于大数的计算

10328 - Coin Toss

有n张牌,求出至少有k张牌连续是正面的排列的种数。(类似的都行),但是这里没有让模1000000007,因此,需要考虑,size_t存不下的情况

和上个题一样,也要转化为至多的情况,因为至多的情况容易判断.

n张牌,总共的排列方式有(2^n)种,因此,减去,最多由(k-1)张连续正面的情况,那么剩下的情况就是至少有(k)张正面的情况.

因此最终这个题,就要求,最多有(k-1)张连续正面的排列组合的方式.

// dp[i][0] 表示第i张为正面,且连续不超过k-1时候的排列数
// dp[i][1] 表示第i张为反面,且连续不超过k-1时候的排列数

因此

因为第i张是反面,不影响正面连续的张数,因此dp[i][1]=dp[i-1][0]+dp[i-0][1],也就是说,第i张为反面,且连续不超过k-1的时候,的排列数,等于,dp[i-1][0]dp[i-1][1]的和.

当第i章是正面的情况,要判断:

  1. i<m那么,无影响,因为所有牌的数量,总数都没超过m,因此最多有m-1张连续正面
  2. i=m需要排除第一张是正面的那一种情况,因为当第一张是正面,那么就有m张连续正面,因此要减去dp[i][0]=dp[i-1][0]+dp[i-1][1]-1
  3. i>m时,那么需要排除第i-1往前到i-(m-1)-1张都是正面的情况,而这种情况,恰恰就是dp[i-(m-1)-1][1],原因在于,第i-1往前到i-(m-1)-1张都是正面这种排列组合和dp[i-(m-1)-1][1]是相同的,因此是:`dp[i][0]=dp[i-1][0]+dp[i-1][1]-dp[i-k-1][1]

但是这个题,最后没有要求对1000000007取模,因此存在一个问题是,可能size_t存不了,因此需要使用大数

思路就是这样,但是这里存在一个大数的问题

#define Maxn 110
 
// 第三个维度是一个数组,用于存储大数的
int dp[Maxn][2][Maxn];// dp[i][0]表示第i个为H,至多出现连续k个连续H,个数
int n,k;
int temp[Maxn];
int ans[Maxn];
int res[Maxn];
 
void add(int a[],int b[]) //加法
{
    int aa=0;
    memset(res,0,sizeof(res));
 
    for(int i=100;i>=1;i--)
    {
        res[i]=(a[i]+b[i]+aa)%10;
        aa=(a[i]+b[i]+aa)/10;
    }
 
}
 
void sub(int a[110],int b[110]) //减法
{
    int cur=0;
    memset(res,0,sizeof(res));
 
    for(int i=100;i>=1;i--)
    {
        if(a[i]-cur>=b[i])
        {
            res[i]=a[i]-cur-b[i];
            cur=0;
        }
        else
        {
            res[i]=a[i]+10-b[i]-cur;
            cur=1;
        }
    }
}
 
int main()
{
   //freopen("in.txt","r",stdin);
   //freopen("out.txt","w",stdout);
   while(~scanf("%d%d",&n,&k))
   {
       memset(dp,0,sizeof(dp));
       memset(ans,0,sizeof(ans));
 
       ans[100]=1;
       dp[0][0][100]=1;
       k--;
 
       for(int i=1;i<=n;i++)
       {
           add(dp[i-1][0],dp[i-1][1]);
           memcpy(dp[i][1],res,sizeof(res));
 
           if(i<=k)
           {
               add(dp[i-1][0],dp[i-1][1]);
               memcpy(dp[i][0],res,sizeof(res));
           }
 
           else if(i==k+1)
           {
               memset(temp,0,sizeof(temp));
               temp[100]=1;
               add(dp[i-1][0],dp[i-1][1]);
               memcpy(dp[i][0],res,sizeof(res));
               sub(dp[i][0],temp);
               memcpy(dp[i][0],res,sizeof(res));
           }
           else
           {
               add(dp[i-1][0],dp[i-1][1]);
               memcpy(dp[i][0],res,sizeof(res));
               sub(dp[i][0],dp[i-k-1][1]);
               memcpy(dp[i][0],res,sizeof(res));
           }
 
           add(ans,ans);
           memcpy(ans,res,sizeof(res));
           int j=1;
           while(!ans[j]&&j<=100)
                j++;
 
       }
 
       sub(ans,dp[n][0]);
 
       memcpy(ans,res,sizeof(res));
 
       sub(ans,dp[n][1]);
       memcpy(ans,res,sizeof(res));
 
       int i=1;
       while(!ans[i]&&i<=100)
            i++;
       printf("%d",ans[i++]);
       while(i<=100)
            printf("%04d",ans[i++]);
       putchar('
');
 
   }
   return 0;
}

这些ACMER的变量用的真是66

1.3 总结

这两个DP都是求一个连续至少或是至多n个的情况,其中第一题涉及到2个限制,但是在dp的时候,直接被拆开了,独立计算,两者之间并没有什么影响

并且,都是将至少,转换位至多来求解的.

1.4 因数分解

给定一个整数,将其因数分解

#include <stdio.h>
int main() {
    int num, i;
    printf( "please input a num:" );
    scanf( "%d", &num );
    printf( "%d=", num );
    for ( i = 2; i <= num; i++ ) {
        while ( num % i == 0 ) {
            num /= i;
            if ( num != 1 ) {
                printf( "%d*", i );
            } else {
                printf( "%d", i );
            }
        }
    }
    printf( "
" );
    return 0;
}

正确性在于:从最小的质数2开始,一直除能够分解出来的最小质数,当不能的时候,再将质数++,因此当是一个偶数的时候,内部的where循环出来的时候num就编程一个奇数了.

优化:主要还是对除2的处理,还有对最大可能的质数的判断.

#include <math.h>
#include <stdio.h>

void prime_factors( int n ) {
    int i;
    while ( n % 2 == 0 ) {
        n = n / 2;
        printf( "%d ", 2 );
    }

    for ( i = 3; i <= sqrt( n ); i += 2 ) {
        while ( n % i == 0 ) {
            n = n / i;
            printf( "%d ", i );
        }
    }

    if ( n > 2 ) {
        printf( "%d", n );
    }
    printf( "
" );
}

void main() {
    int n = 315;
    prime_factors( 315 );
}

2 传输层安全

SSL安全套接层(Secure SOckerts Layer,SSL)

TLS传输层安全协议(Transport Layer Security)

2.1 TLS体系结构

技术分享图片

TLS为TCP提供了可靠的端到端的安全服务,是一个两层协议.

记录协议

TLS记录协议为高层协议提供安全服务,主要提供两种服务,保密性和消息完整性.

TLS记录协议,将上层传输的消息,分块,压缩(可选),加上MAC,加密,加上TLS头.

  1. 分块:将上层协议分成若干小于或等于16384字节的段,
  2. 压缩:可选,必须是无损压缩.
  3. 计算MAC:对压缩过的数据计算消息认证码MAC,TLS采用HMAC算法.(就是计算数据块的hash值),然后附加在数据块后面.
  4. 加密:采用对称加密,AES或是3DES.以上三个步骤增加长度不超过1024字节
  5. TLS协议头:内容类型8,主版本号8,从版本号8,压缩长度16.附加在数据的开头

技术分享图片

修改密码规范协议

协议有一个包含一字节值位1的消息组成,用于更新次链接使用的密码组.

警报协议

用于想对等实体传递TLS相关的警报.

握手协议

TLS的复杂性主要是来自于捂手协议,此协议允许客户端和服务端相互认证,协商加密和MAC的计算算法.

握手协议在传递应用数据之前使用.

握手协议一共10中类型的消息.每个消息由3个字段:

  1. 类型:1字节,10种消息中的一种
  2. 长度:3字节,消息的字节长度
  3. 内容:与消息相关的参数
消息类型 参数 作用
hello_request
client_hello 版本号,随机数,会话标识,密码组,压缩方法
server_hello 版本号,随机数,会话标识,密码组,压缩方法
certificate X.509v3证书链
server_key_exchange 参数,签名
certificate_request 类型,认证机构
server_done
certificate_verify 签名
client_key_exchange 参数,签名
finished Hash值

2.2 握手协议流程

明天补

3 高并发调试

? 这里发现一个很好的gcc的技巧的书链接

多线程和高并发环境下,如果有一个平均运行一百万次才会出现一次的bug,如何调试这个bug

解决的方法大体思路为:

  1. gdb及debug log定位,找到core的原因
  2. 构造线下环境,重现bug

3.1 core dump调试

linux下调试core dump文件

gdb很容易找到导致core dump的点,bt查看此时的函数调用,可以知道出现问题的上下文.

但是在多线程,情况下并不能找到导致变量更改的原因.就是说,A线程在使用一个变量的时候,其他线程更改了变量,导致core dump.

3.2 log

将日志级别调整到DEBUG,core可能会无法重新啊,因为DEBUG日志信息大频繁的写磁盘,导致并发量下降,因此,无法很好的重新bug

3.3 重现bug高并发调试

根据bug出现的时间,以及可能的原因,分析导致bug的原因.

高并发情形下的,内存泄漏.(意思是只有在高并发的时候才会出现内存泄漏)

valgrind缺点,在高并发的情形下,valgrind调试会减低程序的性能.因此不能很好的模拟高并发的情况.

使用gcc的address sanitizer来代替:链接

gcc从4.8版本起,集成了Address Sanitizer工具,可以用来检查内存访问的错误(编译时指定“-fsanitize=address”)

例如:

// client.cc
#include <stdio.h>

int main(void) {
        // your code goes here
        int a[3] = {0};
        a[3] = 1;

        printf("%d
", a[3]);
        return 0;
}
 g++ ./client.cc -fsanitize=address -g

技术分享图片

4 性能热点分析

工具有:

  1. perf: 最全面最方便的一个性能检测工具,ubuntu默认不安装,使用sudo apt install linux-tools-common安装,内核版本不同,可能需要安装其他的类库.
  2. oprefile:基本被opreport取代,命令使用起来也不方便
  3. gprof: 主要是针对应用层程序的性能分析工具,缺点是需要重新编译程序,对程序的戏能由一定影响,不支持内核层面的统计,有点事对应用层统计比较精细.很人性,易读.
  4. systemtap: 一个运行时的程序或是程序信息采集框架

4.1 perf

perf是一个包含22种子工具的工具集,以下是最常用的5种:

list,stat,top,record,report

教程链接1 教程链接2

执行的时候需要root权限

perf 子命令 程序

perf stat 程序

技术分享图片

加上-r 10表示重复10次,-v显示更多信息,-n显示程序执行的时间

perf record 程序生成一个perf.data文件,然后使用perf report -i perf.data查看该文件

但是这时候仍然不利于分析,可以使用火焰图进行分析perf+火焰图

过于复杂的命令,暂时看这些,估计以后不会补了.实际用到在说吧,很强大的工具.

4.2 systemtap

这貌似是一个更加强大的工具,学习难度更大,so,贴个链接吧

systemtap gitbook

5 移动定位LBS

LBS基于位置服务

用户用手机上网时,如何确定用户位置,目前的LBS平台,主要采用三种方式辅助用户定位:

GPS物理模块

使用中基于卫星定位系统,用于获得物理位置信息以及准确的通用协调时间,由美国政府完全负责,原理是:通过测量出卫星到用户接收机之间的距离,然后总和多颗卫星数据就能知道接收机的具体位置.

精度位10米,可以在任何天气条件和全球任何地方工作,GPS的缺点主要有两个:

  1. 室内无法使用,因为需要连接卫星,如果由建筑物等障碍物遮挡,连接质量无法得到保障
  2. 功率大,启动时间慢,耗电.

运营商基站定位

手机可以非常方便高效的采集手机信号所接入的用户基站cell的标识信息,CELLID,全球唯一.移动电话通过测量不同基站的下行导频信号得到不统计站下的导频TOA(到达时刻),根据测量结果并结合基站的坐标,一般采用三角公式股计算法,就能够计算出移动电话的位置.一般而言,基站的数量越多,测量精度越高.

wifiMAC定位

wifi的mac全球唯一,因此原理同上.

6 gcc使用

6.1 取消添加预定义宏

添加预定义宏,意思就是,没有在文件写,但是希望在编译的时候加上一个宏,使用-D命令.例如-D DEBUG表示定义DEBUG这个宏,可能由编写的代码内部,判断一下,输出不同的日志级别等.

取消一个宏,与-D相反,使用-U命令

6.2 运行期自动加测内存问题

在编译的时候添加-fsanitize=address选项,当遇到越界访问,野指针会直接停止,内存泄漏会在程序执行完毕的时候打印.

6.3 运行期多线程资源竞争

指的是,多线程在没有加锁的情况下修改同一变量.

在编译的时候使用-fsanitize=thread -fPIE -pie选项

6.4 强制inline

对于c++,inline关键字,是否inline是编译器说的算的

但是使用__attribute__ ((always_inline))前缀,告诉编译器,强制inline

#if defined(__GNUC__)
#define FORCEDINLINE        __attribute__((always_inline))
#else   
#define FORCEDINLINE
#endif
FORCEDINLINE    int add(int a,int   b)
{
        return  a+b;
}

6.5 禁止函数被优化掉

在编译期开了优化的情况下,可能一个文件中的函数,没有被调用,而被编译器优化掉,也就是说没有生成相对应的代码,那么以后在链接的时候就会出错.

使用__attribute__ ((__used__))函数前缀,强制告诉编译器把函数相应的汇编代码生成.

7 HTTP2

HTTP1.1

HTTP1.1的缺点:

  1. HTTP被看作是一个相对简单的协议,但是早起设计不尽人意,因此到HTTP1.1技术文档增加到176页
  2. HTTP1.1设计预留了很多的选项,几乎没有那个软件实现了所有的细节.
  3. HTTP1.1的设计并没有很好的使用TCP强大的功能,原因在与HTTP1.1收发数据的模式.
  4. HTTP Pipelining:把多个HTTP请求放到一个TCP链接中一一发送,发送过程中不需要等待服务器对前一个请求的响应,因此客户端必须从第一个相应的数据开始处理,也就是 线头阻塞(如果某一缓存头部的包由于拥塞而不能交换到一个输出端口,那么该缓存中余下的包也会被线头包所阻塞,即使这些包的目的端口并没有拥塞。),一个资源排入一个发送/处理队列以后,不能更换队列(根据不同链接的负载进行均衡).因此到目前,大部分的浏览器客户端都会关闭这一功能
  5. HTTP1.1只能对同意服务器发起两个2TCP链接
  6. 当一个含有确切值的Content-Length的HTTP消息被送出之后,不能够停止的他发送.

为了避免HTTP这种请求的缺点,也就有了,小图拼大图的方式,base64内联在css中的方式,js文件拼接.将文件分服务器存放,使他们能够使用不同的链接.

HTTP2

HTTP2起源于google牵头的SPDY协议.同时TLS是可选的

因为HTTP2必须要维持HTTP1.1的范式,因此是基于TCP,不改变链接格式.整除运算,直

如何通知客户端使用HTTP2来发起链接,SPDY在TLS之上实现HTTP2,因此它简化出一个NPN(Next Protocol Negotiation)协议.然后IETF将其规范化形成ALPN.ALPN与NPN的区别在于,ALPN中由服务器决定通信是否使用HTTP2,而NPN中由客户端决定.协商http2的方法就是通过给服务器发送一个带升级头部的报文。如果服务器支持http2,它将以“101 Switching”作为回复的状态码,并从此开始在该连接上使用http2.因此会浪费一个往返的延迟.同时curl也支持了这种方式.

HTTP2是一个二进制的协议,HTTP2传输数据的时候每个帧都关联一个流(没懂),可以使用多路复用.同时每个流都有一个权重.避免了线头阻塞.

HTTP2可以主动的终止一个消息的发送

HTTP2由服务器推送的功能,就是说,当客户端请求A资源时,服务器可以根据需要发送A资源的同时,推送B资源.

HTTP2在应用层上,还实现了一个流量窗口,告诉对端还有多少空间来处理新数据.

以上是关于20180718的主要内容,如果未能解决你的问题,请参考以下文章

20180718

26期20180718 rsync

linux学习第3天

微信小程序代码片段

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js