NOIP常用算法模板

NOIP复赛快到了,于是我整理了一份算法模板,以防忘记。本人弱省OI蒟蒻,若有不正确的地方请指出

1.并查集算法

//并查集基本思想:将两个独立的集合合并到一坨(莫忘鸟要判断根节点是否相同)

#include <iostream> 

using namespace std;

int fa[10001]={0};

//找根节点

int findfa(int x) {

    if(x==fa[x]) return x;

    else {

       fa[x]=findfa(fa[x]);

       return fa[x];

    }

}

 

//判断根节点是否相同

bool judge(int x,int y) {

    if(findfa(x)==findfa(y)) return true;

    else return false;

}

 

//合并两个独立的集合(合并根节点)

void join(int x,int y) {

    int p1=findfa(x);

    int p2=findfa(y);

    if(p1!=p2) fa[p1]=p2;

}

 

int main() {

    int n,m,z,x,y;

    cin >> n >> m;

    fa[1]=1;

    for(int i=1;i<=n;i++) {

       fa[i]=i;//每个结点的根就是自己(独立的)

    }

    for(int i=1;i<=m;i++) {

       cin >> z >> x >> y;

       switch(z) {

           case 1:

              join(x,y);

              break;

           case 2:

              if(judge(x,y)) cout << "Y" << endl;

              else cout << "N" << endl;

              break;

           default:

              break;

       }

    }

    return 0;

}

 

2.二分查找算法

 

//二分查找思想:必须从一个单调序列中选取一个中位数为基准,要找的数比基准数小/大就向左/右找

#include <iostream>

 

using namespace std;

int array[50001];

 

//从小到大二分查找

int search(int n, int target) { 

    int low = 1, high = n, middle; 

    while(low <= high) {  //还冇找完

        middle = (low + high)/2;  //基准数

        if(target == array[middle]) {  //找到

            return middle; 

        } else if(target < array[middle]) {  //比此小向左找

            high = middle - 1; 

        } else if(target > array[middle]) {  //比此大向右找

            low = middle + 1; 

        } 

    } 

    return -1;  //一直找不到

}

 

//这是一个单调递增的序列

int main() {

    int n,p;

    cin >> n >> p;

    for(int i=1;i<=n;i++) cin >> array[i];

    cout << search(n,array[p]) << endl;

    return 0;

}

 

3.高精度四则运算

(1)加

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

string add(string a1,string b1) {

    string res;

    int a[10001]={0},b[10001]={0},c[10001]={0};

    int k=1;

    int plus=0;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    int lena=a1.length(),lenb=b1.length();

    for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';

    for(int i=0;i<lenb;i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       c[k]=a[k]+b[k]+plus;

       plus=c[k]/10;

       c[k]%=10;

       k++;

    }

    c[k]=plus;

    for(int i=k;i>=1;i--) res+=(char)(c[i]+'0');

    while(res[0]=='0') res.erase(res.begin(),res.begin()+1);

    if(res.empty()) return "0";

    else return res;

}

int main() {

    string a,b;

    cin >> a >> b;

    cout << add(a,b) << endl;

    return 0;

}

 

(2)减

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

inline void strswap(string &a,string &b) {

    string t;

    t=a;

    a=b;

    b=t;

}

 

inline bool alessb(string a,string b) {

    if(a.length()<b.length()) return true;

    else if(a.length()==b.length()) {

       if(a<b) return true;

       else return false;

    } else return false;

}

 

inline string minus1(string a1,string b1) {

    bool negative=false;

    string str;

    if(alessb(a1,b1)) {

       strswap(a1,b1);

       negative=true;

    }

    int a[20001],b[20001],c[20001];

    int lena=a1.length(),lenb=b1.length();

    int k=1;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    for(int i=0; i<lena; i++) a[i+1]=a1[i]-'0';

    for(int i=0; i<lenb; i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       if(a[k]-b[k]<0) {

           a[k+1]--;

           a[k]+=10;

       }

       c[k]=a[k]-b[k];

       k++;

    }

    for(int i=k-1; i>=1; i--) str+=(char)(c[i]+'0');

    while(str[0]=='0') str.erase(str.begin(),str.begin()+1);

    if(str.empty()) return "0";

    else {

       if(negative) return "-"+str;

       else return str;

    }

}

 

int main() {

    string a,b;

    cin >> a >> b;

    cout << minus1(a,b) << endl;

    return 0;

}

 

(3)

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

string mul(string a1,string a2) {

    int a[10001]={0},b[10001]={0},c[10001]={0};

    string result;

    int plus=0;

    reverse(a1.begin(),a1.end());

    reverse(a2.begin(),a2.end());

    int lena=a1.length(),lenb=a2.length();

    for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';

    for(int i=0;i<lenb;i++) b[i+1]=a2[i]-'0';

    for(int i=1;i<=lena;i++) {

       plus=0;

       for(int j=1;j<=lenb;j++) {

           c[i+j-1]+=(a[i]*b[j]+plus);

           plus=c[i+j-1]/10;

           c[i+j-1]%=10;

       }

       c[i+lenb]=plus;//注意每次错位相乘乘完后要进位

    }

    for(int i=lena+lenb;i>=1;i--) result+=((char)(c[i]+'0'));

    while(result[0]=='0') result.erase(result.begin(),result.begin()+1);

    if(result.empty()) return "0";

    else return result;

}

 

int main() {

    string a,b;

    cin >> a >> b;

    cout << mul(a,b) << endl;

    return 0;

}

 

(4)高精除低精

//高精度除以低精度

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

typedef long long LL;

struct Info {

    string result;

    LL rest;

};

 

inline Info divide(string a1,LL d) {

    LL a[20001],b[20001];

    string res="";

    LL rest=0;

    int len=a1.length();

    for(int i=0;i<len;i++) a[i+1]=a1[i]-'0';

    for(int i=1;i<=len;i++) {

       rest=rest*10+a[i];

       b[i]=rest/d;

       rest=rest%d;

    }

    for(int i=1;i<=len;i++) res+=(char)(b[i]+'0');

    while(res[0]=='0') res.erase(res.begin(),res.begin()+1);

    return {res,rest};

}

 

int main() {

    string a;

    LL b;

    cin >> a >> b;

    Info p = divide(a,b);

    cout << p.result << "......" << p.rest;

    return 0;

}

(5)高精除高精

//高精度除以高精度

#include <iostream>

#include <algorithm>

#include <string>

#pragma \

GCC optimize("O3")

 

using namespace std;

 

inline void strswap(string &a,string &b) {

    string t;

    t=a;

    a=b;

    b=t;

}

 

inline bool alessb(string a,string b) {

    if(a.length()<b.length()) return true;

    else if(a.length()==b.length()) {

       if(a<b) return true;

       else return false;

    } else return false;

}

 

inline string minus1(string a1,string b1) {

    bool negative=false;

    string str;

    if(alessb(a1,b1)) {

       strswap(a1,b1);

       negative=true;

    }

    int a[20001],b[20001],c[20001];

    int lena=a1.length(),lenb=b1.length();

    int k=1;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    for(int i=0; i<lena; i++) a[i+1]=a1[i]-'0';

    for(int i=0; i<lenb; i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       if(a[k]-b[k]<0) {

           a[k+1]--;

           a[k]+=10;

       }

       c[k]=a[k]-b[k];

       k++;

    }

    for(int i=k-1; i>=1; i--) str+=(char)(c[i]+'0');

    while(str[0]=='0') str.erase(str.begin(),str.begin()+1);//去除前导零

    if(str.empty() || str=="0") return "0";

    else {

       if(negative) return "-"+str;

       else return str;

    }

}

 

inline string add(string a1,string b1) {

    string res;

    int a[10001]={0},b[10001]={0},c[10001]={0};

    int k=1;

    int plus=0;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    int lena=a1.length(),lenb=b1.length();

    for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';

    for(int i=0;i<lenb;i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       c[k]=a[k]+b[k]+plus;

       plus=c[k]/10;

       c[k]%=10;

       k++;

    }

    c[k]=plus;

    for(int i=k;i>=1;i--) res+=(char)(c[i]+'0');

    while(res[0]=='0') res.erase(res.begin(),res.begin()+1);

    if(res.empty()) return "0";

    else return res;

}

 

inline string divide(string s1,string s2) {

    string cnt;

    cnt=add("0","0");

    while(true) {

       s1=minus1(s1,s2);

       if(s1[0]!='-') {

           cnt=add(cnt,"1");

           continue;

       } else break;

    }

    return cnt;

}

 

int main() {

    string s1,s2;

    cin >> s1 >> s2;

    cout << divide(s1,s2) << endl;

    return 0;

}

 

4.递推算法

 

//动态规划 递推 maxn[i][j]=a[i][j]+max(maxn[i+1][j],maxn[i+1][j+1])

#include <iostream>

#include <cstring>

#include <algorithm>

 

using namespace std;

int a[1001][1001];

int maxn[1001][1001];

int n;

int main() {

    cin >> n;

    for(int i=1;i<=n;i++)

       for(int j=1;j<=i;j++)

           cin >> a[i][j];

    memset(maxn,0,sizeof(maxn));

    //最下面一行的最大值来自于自己

    for(int i=1;i<=n;i++) maxn[n][i]=a[n][i];

    //动态规划递推

    for(int i=n-1;i>=1;i--) {

       for(int j=1;j<=i;j++) {

           maxn[i][j]=a[i][j]+max(maxn[i+1][j],maxn[i+1][j+1]);

       }

    }

    cout << maxn[1][1] << endl;

    return 0;

}

 

5.快速幂算法

 

//快速幂思路:将大于等于2的指数n分成两个n/2次方进行递归运算

#include <iostream>

 

using namespace std;

 

//求a的b次方快速幂

long long quickpow(int a,int b) {

    //递归边界莫忘鸟

    if(b==0) return 1;

    else if(b==1) return a;

    else {

       if(b%2==0) return quickpow(a,b>>1)*quickpow(a,b>>1);

       else return quickpow(a,b>>1)*quickpow(a,b>>1)*a;//注意不是偶数的指数需要再乘1次

    }

}

 

int main() {

    int a,b;

    cin >> a >> b;

    cout << quickpow(a,b) << endl;

    return 0;

}

 

6.动态规划01背包问题

//基本01背包:枚举背包容量再选取最优值

#include <iostream>

#include <algorithm>

 

using namespace std;

 

int main() {

    int f[101][1001];//f[i][j]表示采药i花费时间j的最大价值

    int t,m,time[101],value[101];

    cin >> t >> m;

    for(int i=1;i<=m;i++) {

       cin >> time[i] >> value[i];

    }

    for(int j=1;j<=t;j++) f[0][j]=0;//初始化蛮关键(第0号药无论有多少时间花费都不能采)

    for(int i=1;i<=m;i++) {

       for(int j=t;j>=1;j--) {//枚举背包容量大小(采药时间限制)

           if(j>=time[i]) {//没超过时间

              f[i][j]=max(f[i-1][j],

                  f[i-1][j-time[i]]+value[i]

              ); //采、不采之间选一个最大价值

           } else {//背包容量不够(超时)

              f[i][j]=f[i-1][j];//不采

           }

       }

    }

    cout << f[m][t] << endl;//输出采药m花费时间t所得到的最大总价值

    return 0;

}

 

7.最长上升子序列

 

//这其实就是个线性动规:dp[i]表示以a[i]结尾的最长上升/下降子序列的长度

 

#include <iostream>

#include <cstring>

#include <algorithm>

 

using namespace std;

//先求最长上升子序列,再求最长下降子序列

int main() {

    int height[101], n;

    int dp1[1001], dp2[1001];//dp1->最长上升子序列,dp2->最长下降子序列

    cin >> n;

    for (int i = 1; i <= n; i++) cin >> height[i];

    for(int i=1;i<=n;i++)

       dp1[i]=dp2[i]=1;

    for (int i = 1; i <= n; i++) {

       for (int j = 1; j < i; j++) {

           if (height[i] > height[j]) dp1[i] = max(dp1[i],dp1[j]+1);

       }

    }

    //倒着求最长下降子序列,其实就是求最长上升子序列

    for (int i = n; i >= 1; i--) {

       for (int j = n; j > i; j--) {

           if (height[i] > height[j]) dp2[i] = max(dp2[i], dp2[j] + 1);

       }

    }

    int maxlen = 1;//最多留下的人

    for (int i = 1; i <= n; i++) {

       //减1是因为中间有个同学重复算了一次

       maxlen = max(maxlen, dp1[i] + dp2[i] - 1);

    }

    cout << n - maxlen << endl;

    return 0;

}

 

8.区间动规

 

/*基本思路:设前i到j的最优值,枚举剖分(合并)点,将(i,j)分成左右两区间,分别求左右两边最优值。状态转移方程的一般形式:F(i,j)=Max{F(i,k)+F(k+1,j)+决策,k为划分点*/

 

/*

对于这一题需要化环为链

我们可以用化环为链的方法,具体的实现就是将这个环的单圈复制一遍.

 

举个例子,输入1、2、3、4、5;那么我们就复制成1、2、3、4、5、1、2、3、4、5;

 

当我们用DP算完后,我们从起点起,依次向后延伸环长度,你看是不是把环的每一种情况都列举到了。

 

然后其实就是一个简单的DP了。

 

比如说我们要求合并石子i--j的最佳方案,我们可以把 i----j 分为 i--k 与 k+1--j两段;

 

枚举k的取值在分别取最大最小值就行了。

 

PS:DP状态转移式:f[i][j]=max/min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) (最大值最小值都适用)

 

注:+s[j]-s[i-1]是要把i--k与k+1--j合并时的得分

*/

#include <iostream>

#include <cstring>

 

#define INF 99999999

 

using namespace std;

 

int Max[201][201],Min[201][201];//Max/Min[i][j]表示从第i堆石头合并到第j堆最大/小的得分

int a[201]={0};//a[i]表示前i个石头数量和

 

int main() {

    int n;

    //预处理数据

    cin >> n;

    for(int i=1;i<=n;i++) {

       cin >> a[i];

       a[i+n]=a[i];

    }

    //化环为链

    for(int i=2;i<=2*n;i++) a[i]+=a[i-1];

    for(int i=1;i<=2*n;i++)

       for(int j=i+1;j<=2*n;j++)

           Min[i][j]=INF;

    memset(Max,0,sizeof(Max));

    for(int i=2*n-1;i>=1;i--) {

       for(int j=i+1;j<=2*n;j++) {//左右区间合并

           for(int k=i;k<=j-1;k++) {

              //Max(i,j)=max{Max(i,j),Max(i,k)+Max(k+1,j)+t[i][j]}

              Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+a[j]-a[i-1]);

              Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+a[j]-a[i-1]);

           }

       }

    }

    int maxn=0,minn=INF;

    //还要从环上每个顶点统计一遍

    for(int i=1;i<=n;i++) {

       maxn=max(maxn,Max[i][i+n-1]);

       minn=min(minn,Min[i][i+n-1]);

    }

    cout << minn << endl << maxn << endl;

    return 0;

}

 

9.坐标规则型动规

 

//基本思路:那在一个矩阵中给出一些规则,然后按规则去做某些决策

//DP关系式: F(i,j)=Max{f(i-1,k)}+决策

#include <iostream>

#include <algorithm>

 

using namespace std;

 

int bx,by,mx,my;

long long f[31][31]={0};//f[i][j]表示(0,0)到(i,j)的路径条数

long md[9][2]={ {0,0},{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};

bool cant[31][31]={false};

 

int main() {

    cin >> bx >> by >> mx >> my;

    for(int k=0;k<9;k++) {

       int nx=mx+md[k][0];

       int ny=my+md[k][1];

       if(nx>=0&&nx<=bx&&ny>=0&&ny<=by) cant[nx][ny]=true;

    }

    //起点冇被马包围

    if(!cant[0][0]) {

       f[0][0]=1;

       for(int i=0;i<=bx;i++) {

           for(int j=0;j<=by;j++) {

              if(!cant[i][j]) {

                  if(i!=0 && !cant[i-1][j]) f[i][j]+=f[i-1][j];

                  if(j!=0 && !cant[i][j-1]) f[i][j]+=f[i][j-1];

              }

           }

       }

       cout << f[bx][by] << endl;

    } else cout << 0 << endl;//否则冇得办法

    return 0;

}

 

 

 

10.线段树

//使用线段树查询一个区间的最值或和差

#include <iostream>

#include <algorithm>

 

using namespace std;

 

int segtree[400001];

int array[100001];

int m,n;

 

//构造区间最小数的线段树

//node:结点编号,start:左区间,end:右区间

inline void build(int node,int start,int end) {

    if(start==end) segtree[node]=array[start];//到了根节点填值

    else {

       int mid=(start+end)/2;

       build(node*2,start,mid);//构造左子树

       build(node*2+1,mid+1,end);//构造右子树

       segtree[node]=min(segtree[node*2],segtree[node*2+1]);//从左右子树回溯填入最小值千万莫忘鸟!

    }

}

 

//查询线段树

//node->节点编号,[begin,end]当前结点区间,[l,r]查询区间

inline int query(int node, int begin, int end, int left, int right) {

    int p1, p2;

    if (left > end || right < begin) return -1;//查询区间和要求的区间没有交集

    if (begin >= left && end <= right) return segtree[node];//当前节点区间包含在查询区间内

    /* 分别从左右子树查询,返回两者查询结果的较小值 */

    p1 = query(2 * node, begin, (begin + end) / 2, left, right);

    p2 = query(2 * node + 1, (begin + end) / 2 + 1, end, left, right);

    /*  返回需要的值  */

    if (p1 == -1) return p2;

    if (p2 == -1) return p1;

    return min(p1,p2);

}

 

int main() {

    cin >> m >> n;

    for(int i=1;i<=m;i++) cin >> array[i];

    build(1,1,m);

    for(int i=1;i<=n;i++) {

       int a,b;

       cin >> a >> b;

       cout << query(1,1,m,a,b) << " ";

    }

    return 0;

}

 

 

11.邻接表

//使用vector动态数组存储边的信息

#include <iostream>

#include <vector>

 

using namespace std;

 

const int MAX = 10000;

 

struct EdgeNode {

    int to;//指向的结点编号

    int w;//这两点间的权值

};

 

vector<EdgeNode> v[MAX];

 

int main() {

    EdgeNode e;

    int n,m,w;//n个顶点m条边 ,w权值

    int a,b;//a->b的顶点边

    ios::sync_with_stdio(false);

    cin >> n >> m;

    for(int i=0;i<m;i++) {

       cin >> a >> b >> w;

       v[a].push_back({b,w});

    }

    //遍历

    for(int i=1;i<=n;i++) {

       vector<EdgeNode>::iterator it;

       for(it=v[i].begin();it!=v[i].end();it++) {

           EdgeNode r = *it;

           cout << "Edge " << i << " to " << r.to << " is " << r.w << endl;

       }

    }

    return 0;

}

 

12.Sparse Table算法

//用于求区间最值

#include <cstdio>

#include <algorithm>

#include <cmath>

 

using namespace std;

 

int n,m;

int dp[200001][30];//dp[i][j]=[i,i+2^j-1]min

 

int query(int l,int r) {

    int k=log(r-l+1)/log(2);//区间长度为r-l+1的对数

    return max(dp[l][k],dp[r-(1<<k)+1][k]);//合并区间最小值

}

 

int main() {

    scanf("%d%d",&n,&m);

    for(int i=1;i<=n;i++) {

       scanf("%d",&dp[i][0]);

    }

    for(int i=1;i<=log(n)/log(2);i++) {//枚举log2(n)的数

       for(int j=1;j<=n-(1<<i)+1;j++) {//枚举1~n-(2^i-1)的数

           dp[j][i]=max(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);

       }

    }

    for(int i=1;i<=m;i++) {

       int l,r;

       scanf("%d%d",&l,&r);

       printf("%d\n",query(l,r));

    }

    return 0;

}

 

13.SPFA(Bellman-Ford队列优化)

//注意要用邻接表实现,每次取未经过的点放入队首松弛以求最短路径

#include <iostream>

#include <fstream>

#include <algorithm>

#include <queue>

#include <vector>

 

#define FSTREAM 1

 

using namespace std;

 

struct Edge{

    int u,v,w;

};

const int inf = 1<<30;

int n,m;

queue<int> q;

vector<Edge> e;

int dis[10001];//dis[i]->1~i的权值

bool book[10001];//i号顶点是否在队列中

int first[10001],nxt[10001];//邻接表

 

int main() {

#if FSTREAM

    ifstream fin("spfa.in");

    ofstream fout("spfa.out");

    fin >> n >> m;

#else

    cin >> n >> m;

#endif

    fill(dis,dis+n+1,inf);

    dis[1]=0;

    fill(book,book+n+1,false);

    fill(first,first+n+1,-1);

    for(int i=0;i<m;i++) {

       int p1,p2,p3;

#if FSTREAM

       fin >> p1 >> p2 >> p3;

#else

       cin >> p1 >> p2 >> p3;

#endif

       e.push_back({p1,p2,p3});

       nxt[i]=first[p1];

       first[p1]=i;

    }

    //1号顶点入队

    q.push(1);

    book[1]=true;

    int k;//当前需要处理的队首顶点

    while(!q.empty()) {

       k=first[q.front()];

       while(k!=-1) {//搜索当前顶点所有边

           if(dis[e[k].v]>dis[e[k].u]+e[k].w) {

              dis[e[k].v]=dis[e[k].u]+e[k].w;//松弛

              if(!book[e[k].v]) {//不在队列中,加入队列

                  book[e[k].v]=true;

                  q.push(e[k].v);

              }

           }

           k=nxt[k];//继续下一个

       }

       book[q.front()]=false;

       q.pop();

    }

#if FSTREAM

    for(int i=1;i<=n;i++) {

       if(dis[i]!=inf) fout << dis[i] << " ";

       else fout << "INF ";

    }

    fin.close();

    fout.close();

#else

    for(int i=1;i<=n;i++) {

       if(dis[i]!=inf) cout << dis[i] << " ";

       else cout << "INF ";

    }

#endif

    return 0;

}

 

14.埃氏筛素数

#include <iostream>

#include <algorithm>

#include <cmath>

 

using namespace std;

 

const int MAX = 100001;

bool prime[MAX]={false};

 

inline void make_prime() {

    fill(prime,prime+MAX,true);

    prime[0]=prime[1]=false;

    int t=(int)sqrt(MAX*1.0);

    for(register int i=2;i<=t;i++) {

       if(prime[i]) {

           for(register int j=2*i;j<MAX;j+=i) {

              prime[j]=false;

           }

       }

    }

}

 

int main() {

    make_prime();

    for(register int i=0;i<=MAX;i++) if(prime[i]) cout << i << "\t";

    return 0;

}

 

15.欧拉筛素数

#include <iostream>

#include <vector>

#include <algorithm>

 

using namespace std;

const int MAX = 100001;

bool prime[MAX];

vector<int> v;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值