DQS DQS

HIT Summer Training Day8 树状数组|线段树

in 算法,线段树&&树状数组,数据结构read (86) 文章转载请注明来源!

扫描线会BB不会写,一万年写不出来……你看这次不就GG了。

还是弱,码力极差。


A : poj - 3468

线段树裸题

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;

typedef long long LL;
const int SZ = 1000010;
const int INF = 1000000010;

int n,m,a[SZ];

struct segment
{
    int l,r;
    LL add,sum;
}tree[SZ * 4];

void pushadd(int p,LL d)
{
    tree[p].add += d;
    tree[p].sum += d * (tree[p].r - tree[p].l + 1);
}

void pushdown(int p)
{
    if(tree[p].add)
    {
        LL d = tree[p].add;
        tree[p << 1].add += d;
        tree[p << 1 | 1].add += d;
        tree[p << 1].sum += d * (tree[p << 1].r - tree[p << 1].l + 1);
        tree[p << 1 | 1].sum += d * (tree[p << 1 | 1].r - tree[p << 1 | 1].l + 1);
        tree[p].add = 0;
    }
}

void update(int p)
{
    tree[p].sum = tree[p << 1].sum + tree[p << 1 | 1].sum;
}

void build(int p,int l,int r)
{
    tree[p].l = l; tree[p].r = r;
    if(l == r)
    {
        tree[p].sum = a[l]; tree[p].add = 0;
        return ;
    }
    int mid = l + r >> 1;
    build(p << 1,l,mid); build(p << 1 | 1,mid + 1,r);
    update(p);
}

LL ask(int p,int l,int r)
{
    if(l <= tree[p].l && tree[p].r <= r)
        return tree[p].sum;
    pushdown(p);
    int mid = tree[p].l + tree[p].r >> 1;
    LL ans = 0;
    if(l <= mid) ans += ask(p << 1,l,r);
    if(mid < r) ans += ask(p << 1 | 1,l,r);
    return ans;
}

void add(int p,int l,int r,LL d)
{
    if(l <= tree[p].l && tree[p].r <= r)
        { pushadd(p,d); return ; }
    pushdown(p);
    int mid = tree[p].l + tree[p].r >> 1;
    if(l <= mid) add(p << 1,l,r,d);
    if(mid < r) add(p << 1 | 1,l,r,d);
    update(p);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++)
        scanf("%lld",&a[i]);
    build(1,1,n);
    char str[20];
    for(int i = 1;i <= m;i ++)
    {
        scanf("%s",str);
        int l,r;
        LL x;
        if(str[0] == 'Q')
            scanf("%d%d",&l,&r),printf("%lld\n",ask(1,l,r));
        if(str[0] == 'C')
            scanf("%d%d%lld",&l,&r,&x),add(1,l,r,x);
   }

    return 0;
}


B : poj - 2352

给一些星星的坐标,一个星星的等级定义为x和y坐标都小于等于它的星星的数量。问每个等级的星星有多少个。

对x坐标排序,遇到一个点就插入。查询第i个点的等级时,答案为$y<=y_i$的点的数量,可以用树状数组维护。

需要注意x和y可能是0,而树状数组只能维护非零数,所以输入后要++。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mem(a,t) memset(a,t,sizeof(a))
using namespace std;

typedef long long LL;
const int SZ = 300010;
const int INF = 1000000010;

int n,m;

int bit[SZ];

int ask(int x)
{
    int ans = 0;
    for(int j = x;j > 0;j -= j & -j)
        ans += bit[j];
    return ans;
}

void change(int x,int d)
{
    for(int j = x;j <= 50000;j += j & -j)
        bit[j] += d;
}

struct node
{
    int x,y;
}l[SZ];

bool cmp(node a,node b)
{
    if(a.x != b.x) return a.x < b.x;
    return a.y < b.y;
}

int ans[SZ];

int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)
        scanf("%d%d",&l[i].x,&l[i].y),l[i].x ++,l[i].y ++;
    sort(l + 1,l + 1 + n,cmp);
    for(int i = 1;i <= n;i ++)
    {
        int x = l[i].y,lev = ask(l[i].y);
        ans[lev] ++;
        change(l[i].y,1);
    }
    for(int i = 0;i <= n - 1;i ++)
        printf("%d\n",ans[i]);
    return 0;
}


C : poj - 2299

求逆序对。

归并排序做法就不说了。

树状数组:先离散化,按顺序插入。插入每个点统计一下比它大的点的数量,累加即可。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
 
typedef long long LL;
const int SZ = 1000010;
const int INF = 1000000010;
const int mod = 10000;
 
LL ans = 0;
int a[SZ],tmp[SZ],n;
 
void mergesort(int l,int r)
{
    if(l == r) return ;
    int mid = l + r >> 1;
    mergesort(l,mid); mergesort(mid + 1,r);
    int i = l,j = mid + 1,p = l;
    while(i <= mid && j <= r)
    {
        if(a[i] > a[j]) ans += mid - i + 1,tmp[p ++] = a[j ++];
        else tmp[p ++] = a[i ++];
    }
    while(i <= mid) tmp[p ++] = a[i ++];
    while(j <= r) tmp[p ++] = a[j ++];
    for(int i = l;i <= r;i ++)
        a[i] = tmp[i];
}
 
int main()
{
    while(~scanf("%d",&n) && n)
    {
        ans = 0;
        for(int i = 1;i <= n;i ++)
            scanf("%d",&a[i]);
        mergesort(1,n);
        //for(int i = 1;i <= n;i ++)    printf("%d ",a[i]);
        printf("%lld\n",ans);
    }
    return 0;
}

D : poj - 1151

给一些矩形,求面积并。

对于每个矩形,抽象成两个线段:一个线段代表添加,一个线段代表删除。

按x坐标排序,将y映射到数组上,遇到边就统计答案。

调不出来这个题的我居然写暴力过了……

代码实现的话:线段树每个点代表这个区间被覆盖多少次。每个点代表离散化区间的两个点,这样如果离散化后有n个数,建树只需要建n-1的长度即可。

update的时候,如果当前点是叶子节点那么覆盖了就是对应长度,没覆盖就是0。回溯时如果覆盖了就是对应长度,没覆盖就是两个儿子的答案之和。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;

typedef long long LL;
const int SZ = 50010;
const int INF = 1000000010;

int n,m;

double lsh[SZ];
int lshlen = 0;

struct segment
{
    int l,r,c;
    double sum,len;
}tree[SZ * 4];

void pushadd(int p,int d)
{
    tree[p].c += d;
}

void pushdown(int p)
{

}

void update(int p)
{
    if(tree[p].c) tree[p].sum = tree[p].len;
    else if(tree[p].l == tree[p].r) tree[p].sum = 0;
    else tree[p].sum = tree[p << 1].sum + tree[p << 1 | 1].sum;
}

void build(int p,int l,int r)
{
    tree[p].l = l; tree[p].r = r;
    tree[p].len = lsh[r + 1] - lsh[l];
    tree[p].sum = 0; tree[p].c = 0;
    if(l == r)
        return ;
    int mid = l + r >> 1;
    build(p << 1,l,mid); build(p << 1 | 1,mid + 1,r);
}

void change(int p,int l,int r,int d)
{
    if(l <= tree[p].l && tree[p].r <= r)
    {
        pushadd(p,d);
        update(p);
        return ;
    }
    int mid = tree[p].l + tree[p].r >> 1;
    if(l <= mid) change(p << 1,l,r,d);
    if(mid < r) change(p << 1 | 1,l,r,d);
    update(p);
}

struct haha
{
    double l,r,x,d;
}l[SZ];

bool cmp(haha a,haha b)
{
    return a.x < b.x;
}

int main()
{
    int cc = 0;
    while(~scanf("%d",&n) && n)
    {
        memset(tree,0,sizeof(tree)); memset(lsh,0,sizeof(lsh)); lshlen = 0;
        int tot = 0;
        for(int i = 1;i <= n;i ++)
        {
            double x1,y1,x2,y2;
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            lsh[++ lshlen] = y1,lsh[++ lshlen] = y2;
            l[++ tot] = (haha){y1,y2,x1,1};
            l[++ tot] = (haha){y1,y2,x2,-1};
        }

        sort(lsh + 1,lsh + 1 + lshlen);

        int len = unique(lsh + 1,lsh + 1 + lshlen) - lsh - 1;
        for(int i = 1;i <= tot;i ++)
        {
            l[i].l = lower_bound(lsh + 1,lsh + 1 + len,l[i].l) - lsh;
            l[i].r = lower_bound(lsh + 1,lsh + 1 + len,l[i].r) - lsh;
        }

        build(1,1,len - 1);
        sort(l + 1,l + 1 + tot,cmp);
    //    puts("");

        double ans = 0;
    //    for(int i = 1;i <= tot;i ++)
    //        printf("%.2f %.2f %.2f %d\n",l[i].l,l[i].r,l[i].x,l[i].c);
        for(int i = 1;i < tot;i ++)
        {
            change(1,l[i].l,l[i].r - 1,l[i].d);
            ans += tree[1].sum * (l[i + 1].x - l[i].x);
        }

        printf("Test case #%d\nTotal explored area: %.2f\n",++ cc,ans);
        puts("");
    }
    return 0;
}

/*
2
1 1 4 4
2 2 3 3

*/


E : poj - 2528

区间赋值,每次赋值不同,问最终序列有多少种不同的值。

区间赋值,最后扫一遍…………

BB一个不用线段树的做法:对于每个区间的出现,在l处记个时间戳和加入标志,在r+1处记个时间戳和删除标志。最后扫的时候用一个堆维护时间戳,删除用lazy标记,边扫边统计答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#define mem(a,t) memset(a,t,sizeof(a))
using namespace std;

typedef long long LL;
const int SZ = 100010;
const int INF = 1000000010;

int n,m;

struct segment
{
    int l,r,c,v;
}tree[SZ * 4];

void build(int p,int l,int r)
{
    tree[p].l = l; tree[p].r = r;
    if(l == r)
    {
        tree[p].c = tree[p].v = 0;
        return ;
    }
    int mid = l + r >> 1;
    build(p << 1,l,mid); build(p << 1 | 1,mid + 1,r);
}

void spread(int p)
{
    if(tree[p].c)
    {
        int d = tree[p].v;
        tree[p << 1].c = 1;
        tree[p << 1 | 1].c = 1;
        tree[p << 1].v = d;
        tree[p << 1 | 1].v = d;
        tree[p].c = 0;
    }
}

void change(int p,int l,int r,int d)
{
    if(l <= tree[p].l && tree[p].r <= r)
    {
        tree[p].c = 1; tree[p].v = d;
        return ;
    }
    spread(p);
    int mid = tree[p].l + tree[p].r >> 1;
    if(l <= mid) change(p << 1,l,r,d);
    if(mid < r) change(p << 1 | 1,l,r,d);
}

int ask(int p,int x)
{
    if(tree[p].l == tree[p].r && tree[p].l == x)
        return tree[p].v;
    spread(p);
    int mid = tree[p].l + tree[p].r >> 1;
    if(x <= mid) return ask(p << 1,x);
    if(mid < x) return ask(p << 1 | 1,x);
}

int ll[SZ],rr[SZ],lsh[SZ];
bool use[SZ];

int main()
{
    int T;
    scanf("%d",&T);
    while(T --)
    {
        mem(tree,0); mem(lsh,0); mem(use,0);
        scanf("%d",&n);
        for(int i = 1;i <= n;i ++)
        {
            scanf("%d%d",&ll[i],&rr[i]);
            lsh[++ lsh[0]] = ll[i];
            lsh[++ lsh[0]] = rr[i];
        }
        sort(lsh + 1,lsh + 1 + lsh[0]);
        //for(int i = 1;i <= lsh[0];i ++)
        //    cout << lsh[i] << " "; puts("");
        int len = unique(lsh + 1,lsh + 1 + lsh[0]) - lsh - 1;
    //    cout << len << endl;
        build(1,1,len);
        for(int i = 1;i <= n;i ++)
        {
            int l = lower_bound(lsh + 1,lsh + 1 + len,ll[i]) - lsh;
            int r = lower_bound(lsh + 1,lsh + 1 + len,rr[i]) - lsh;
            change(1,l,r,i);
        }
        int ans = 0;
        for(int i = 1;i <= len;i ++)
        {
            int x = ask(1,i);
            if(!use[x]) use[x] = 1,ans ++;
        }
        printf("%d\n",ans);
    }
    return 0;
}


F : poj - 2482

在平面内有一些点,点有权值,用w*h的窗口覆盖它们。问能覆盖到的点权和最大是多少。

又是扫描线……对于一个点,如果要覆盖它,它至少要出现在矩形的右下角或者左上角。由于对称性,我们只考虑它在左上角。这样对于点(x,y),如果矩形在${(X,Y) \mid x \le X\le x+w-1,y \le Y \le y+h-1}$的区域就能覆盖到他。

这样如果两个这种矩形重合,那么一定存在一个矩形可以覆盖这两个点。

这样就转化成扫描线问题:我们需要知道矩形面积并的权值和的最大值。

把每个点产生的矩形看做是这个点的点权的加入和删除,然后套用扫描线的模板就可以了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;

typedef long long LL;
const int SZ = 50010;
const int INF = 1000000010;

int n,m;

LL lsh[SZ];
int lshlen = 0;

struct segment
{
    int l,r;
    LL ans,add;
}tree[SZ * 4];

void pushadd(int p,LL d)
{
    tree[p].ans += d;
    tree[p].add += d;
}

void pushdown(int p)
{
    if(tree[p].add)
    {
        LL d = tree[p].add;
        tree[p << 1].ans += d;
        tree[p << 1 | 1].ans += d;
        tree[p << 1].add += d;
        tree[p << 1 | 1].add += d;
        tree[p].add = 0;
    }
}

void update(int p)
{
    tree[p].ans = max(tree[p << 1].ans,tree[p << 1 | 1].ans);
}

void build(int p,int l,int r)
{
    tree[p].l = l; tree[p].r = r;
    tree[p].ans = 0; tree[p].add = 0;
    if(l == r)
        return ;
    int mid = l + r >> 1;
    build(p << 1,l,mid); build(p << 1 | 1,mid + 1,r);
}

void change(int p,int l,int r,LL d)
{
    if(l <= tree[p].l && tree[p].r <= r)
    {
        pushadd(p,d);
        return ;
    }
    pushdown(p);
    int mid = tree[p].l + tree[p].r >> 1;
    if(l <= mid) change(p << 1,l,r,d);
    if(mid < r) change(p << 1 | 1,l,r,d);
    update(p);
}

struct haha
{
    LL l,r,x,d;
}l[SZ];

bool cmp(haha a,haha b)
{
    if(a.x != b.x) return a.x < b.x;
    return a.d > b.d;
}

int main()
{
    LL w,h;
    while(~scanf("%d%lld%lld",&n,&w,&h))
    {
        memset(tree,0,sizeof(tree)); memset(lsh,0,sizeof(lsh)); lshlen = 0;
        int tot = 0;
        for(int i = 1;i <= n;i ++)
        {
            LL x,y,val;
            scanf("%lld%lld%lld",&x,&y,&val);
            LL x1 = x,y1 = y,x2 = x + w - 1,y2 = y + h - 1;
            lsh[++ lshlen] = y,lsh[++ lshlen] = y2;
            l[++ tot] = (haha){y1,y2,x1,val};
            l[++ tot] = (haha){y1,y2,x2,-val};
        }

        sort(lsh + 1,lsh + 1 + lshlen);

        int len = unique(lsh + 1,lsh + 1 + lshlen) - lsh - 1;
        for(int i = 1;i <= tot;i ++)
        {
            l[i].l = lower_bound(lsh + 1,lsh + 1 + len,l[i].l) - lsh;
            l[i].r = lower_bound(lsh + 1,lsh + 1 + len,l[i].r) - lsh;
        }

        build(1,1,len);
        sort(l + 1,l + 1 + tot,cmp);
    //    puts("");
        LL ans = 0;
    //    for(int i = 1;i <= tot;i ++)
    //        printf("%.2f %.2f %.2f %d\n",l[i].l,l[i].r,l[i].x,l[i].c);
        for(int i = 1;i <= tot;i ++)
        {
        //    cout << l[i].l << " " << l[i].r << endl;
            change(1,l[i].l,l[i].r,l[i].d);
        //    cout << l[i].l << " " << l[i].r << " " << l[i].d << endl;
            ans = max(ans,tree[1].ans);
        }

        printf("%lld\n",ans);
    }
    return 0;
}

/*
2
1 1 4 4
2 2 3 3

*/


G : codeforces - 703D

给一个序列,多次询问一个区间中出现偶数次数字的异或和。

出现偶数次数字的异或和=区间异或和^区间不同数的异或和

前者搞个前缀和就行了,后者可以对每个数记一下下一次出现的位置,然后按左端点排序离线,左端点每扫过一个点就将其删除并在下一次出现的位置加入。

这样我们需要支持单点加入一个数、单点删除一个数、区间异或和。因为异或支持区间减法,所以可以树状数组做。

这种离线+树状数组的方法很常用。

这个题好像是去年刚退役时,a打CF问我,我口头BB的做法,居然今天写出来了……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#define mem(a,t) memset(a,t,sizeof(a))
using namespace std;

typedef long long LL;
const int SZ = 3000010;
const int INF = 1000000010;

int n,m;

int bit[SZ],a[SZ],sum[SZ];

int ask(int x)
{
    int ans = 0;
    for(int j = x;j > 0;j -= j & -j)
        ans ^= bit[j];
    return ans;
}

void change(int x,int d)
{
    for(int j = x;j <= n;j += j & -j)
        bit[j] ^= d;
}

map<int,int> mp;
int nxt[SZ],ans[SZ];

struct haha
{
    int l,r,id;
}l[SZ];

bool cmp(haha a,haha b)
{
    return a.l < b.l;
}

int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)
        scanf("%d",&a[i]),sum[i] = sum[i - 1] ^ a[i];
    scanf("%d",&m);
    for(int i = 1;i <= m;i ++)
        scanf("%d%d",&l[i].l,&l[i].r),l[i].id = i;

    for(int i = n;i >= 1;i --)
        nxt[i] = mp[a[i]],mp[a[i]] = i;

    mp.clear();
    for(int i = 1;i <= n;i ++)
        if(!mp[a[i]]) change(i,a[i]),mp[a[i]] = 1;


    sort(l + 1,l + 1 + m,cmp);
    for(int i = 1,j = 1;i <= m;i ++)
    {
        while(j < l[i].l)
        {
            change(j,-a[j]);
            if(nxt[j]) change(nxt[j],a[j]);
            j ++;
        }
        int ans1 = sum[l[i].r] ^ sum[l[i].l - 1];
        int ans2 = ask(l[i].r) ^ ask(l[i].l - 1);
        ans[l[i].id] =  ans1 ^ ans2;
    }
    for(int i = 1;i <= m;i ++)
        printf("%d\n",ans[i]);
    return 0;
}


H : hdu - 5107

给n个二维坐标,每个坐标有一个权值。m次询问一个二维前缀中的k小值是多少。k<=10,n,m<=3w

读完题感觉是二维树状数组套splay,然后感觉太难写,想了半天乱搞方法还是不会……然后发现k<=10,然后就想到一个超暴力的方法。这时候才发现n<=3w。2333如果我去写splay肯定到结束也看不见2333

对x排序,这时只有关键字y,和B题一样对y坐标建树状数组。由于是k小值,暴力的做是把比查询的y小的点的权值全部取出来排序,但我们只考虑到前十个,于是每次不需要全部取出。

考虑树状数组套堆:树状数组每个节点上有一个堆。每次只取出前十个,因为最多有log个,所以一共有10log个数,对他们进行排序即可。

网上的用线段树暴力合并,和这个做法是一样的

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define mem(a,t) memset(a,t,sizeof(a))
using namespace std;

typedef long long LL;
const int SZ = 500010;
const int INF = 1000000010;

int n,m;

struct haha
{
    int x,y,v;
}l[SZ];

bool cmp1(haha a,haha b) { return a.x < b.x; }

struct haha2
{
    int x,y,k,id;
}Ask[SZ];

bool cmp2(haha2 a,haha2 b) { return a.x < b.x; }


priority_queue<int> bit[SZ];

int len;

void add(int x,int d)
{
    for(int i = x;i <= len;i += i & -i)
        bit[i].push(-d);
}

vector<int> g,tmp;

int ask(int x,int k)
{
    g.clear();
    for(int i = x;i > 0;i -= i & -i)
    {
        tmp.clear();
        int t = 0;
        while(bit[i].size() && t < 10)
            g.push_back(-bit[i].top()),tmp.push_back(-bit[i].top()),bit[i].pop(),t ++;

        for(int j = 0;j < tmp.size();j ++)
            bit[i].push(-tmp[j]);
    }
    //for(int i = 0;i < g.size();i ++)
    //    printf("%d ",g[i]); puts("");
    if((int)g.size() < k) return -1;
    sort(g.begin(),g.end());
    return g[k - 1];
}


int ans[SZ],lsh[SZ];

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        mem(ans,0); mem(lsh,0);
        for(int i = 1;i <= len;i ++) while(bit[i].size()) bit[i].pop();

        for(int i = 1;i <= n;i ++)
        {
            scanf("%d%d%d",&l[i].x,&l[i].y,&l[i].v);
            lsh[++ lsh[0]] = l[i].x;
            lsh[++ lsh[0]] = l[i].y;
        }

        for(int i = 1;i <= m;i ++)
        {
            scanf("%d%d%d",&Ask[i].x,&Ask[i].y,&Ask[i].k),Ask[i].id = i;
            lsh[++ lsh[0]] = Ask[i].x;
            lsh[++ lsh[0]] = Ask[i].y;
        }

        sort(lsh + 1,lsh + 1 + lsh[0]);
        len = unique(lsh + 1,lsh + 1 + lsh[0]) - lsh - 1;
        for(int i = 1;i <= n;i ++)
        {
            l[i].x = lower_bound(lsh + 1,lsh + 1 + len,l[i].x) - lsh;
            l[i].y = lower_bound(lsh + 1,lsh + 1 + len,l[i].y) - lsh;
        }
        for(int i = 1;i <= m;i ++)
        {
            Ask[i].x = lower_bound(lsh + 1,lsh + 1 + len,Ask[i].x) - lsh;
            Ask[i].y = lower_bound(lsh + 1,lsh + 1 + len,Ask[i].y) - lsh;
        }

        sort(l + 1,l + 1 + n,cmp1);
        sort(Ask + 1,Ask + 1 + m,cmp2);

        /*for(int i = 1;i <= n;i ++)
            cout << l[i].x << " " << l[i].y << endl;
        for(int i = 1;i <= m;i ++)
            cout << Ask[i].x << " " << Ask[i].y << endl;*/

        for(int i = 1,j = 1;i <= m;i ++) //i:询问  j:star
        {
            while(j <= n && l[j].x <= Ask[i].x)
                add(l[j].y,l[j].v),j ++;
        //    cout << i << " " << j << endl;
            ans[Ask[i].id] = ask(Ask[i].y,Ask[i].k);
        }
        for(int i = 1;i <= m;i ++)
            printf("%d\n",ans[i]);
    }
    return 0;
}
/*
7 1
1 0 4
0 0 3
0 3 6
-4 6 2
1 6 4
-4 9 8
2 0 -4

1 2 1


3 1
1 0 4
0 0 3
0 3 6

1 2 1


*/

jrotty WeChat Pay

微信打赏

jrotty Alipay

支付宝打赏

文章二维码

扫描二维码,在手机上阅读!

算法线段树&&树状数组数据结构
最后由DQS修改于2017-08-10 00:47
发表新评论
博客已萌萌哒运行
© 2018 由 Typecho 强力驱动.Theme by Yodu
前篇 后篇
雷姆
拉姆