20180718
Posted perfy576
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20180718相关的知识,希望对你有一定的参考价值。
1 算法
1.1 有限制条件的排列组合
? 难点在于分析从[i-u-1,i-1]是G的这一段排列组合如何求
给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-1
到i-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的排列数,那么难点在于大数的计算
有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章是正面的情况,要判断:
i<m
那么,无影响,因为所有牌的数量,总数都没超过m
,因此最多有m-1
张连续正面i=m
需要排除第一张是正面的那一种情况,因为当第一张是正面,那么就有m
张连续正面,因此要减去dp[i][0]=dp[i-1][0]+dp[i-1][1]-1
- 当
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头.
- 分块:将上层协议分成若干小于或等于16384字节的段,
- 压缩:可选,必须是无损压缩.
- 计算MAC:对压缩过的数据计算消息认证码MAC,TLS采用HMAC算法.(就是计算数据块的hash值),然后附加在数据块后面.
- 加密:采用对称加密,AES或是3DES.以上三个步骤增加长度不超过1024字节
- TLS协议头:内容类型8,主版本号8,从版本号8,压缩长度16.附加在数据的开头
修改密码规范协议
协议有一个包含一字节值位1的消息组成,用于更新次链接使用的密码组.
警报协议
用于想对等实体传递TLS相关的警报.
握手协议
TLS的复杂性主要是来自于捂手协议,此协议允许客户端和服务端相互认证,协商加密和MAC的计算算法.
握手协议在传递应用数据之前使用.
握手协议一共10中类型的消息.每个消息由3个字段:
- 类型:1字节,10种消息中的一种
- 长度: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
解决的方法大体思路为:
- gdb及debug log定位,找到core的原因
- 构造线下环境,重现bug
3.1 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 性能热点分析
工具有:
- perf: 最全面最方便的一个性能检测工具,ubuntu默认不安装,使用
sudo apt install linux-tools-common
安装,内核版本不同,可能需要安装其他的类库. - oprefile:基本被opreport取代,命令使用起来也不方便
- gprof: 主要是针对应用层程序的性能分析工具,缺点是需要重新编译程序,对程序的戏能由一定影响,不支持内核层面的统计,有点事对应用层统计比较精细.很人性,易读.
- systemtap: 一个运行时的程序或是程序信息采集框架
4.1 perf
perf是一个包含22种子工具的工具集,以下是最常用的5种:
list,stat,top,record,report
执行的时候需要root权限
perf 子命令 程序
perf stat 程序
加上-r 10
表示重复10次,-v
显示更多信息,-n
显示程序执行的时间
perf record 程序
生成一个perf.data
文件,然后使用perf report -i perf.data
查看该文件
但是这时候仍然不利于分析,可以使用火焰图进行分析perf+火焰图
过于复杂的命令,暂时看这些,估计以后不会补了.实际用到在说吧,很强大的工具.
4.2 systemtap
这貌似是一个更加强大的工具,学习难度更大,so,贴个链接吧
5 移动定位LBS
LBS基于位置服务
用户用手机上网时,如何确定用户位置,目前的LBS平台,主要采用三种方式辅助用户定位:
GPS物理模块
使用中基于卫星定位系统,用于获得物理位置信息以及准确的通用协调时间,由美国政府完全负责,原理是:通过测量出卫星到用户接收机之间的距离,然后总和多颗卫星数据就能知道接收机的具体位置.
精度位10米,可以在任何天气条件和全球任何地方工作,GPS的缺点主要有两个:
- 室内无法使用,因为需要连接卫星,如果由建筑物等障碍物遮挡,连接质量无法得到保障
- 功率大,启动时间慢,耗电.
运营商基站定位
手机可以非常方便高效的采集手机信号所接入的用户基站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的缺点:
- HTTP被看作是一个相对简单的协议,但是早起设计不尽人意,因此到HTTP1.1技术文档增加到176页
- HTTP1.1设计预留了很多的选项,几乎没有那个软件实现了所有的细节.
- HTTP1.1的设计并没有很好的使用TCP强大的功能,原因在与HTTP1.1收发数据的模式.
- HTTP Pipelining:把多个HTTP请求放到一个TCP链接中一一发送,发送过程中不需要等待服务器对前一个请求的响应,因此客户端必须从第一个相应的数据开始处理,也就是 线头阻塞(如果某一缓存头部的包由于拥塞而不能交换到一个输出端口,那么该缓存中余下的包也会被线头包所阻塞,即使这些包的目的端口并没有拥塞。),一个资源排入一个发送/处理队列以后,不能更换队列(根据不同链接的负载进行均衡).因此到目前,大部分的浏览器客户端都会关闭这一功能
- HTTP1.1只能对同意服务器发起两个2TCP链接
- 当一个含有确切值的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的主要内容,如果未能解决你的问题,请参考以下文章