c++11, shared_ptr使用数组

2014年8月19日 21:52

最近被项目垃圾的内存管理搞的特别头大,正好这段时间在看《深入理解C++11:C++11新特性解析与应用》,了解了些c++11的特性。于是想着用c++11高大上的智能指针来做内存管理。

所谓高大上,相对于各种天生就支持垃圾回收的语言(如:java,python)来说也是个废才。支持个数组都麻烦的很,各方考证,下面写一个关于 shared_ptr 使用数组的小样例,主要参考 http://stackoverflow.com/questions/14957359/problems-with-shared-ptrt-wrapping-a-dynamic-array

基于shared_ptr的vector

下面是一个基于shared_ptr的vector模板。当然完全没有必要使用 shared_ptr去实现vector,vector的内存管理本来就是透明的。这个地方就是为了展示shared_ptr怎么使用数组。

template <class T>
class Vector {
    shared_ptr<T> ptr_;
    int size_, capacity_;
public:
    
    Vector()
    :ptr_(shared_ptr<T>(new T[initial_capacity], std::default_delete<T[]>())),
    capacity_(initial_capacity), size_(0) {}
    
    int size() const {
        return size_;
    }
    
    void push_back(const T &x) {
        if (size_ == capacity_) {
            capacity_ *= 2;
            T* new_memory = new T[capacity_];
            for (int i = 0; i < size_; ++i) {
                new_memory[i] = ptr_.get()[i];
            }
            ptr_ = shared_ptr<T>(new_memory, std::default_delete<T[]>());
        }
        ptr_.get()[size_++] = x;
    }
    
    const T& operator[] (int idx) const {
        return ptr_.get()[idx];
    }
    
    T& operator[] (int idx) {
        return ptr_.get()[idx];
    }
    static const int initial_capacity = 2;
};

这里有两个必须要注意的地方

  1. 除了使用指向内存的指针初始化 shared_ptr外,还需要传入析够数组的函数。不然当内存的引用数降到0时,会调用 delete 释放一个数组,直接导致程序崩溃,std::default_delete<T[]>()指明调用的是 delete[]
    shared_ptr<T>(new T[size], std::default_delete<T[]>()); 
  2. 既然没有直接支持数组,shared_ptr 也就没有重载 operator [] 了。查看相关文档https://gcc.gnu.org/onlinedocs/gcc-4.6.0/libstdc++/api/a00267.html才知道,shared_ptr 只支持了两个 operator,外加一个获取内存指针的get方法
    std::add_lvalue_reference< _Tp >::type operator* () const
    _Tp * operator-> () const;
    _Tp * get () const;
    
    因此想要通过下标访问数组元素就可以使用get() 获取原始数组指针,然后使用[]访问,或者直接把这个操作封装起来
    T& operator[] (int idx) {
        return ptr_.get()[idx];
    }

简单的测试

最后写一个简单的测试程序测试下 Vector 是不是能够zhe,测试程序如下:

class Point {
public:
    Point(int x = 0, int y = 0) { ++counter_; }
    ~Point() { --counter_; }
    static int counter() { return counter_; }
    int x, y;
private:
    static int counter_;
};

int Point::counter_ = 0;

void test_function() {
    Vector<Point> vec;
    Point p;
    for (int i = 0; i < 4; ++i) {
        p.x = i;
        vec.push_back(p);
    }
    cout << vec[vec.size() - 1].x << endl;
}

int main(int argc, const char * argv[]) {
    test_function();
    cout << Point::counter() << endl;
    return 0;
}

测试程序输出如下:

3
0

最后一行输出0,说明 test_function函数退出后 Point 类全部析够,正常!

题目大意

已知斐波那契数列F1 = 1, F2 = 2, Fi = Fi - 1 + Fi - 2 (i > 2)。定义Ai(k) = Fi × ik (i ≥ 1),A1(k) + A2(k) + ... + An(k)对1000000007(109+7)取模的结果。(1 ≤ n ≤ 1017; 1 ≤ k ≤ 40)

题目链接: http://codeforces.com/problemset/problem/392/C

解题报告

算法思路

这类题目基本上都是矩阵快速幂。

分析流程

1. 二项式

首先根据二项式定理

可以得到以下等式

2. 简写

为了方便后续公式的书写,我们做如下定义(简写),记

于是上面的公式可以简写为如下形式:

3. 矩阵构造

根据题目意思,令

同时定义一个 k乘1的列向量,该列向量前k-1行均为0,最后一行为1,记为Ik

构造如下矩阵,根据前面简写、定义,可知如下等式成立。

根据上述等式,很容易写出如下的通项公式

4. 初值

根据题目意思,斐波那契数列初值F1 = 1, F2 = 2,可以推断F0 = 1,于是通项公式的初值如下:

5. 求解

部分由矩阵快速幂求解。时间复杂度为O((2k + 3)log n)=O(k3 log n)。矩阵快速幂后乘以初值即可得到答案。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <set>
#include <queue>
#include <algorithm>

using namespace std;

typedef long long LL;

#define maxn 84
#define MOD 1000000007

LL A[maxn][maxn], C[41][41], ans[maxn][maxn], tp[maxn][maxn];

void gen(int k) {
    for (int i = 0; i <= k; ++i) {
        C[i][0] = C[i][i] = 1;
    }
    for (int i = 2; i <= k; ++i) {
        for (int j = 1; j < i; ++j) {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
        }
    }
    for (int i = 0; i <= k; ++i) {
        for (int j = i; j <= k; ++j) {
            A[i + k + 1][j] = A[i][j + k + 1] = A[i + k + 1][j + k + 1] = C[j][i];
        }
    }
    A[2 * k + 1][2 * k + 2] = 1;
    A[2 * k + 2][2 * k + 2] = 1;
    
    for (int i = 0; i < 2 * k + 3; ++i) {
        ans[i][i] = 1;
    }
}

void multi(LL C[][maxn], LL A[][maxn], LL B[][maxn], int n) {
    memset(tp, 0, sizeof(tp));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < n; ++k) {
                tp[i][j] = (tp[i][j] + A[i][k] * B[k][j] % MOD) % MOD;
            }
        }
    }
    memcpy(C, tp, sizeof(tp));
}

LL solve(LL n, int k) {
    gen(k);
    
    while (n) {
        if (n % 2) {
            multi(ans, A, ans, 2 * k + 3);
        }
        multi(A, A, A, 2 * k + 3);
        n /= 2;
    }
    
    LL res = 0;
    for (int i = 0; i < 2 * k + 2; ++i) {
        res = (res + ans[i][2 * k + 2]) % MOD;
    }
    return res;
}

int main() {
    LL n;
    int k;
    cin >> n >> k;
    
    cout << solve(n, k) << endl;
    

    return 0;
}

12个外观一样的球,其中有1个和其他11个质量不一样,如何用天平称三次把质量不一样的球找出来,并确定这个球是轻了还是重了 。

首先将所有的球编号,如下所示:

用如下颜色表示称量过程中可能出现的情况

第一步:将1、2、3、4放在天平的左边,5、6、7、8放在天平的右边

如果天平两边平衡

说明1、2、3、4、5、6、7、8都是正常的球,9、10 、11、12的情况为未确定

第二步:1、2、3放在天平的左边,9、10、11放在天平的右边

如果天平两边平衡

说明9、10、11是正常的球,12就是那个不正常的球。

 

第三步:将12号与一个正常的球分别放在天平的两边,即可确定12是重了还是轻了

如果天平左边下沉

说明9、10、11有可能轻了,12是正常的

 

第三步:把9、10分别放在天平的两边

如果天平平衡

说明9和10是正常的,11号球是不正常的球,并且这个球轻了

如果天平不平衡

那么上翘的那段就是不正常的球,并且这个球轻了

如果天平右边下沉

说明9、10、11有可能重了,12是正常的

 

第三步:把9、10放在天平两边,用类似上面的方法即可确定哪个球重了

如果天平不平衡

假设天平左边下沉,即1、2、3、4可能重了,5、6、7、8可能轻了

第二步:把9、10、3、7放在天平左边,1、2、5、6放在天平右边

如果天平左边下沉

根据之前的判断,7可能轻了不可能重了,现在7所在的一边下沉了,所以7肯定是正常的。同理1、2也是正常的

 

第三步:取两个正常的球(这里取1、2)放在天平的左边,3、5放在天平的右边

如果天平平衡

说明3、5都是正常的球,那么就是6轻了

 

如果天平右边下沉

根据之前的判断,3可能重了、5可能轻了,而右边下沉所以肯定是3重了

如果天平左边下沉

根据之前的判断,3可能重了、5可能轻了,而左边下沉(右边上浮)所以肯定是5轻了

如果天平右边下沉

根据之前的判断,3可能轻了不可能重了,现在3所在的一边上浮,所以3肯定是正常的。同理5、6也是正常的

用上面类似的方法进行第三步称量,可以确认1、2、7中哪个有问题,并且是什么问题

三角形内随机生成一个点

在三角形内生成一个点

已有一个能生成0到1之间的数,并且这些数是均匀分布的随机生成器,给定一个任意的三角形,如何能在三角形内随机的生成一个点?

如上面这个三角形,点D就是三角形内部随机的一个点。一种简单的方法是通过如下公式生成D点:

或者

其中,a和b是两个独立随机生成器

要求等概率的情况

以上的两个公式很好的解决了在三角形内随机生成一个点的问题。然而三角形内每一个点被取到的概率一样吗?

来说。a和b从0开始,每隔1/n取一个点。但n=3时,a和b的取值为{0, 1/3, 2/3, 1},将取到的D点在三角形种标出,如下图

很明显,靠近C点的地方D取值的概率更大。但n趋近于正无穷的时候,上面的图就是D取值的概率密度函数。很显然,三角形内每一个点被取到的概率并不相等。

为了克服这个问题,一名睿智的同学提出了如下的方法:

  1. 将三角形扩充成一个矩形

  2. 将矩形的两条边分别线性映射成一个随机生成器,这两个随机生成器相互独立

  3. 如果生成的D点在三角形外,将D以靠近的边为对称轴映射到三角形内的D'上。

显然随机生成的点在矩形内的分布是等概率的,第3部的映射也是一一对应的,因此在三角形内生成的点也是均匀分布的。

多边形内随机生成一个点

多边形可以先分割成多个三角形。根据面积的比率,使用一次随机生成器确定点落在哪个三角形内。然后在使用上面的方法在三角形内随机生成一个点。

圆内随机生成一个点

给定一个半径为R的圆,如何在圆内等概率随机生成一个点?

圆用笛卡尔坐标系很不好处理,自然想到使用极坐标系。a表示OD与极轴的夹角,r表示D到坐标原点的距离。a从0度到360度,r从0到R。

如果将a和r直接线性映射到两个独立的随机生成器上。很明显圆内的概率密度函数并不相等。

当r为一个定值,D的轨迹为一个圆,圆的周长=2 πr。如果我们能使D点落在任意一个圆的周长的单位长度上的概率相等,即可使圆内概率相等。简单的说,如果有一个随机生成器取r的概率和r线性相关,就可以搞定这个问题。

构造如下的方法解决这个问题:

如图,构造一个边长为R的等腰直角三角形。使用等概率在三角形内生成一个点的方法生成D点。将R减D点的x坐标设置为r。

此时,r取值概率与r的大小线性相关,满足上面条件。因此,用此方法生成的点在圆内等概率分布。

markdown 和pandoc

2013年8月21日 17:46

今天接触到两个很好用的东西,一个是markdown,一个是pandoc。在此mark下。

markdown

markdown是一种非常简介的文本标记语言,网上也有很多教程,这里就不多说了。尝尝鲜,篇博文就是用markdown写的。

pandoc

什么是pandoc呢?官网上是这么是的“如果你要把文件从一种文本标记语言转到另一种文本标记语言,pandoc就是你的瑞士'军刀'”

下面是张来自于官网的一张关于pandoc转换能力的图片,果断亮瞎双眼

pandoc能转换的格式

转换

从md文件转化成latex文件格式执行如下代码 pandoc file.md -o file.tex

从md文件直接转化成pdf文件格式执行如下代码 pandoc file.md -o file.pdf

pandoc对中文支持不是很好,md转化成tex没有问题,转化成pdf中文出不来。这个有待进一步折腾折折腾

干什么用

  • 我准备用markdown来写日常的一些小文档,可以html格式发布。如果能解决pdf中文问题,也可以直接转成pdf;如果不行,那就先转成latex然后在转pdf。

  • 由于markdown较tex简洁很多,可以先用markdown搭框架,然后转tex再添细节。虽然这有点多此一举,tex的标签确实复杂了点

  • 之前用latex排的东西可以直接转成html格式发布