尺取法-反转问题-弹性碰撞-折半搜索

6 篇文章 0 订阅

尺取法

尺取法的取名来源一种名叫" 尺取 "的小虫,类似于小虫前进前后移动的过程。
它是一种常用的解题技巧,能够在遍历时优化遍历过程,减少不必要的循环,其通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况,根据区间的特征交替推进左右端点求解问题,避免了大量枚举。
一般来说,要求的是:
1.连续子序列
2.区间有一定的单调性

poj 例题

Bound-Found

题目大意:
给定N个数,从这N个数中找到连续子序列的和的绝对值, 最接近T 的(1<=n<=100000 0<=t<=1000000000).
数组值<=10000
输入:
每个测试用例 第一行是N ,K
第二行是N个数
第三行是K个T,每个T都要输出一个结果
N = 0,K = 0时退出
输出:
每个结果为 和的绝对值,和连续子序列的上下界

思路:
这道题类似于poj3061
只不过变成了和的绝对值最接近,而且可能出现非正数
由于我们尺取法的时候需要确保区间具有一定的单调性,这样左右移	不会有遗漏
而题目的数组不符合要求,我们需要作处理:
我们计算每个i的前缀和sum,然后将sum升序排列(保留编号)
由于abs(sum[i]-sum[j])=abs(sum[j]-sum[i]),可以忽视数列前缀和的前后关系。
又因为,区间和sum[r]-sum[l]有单调增加性(l不变,r右走)
我们修改尺取规则:
1.初始lb = 0,ub = 1
2.尝试更新结果,now_sum = sum[ub].first - sum[lb].first(now_sum >= 0),右移ub,不断找和	比当前区间大的区间,当和大于等于t时,停止(在往后肯定会越来越大)
3.左移lb,重复2
PS:注意用long long避免溢出

代码

#include<cstdio>
#include<algorithm>
#define MAX_N 100005
typedef long long ll;
using namespace std;
struct S{
     int sum,id;
};
bool cmp(const S& x,const S& y){
     return x.sum < y.sum;
}

ll my_abs(ll x){
     return x>=0 ? x : -x;
}

ll num[MAX_N];
S A[MAX_N];
int N,K;

void solve(ll t){
     ll lb = 0,ub = 1,res = 0x3f3f3f3f,now_sum = A[1].sum;
     ll x,y,z;
     while(ub <= N){
          now_sum = A[ub].sum - A[lb].sum;//下一个区间的和,因为A[ub].sum >= A[lb].sum,所以该值必非负
          if(my_abs(now_sum - t) < res){
               res = my_abs(now_sum - t);
               x = A[lb].id;
               y = A[ub].id;
               z = now_sum;
               if(res == 0)
                    break;//res=0就是最接近 省去没必要的循环
          }
          if(now_sum > t){
               lb++;
          }else ub++;
          if(lb == ub) ub++; //边界相等时,不计算,ub++ lb永远不可能大于ub
     }
     if(x > y){
          ll a = x;
          x = y;
          y = a;
     }
     printf("%lld %lld %lld\n",z,x+1,y);
}

int main(){
     while(scanf("%d%d",&N,&K) != EOF){
          if(N == 0 && K == 0) break;
          A[0].sum = 0;A[0].id = 0;
          for(int i = 0;i < N;i++){
               scanf("%lld",&num[i]);
               A[i+1].sum = A[i].sum + num[i];
               A[i+1].id = i+1;
          }
          sort(A,A+1+N,cmp); 
          //注意 A[0]一定要放进去排序,一开始没放进去,导致所有de从1到n的数列不会检测
          for(int j = 0;j < K;j++){
               ll t;
               scanf("%lld",&t);
               solve(t);
          }
     }
     return 0;
}

Graveyard-Design

将一个正整数,分解为连续正数平方之和,有多少种分法?
1 <= n <= 10^14
Sample Input
2030
Sample Output (要求输出行按L 降序排列,长度, 从小到大的连续正数)
2
4 21 22 23 24
3 25 26 27
//21* 21 + 22* 22 + 23* 23 + 24* 24 = 2030

思路:
思路:跟poj2739类似 连续单调 尺取法
只考虑正数从1开始
1 + 4 + 9 + ... 18 * 18 = 2109 > 2030 左移
4 + 9 + ... 18 * 18 = 2108 > 左移
...
7*7 + ... 18 * 18 = 2018 < 2030 右移
7*7 + ... 19 * 19 = 2379 > 2030 左移
...
21*21 + 22*22 + 23*23 + 24*24 = 2030 输出
...
当+46*46时(>2030)退出 长度为1的可能都没有
按L 降序排列 用尺取法无需考虑,本身结果获得的就是降序(因为后面的数只会越来越大)
#include<cstdio>
#include<vector>
using namespace std;
#define MAX_N 1e14
typedef long long ll;
struct result
{
    ll lb,ub;
};
vector<result> r;
ll n;

void solve(ll x)
{
    r.clear();
    ll lb = 1,ub = 1;
    ll sum = 0;
    while(lb <= ub && ub <= 1e7)
    {
        if(sum < x)
        {
            if(ub * ub > x)
                break;
            sum += ub * ub;
            ub++;
        }
        else if(sum >= x)
        {
            if(sum == x)
            {
                r.push_back(result{lb,ub}); //先保存
            }
            sum -= lb*lb;
            lb++;
        }
    }
    //再输出
    printf("%d\n",r.size());
    for(int i = 0; i < r.size(); ++i)
    {
        ll ub = r[i].ub,lb = r[i].lb;
        printf("%lld ",ub - lb);
        for(int i = 0; i < ub - lb; ++i)
        {
            printf("%lld%c",lb+i,i == ub - lb - 1 ? '\n' : ' ');
        }
    }
}

int main()
{
    while(scanf("%lld",&n) != EOF)
    {
        solve(n);
    }
    return 0;
}

Sum_of_Consecutive_Prime_Numbers

一些正整数可以由一个或多个连续质数之和表示。给定的正整数有多少个这种表示形式?
例如,整数53具有两个表示形式5 + 7 + 11 + 13 + 17和53。
整数41具有三个表示形式2 + 3 + 5 + 7 + 11 + 13、11 + 13 + 17和41。
3仅具有一个表示形式,即3。
整数20没有这种表示形式。请注意,求和数必须是连续的质数,因此7 + 13或3 + 5 + 5 + 7都不是整数20的有效表示。
您的任务是编写一个报告给定正整数的表示数量的程序

思路
提前求好所有质数,埃氏筛法,问题是连续子序列,区间单调
标准的尺取法,给定质数数组求和,满足x的所有连续子序列数量
举个例子41:
2 + 3 + 5 + 7 + 11 + 13 >= 41 等于更新,左移1
3 + 5 + 7 + 11 + 13 < 41 右移1
3 + 5 + 7 + 11 + 13 + 17 > 41 左移
5 + 7 + 11 + 13 + 17 > 41 继续左移···
11 + 13 + 17 >= 41 等于则更新,左移1
13 + 17 < 41 ...
37 + 41 > 41 左移1
41 >= 41  等于则更新,左移失败结束,一直到新质数不小于原值

代码

#include<cstdio>
#include<algorithm>
#define MAX_N 10005
using namespace std;
int prim[MAX_N];//保存质数
int t[MAX_N];//检测质数
int pnum = 0;

void getP() //埃氏筛法
{
    fill(t,t+MAX_N,1);//一开始都是质数
    t[1] = 0; //1不是质数
    for(int i = 2; i < MAX_N; ++i)
    {
        if(t[i] == 1)  //找到一个质数
        {
            prim[pnum++] = i;
            int a = i;
            while(a < MAX_N) //该质数的所有倍数不是质数
            {
                t[a] = 0;
                a += i;
            }
        }
    }
}

void printP() //打印质数
{
    for(int i = 0; i < pnum; ++i)
        printf("%d%c",prim[i],i % 10 == 9 ? '\n' : ' ');
    printf("\n");
}

void solve(int x)
{
    int way = 0,sum = 0,lb = 0,ub = 0;
    while(lb <= ub && ub < pnum)
    {
        if(sum >= x) //左移
        {
            if(sum == x)
                way++;
            sum -= prim[lb++];
        }
        else if(sum < x) //右移
        {
            if(prim[ub] > x)
               break;
            sum += prim[ub++];
        }
    }
    printf("%d\n",way);
}

int main()
{
    getP();
    //printP();
    int x;
    while(scanf("%d",&x) != EOF && x != 0)
    {
        solve(x);
    }
    return 0;
}

反转问题

反转问题据说通解是使用高斯消元法,这里讲枚举+贪心的思路。
反转问题是类似于开关灯一类的小游戏,问题一般为:
一开始灯有灭有亮,希望最后都亮; 点击一个灯,其自身包括WASD四个方向相邻一格的灯都会反转,求如何反转优解?
这种题一般会枚举+贪心尝试去做

poj例题:

The_Water_Bowls

给你一组碗,0为朝上,1为朝下
你希望让所有碗朝上为0,但是你每次翻一个朝下1的碗时,会把左右两边的碗反转,请问我们最小反转多少次,让最后的碗全部朝上?
输入
0 0 1 1 1 0 0 1 1 0 1 1 0 0 0 0 0 0 0 0
输出
3
Hint:
Flip bowls 4, 9, and 11 to make them all drinkable:
0 0 1 1 1 0 0 1 1 0 1 1 0 0 0 0 0 0 0 0 [initial state]
0 0 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 0 0 0 [after flipping bowl 4]
0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 [after flipping bowl 9]
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 [after flipping bowl 11]

思路:
简单的反转问题,为了让反转次数最小
我们从左往右看,当第一次遇到1时,反转自身+后两碗,
然后再往后看到有没有下一个1重复操作,直到最后一个
题目规定给定输入肯定有解,不考虑无解问题
反转以后数组的变化,对于这道题由于反转次数小,直接修改碗可行,
但是当反转碗比较多的时候,还是需要进行反转优化(不然反转操作每次需要K次)
优化一般为: 使用f[i]记录第i个碗是否被反转,计算f[i]前缀和来判断当前碗的状态

与poj 3276不同的是:我们可以反转小于3的碗(两端)
这导致0 0 1 0 0 ..... 0也会有解2,所以需要左扫一次右扫一次取两者最小值...
其实左扫右扫,就是看第一个为1格子解决方案的可能是从左往右翻的,也有可能是从右往左翻的;

代码:

#include<cstdio>
#include<string.h>
using namespace std;
int num[25];
int f[25];

void solve(){
     memset(f,0,sizeof(f));
     int res1 = 0,sum1 = 0;
     //左扫
     for(int i = 0; i < 20;++i){
          if((num[i] + sum1) & 1 != 0){ //当前碗是1,类比3276当作翻它自己以及后两个
               f[i] = 1; //当作第一个碗翻了
               res1++;
          }
          //sum[i] = f[i-2] + f[i-1]
          sum1 += f[i];
          if(i - 2 >= 0) sum1 -= f[i-2];
     }
     //右扫
     int res2 = 0,sum2 = 0;
     memset(f,0,sizeof(f));
     for(int i = 19; i >= 0;--i){
          if((num[i] + sum2) & 1 != 0){
               f[i] = 1;
               res2++;
          }
          //sum[i] = f[i+2] + f[i+1] 方向相反
          sum2 += f[i];
          if(i + 2 < 20) sum2 -= f[i+2];
     }
     printf("%d\n",res1 > res2 ? res2 : res1);
}

int main()
{
     for(int i = 0;i < 20;++i){
          scanf("%d",&num[i]);
     }
     solve();
}

EXTENDED_LIGHTS_OUT

每行5行,每行6个按钮的拼图
当按下一个按钮时,该按钮及其上方,下方,右侧和左侧的每个邻居(包括自己最多5个)的灯状态将反转;
从显示屏上任何初始亮起的灯开始,按下按钮使显示屏进入所有灯都熄灭的状态;
求上述按法为?

思路:
类似3279,枚举第一行的反转情况 000000 到 111111
然后每种情况考虑下一行,下一行根据上一行是否为1决定是否按,
最后检测最一行是否全为0,判断是否有解

代码:

#include<cstdio>
#include<string.h>
using namespace std;
const int dx[5] = {-1,0,0,0,1};
const int dy[5] = {0,-1,0,1,0};
int tile[5][6],flip[5][6];//棋,中间反转的尝试

int getColor(int x,int y)   //获取当前灯的亮熄
{
    int first = tile[x][y];//初始状态
    for(int i = 0; i < 5; ++i) //跟5个有关
    {
        int x2 = x + dx[i],y2 = y + dy[i];
        if((x2 >= 0 && x2 < 5 && y2 >= 0 && y2 < 6))
        {
             first += flip[x2][y2];
        }
    }
     return first % 2;
}

bool cal(){ //当前第一行确认情况下是否有解
     for(int i = 1;i < 5;++i){  //第二行开始
          for(int j = 0;j < 6;++j){
               if(getColor(i-1,j) != 0){ //看上一行
                    flip[i][j] = 1;
               }
          }
     }
     //判断最后一行
     for(int j = 0;j < 6;++j){
          if(getColor(4,j) != 0){
               return false;
          }
     }
     return true;
}

void printF(){ //打印反转矩阵
     for(int i = 0;i < 5;++i){
          for(int j = 0;j < 6;++j){
               printf("%d%c",flip[i][j],j == 5 ? '\n' : ' ');
          }
     }
}

void solve(int n){
     //枚举第一行的flip 2^6
     for(int i = 0;i < (1 << 6);++i){
          memset(flip,0,sizeof(flip));
          for(int j = 0;j < 6;++j){
               flip[0][5 - j] = (i >> j) & 1;
               //最后位运算,其实就是把i逐位写在第一行,低位在右
          }
          /*for(int j = 0;j < 6;++j){
               printf("%d ",flip[0][j]);
          }
           printf("\n");*/
          if(cal() ){
               printf("PUZZLE #%d\n",n);
               printF();
               break;
          }
     }
}

int main()
{
     int cases;
     scanf("%d",&cases);
     for(int i = 1;i <= cases;++i){
          for(int i = 0;i < 5;++i){
               for(int j = 0;j < 6;++j){
                    scanf("%d",&tile[i][j]);
               }
          }
          solve(i);
     }
     return 0;
}

弹性碰撞

弹性碰撞其实指的是相撞不损失能量一类的问题,
有两个球分别在一根绳子左右某个位置以相对方向相撞,求谁先落到绳子下
我们一般不计算相撞后的变化,而是认为它们互相穿过去了

poj例题

Linear_world

想象一维(线性)世界。在这样的世界上,只有两个可能的方向(左和右)。
这个世界上的所有居民都是在同一时间创造的,突然之间,他们所有人都开始朝一个方向或另一个方向移动(都以相同的恒定速度)。
如果两个居民相遇,他们会礼貌地交换问候,然后他们转身开始向相反的方向移动。当一个居民到达世界的尽头时,他掉下来消失了。
您的任务是,对于给定的世界场景,确定哪个居民以及何时(从创建时刻算起)是最后一个消失的人。您可以假设交换问候和转身所需的时间为0

思路:
求最后掉下的时间就可以直接求每个人走到各自方向的端点所需要的时间,然后求最大值即可;
最后掉下去的人是谁,那么最后掉下去的人肯定就是和A碰撞过的人且一直不停的碰撞的最后一个人;
可以按照初始位置对N个人进行排序,找出从A到A方向的端点之间和A方向相反的人的个数count,
可以画图得知,从A开始,沿着A的方向的第count个人,就是最后和A碰撞之后的人碰撞的那个人,其最终代替A掉下;

代码:

#include<cstdio>
#include<algorithm>
#define MAX_N 32005
using namespace std;
int N;
double L,V;
struct person{
     char d; //方向
     double init; //初始位置
     char name[255]; //名字
};
person peo[MAX_N];

double getTime(char d,double init){
     if(d == 'p' || d == 'P')
          return (L - init) / V;
     else if(d == 'n' || d == 'N')
          return init / V;
     else return -1.0;
}

bool cmp(const person& x,const person& y){
     return x.init < y.init;
}

int main(){
     while(scanf("%d",&N) != EOF && N != 0){
          scanf("%lf%lf",&L,&V);
          double time_ = 0;
          int res;
          for(int i = 0;i < N;++i){
               getchar();
               scanf("%c %lf %s",&peo[i].d,&peo[i].init,&peo[i].name);
               if(time_ < getTime(peo[i].d,peo[i].init)){ //记录最大值
                    time_ = getTime(peo[i].d,peo[i].init);
                    res = i;
               }
               //printf("%c %f %s\n",peo[i].d,peo[i].init,peo[i].name);
          }
          person now = peo[res];
          sort(peo,peo+N,cmp);
          int now_i = res,co = 0;

          for(int k = 0;k < N;++k){
               person p = peo[k];
               if(p.d == now.d && p.init == now.d){
                    now_i = k;
               }
               else if( (now.d == 'p' || now.d == 'P') && (p.d == 'N' || p.d == 'n') && p.init > now.init){
                    co++;
               }
               else if( (now.d == 'n' || now.d == 'N') && (p.d == 'p' || p.d == 'P') && p.init < now.init){
                    co++;
               }
          }
          //%m.nf,m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大于m,则按实际位数输出。n为保留n位小数
          if((now.d == 'p' || now.d == 'P')) //%.2f 会四舍五入
               printf("%13.2f %s\n",int(100*time_)/100.0,peo[now_i + co].name);
          else printf("%13.2f %s\n",int(100*time_)/100.0,peo[now_i - co].name);
     }
     return 0;
}

折半搜索

某些题,初次观察可能只能暴力枚举,但是单纯的暴力枚举很容易超时;
这个时候可以采用比较优雅的暴力枚举,将原数据拆成2半,分别对这两半求解;
这能够一定程度上减少部分时间复杂度;

poj例题:

Subset

给定一个绝对值不大于10^15的N个整数列表,请找到这些数字的一个非空子集,该子集元素之和的绝对值最小。如果存在多个子集,请选择一个元素数量较少的子集。

思路:
如果是连续子序列还可以考虑一下尺取法,但是这里是子集
对n个数,其非空子集个数为2^N-1个,暴力枚举所有子集的话,百亿量级复杂度铁TLE
可以折半枚举····
子集个数共有2^n个,所以不能全部枚举,但是可以分为两部分枚举;
枚举一半元素的所有情况2^(n/2)保存,然后后一半,二分查找前一半结果中最接近的即可;
我们把这个集合一拆为2,这样每个小集合的规模为17,枚举量大概是130000。
这样我们就得到了两个大小都为130000的答案集合,
按照最优的思路,对于第一个集合中的元素x,它没有必要和另一个集合中每一个元素尝试合并,
它要合并的那个数最好应该为-x,再不济也是和-x很近的那个数字。
由于第一个集合是静态的集合,所有我们完全可以二分逼近这个答案.

代码

#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
#define MAX_N 36
int N;
ll num[MAX_N];
map<ll,int> m;//区间和 长度
pair<ll,int> res;//最终结果

ll ll_abs(const ll& x)
{
    return x >= 0 ? x : -x;
}

void solve()
{
    m.clear();
    res = pair<ll,int>(ll_abs(num[0]),1);
    for(int i = 0; i < (1 << (N / 2)) ; ++i) //枚举前一半的区间和,存在map中
    {
        ll sum = 0;
        int length_ = 0;
        for(int j = 0; j < N / 2; ++j)
        {
            if( (i >> j) & 1)
            {
                sum += num[j];
                length_++;
            }
        }
        if(length_ == 0) continue;
        if(ll_abs(sum) < res.first || (ll_abs(sum) == res.first && res.second > length_)  ) //res存所有区间和的绝对值的最小值以及长度最小的
        {
            res.first = ll_abs(sum);
            res.second = length_;
        }
        map<ll,int>::iterator iter = m.find(sum);
        if(iter != m.end()) //存在当前和
        {
            if(iter->second > length_) //存在sum但是长度更长,更新为元素较少的子集
                iter->second = length_;
        }
        else m[sum] = length_;
    }

    //枚举后一半的区间和,不存了
    for(int i = 0; i < (1 << (N - N / 2)) ; ++i) //枚举后一半的区间和
    {
        ll sum = 0;
        int length_ = 0;
        for(int j = 0; j < (N - N / 2); ++j)
        {
            if((i >> j) & 1)
            {
                sum += num[N / 2 + j];
                length_++;
            }
        }
        if(length_ == 0) continue;
        if(ll_abs(sum) < res.first || (ll_abs(sum) == res.first && res.second > length_))
        {
            res.first = ll_abs(sum);
            res.second = length_;
        }
        map<ll, int>::iterator it = m.lower_bound(-sum); //返回一个迭代器,指向键值>= key的第一个元素。
        if (it != m.end())// 可能是该位置
        {
             if(ll_abs(sum + it->first) < res.first || (ll_abs(sum + it->first) == res.first && res.second > length_ + it->second))
             {
                 res.first = ll_abs(sum + it->first);
                 res.second = length_ + it->second;
             }
        }
        if (it != m.begin())// 或比该元素小一点点的
        {
            it--;
            if(ll_abs(sum + it->first) < res.first || (ll_abs(sum + it->first) == res.first && res.second > length_ + it->second))
             {
                 res.first = ll_abs(sum + it->first);
                 res.second = length_ + it->second;
             }
        }
    }
    printf("%lld %d\n",ll_abs(res.first),res.second);
}

int main()
{
    while(scanf("%d",&N) != EOF && N != 0)
    {
        for(int i = 0; i < N; ++i)
        {
            scanf("%lld",&num[i]);
        }
        solve();
    }
}


Sumsets

给定S(一组整数),找到最大的d,使得a + b + c = d,其中a,b,c和d是S的不同元素。

思路:
折半+二分
a + b = d - c
枚举左边结果保存并排序后;
对d-c枚举:
使用二分查找找到满足的结果,并且满足 a,b,c,d互不相等,保存d

代码:

#include<cstdio>
#include<algorithm>
#define MAX_N 1005
using namespace std;
int N;
int num[MAX_N];
struct Sum
{
    int s,a,b; //和,a + b 
    bool operator < (const Sum& x) const //为了满足lower_bound、sort···
    {
        return this->s < x.s;
    }
};
Sum sum[MAX_N * MAX_N];

int abs(int x)
{
    return x >= 0 ? x:-x;
}

bool cmp(const Sum& x,const Sum& y)
{
    return x.s < y.s;
}

void solve()
{
    int res = -1000000000;
    int index = 0;
    for(int i = 0; i < N; ++i) //枚举 a + b ,a b为不同元素
    {
        for(int j = i + 1; j < N; ++j)
        {
            sum[index].s = (num[i] + num[j]);
            sum[index].a = i;
            sum[index++].b = j;
        }
    }
    sort(sum,sum+index); //按和升序排列,方便二分
    for(int i = 0; i < N; ++i)
    {
        for(int j = 0; j < N; ++j) //枚举d - c
        {
            if(i == j) continue; //c d为不同元素
            int sum_dc = num[i] - num[j];//d - c
            int s1 = upper_bound(sum,sum+index,Sum{sum_dc,i,j}) - sum,
            s2 =  lower_bound(sum,sum+index,Sum{sum_dc,i,j}) - sum;//需要把我们查找的数封装成一个结构体才能进行bound查找,而且结构体要重载运算符<
            if(s1 - s2 > 0)  //有解
            {
                for(int k = 0; k < s1 - s2; ++k)
                {
                    if((sum[s2 + k].a != i) && (sum[s2 + k].a != j) && (sum[s2 + k].b != i) && (sum[s2 + k].b != j))
                    {
                        res = res > num[i] ? res : num[i];//取较大的d
                    }
                }
            }
        }
    }
    if(res == -1000000000)
        printf("no solution\n");
    else printf("%d\n",res);
}

int main()
{
    while(scanf("%d",&N) != EOF && N != 0)
    {
        for(int i = 0; i < N; ++i)
        {
            scanf("%d",&num[i]);
        }
        solve();
    }
    return 0;
}

-----纸上得来终觉浅,绝知此事要躬行-----

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值