状态dp小结 (hdu4804 hdu4856 hdu3681 poj3311 hdu3001 cf453B hdu4906 zoj3802)

会的人说是水题,不会的是神题,这就是状态dp的魅力。

hdu4804

轮廓线dp水题,所谓轮廓线dp,核心思想就是,以轮廓线作为状态,然后进行dp,不是按行,不是按列,是轮廓线,必须把握这一精髓。

对于这个题目,只需记录当前轮廓线上哪些点已经被覆盖,哪些点没有被覆盖,以及这种轮廓线状态下的结果。然后,不断地由当前轮廓线递推出将当前轮廓右下角纳入轮廓线之后的状态。而由当前轮廓线状态推理下一轮廓线状态时,实际上状态中所有点仅当前处理位置上部的节点和左部的节点对下一轮廓线状态有关,其他均可直接从上以轮廓线状态推得,不清楚的可以在纸上划一下。然后就是分情况讨论了。详细看代码。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

int n,m,c,d;
char map[110][11];
long long int dp[2][25][1<<10+1];   //第一维状态压缩,记录处理到第几个轮廓线,第二维记录用多少个1*1,第三维记录状态,值为当前状态结果
const int MOD=1000000000+7;
int now;

void DP()
{
    memset(dp,0,sizeof(dp));
    dp[0][0][(1<<m)-1]=1;
    now=0;
    int maxs=(1<<m);
    int pre=1;
    for (int i=0;i<n;i++)
    {
        for (int t=0;t<m;t++)
        {
            swap(now,pre);
            memset(dp[now],0,sizeof(dp[now]));  //每次将新的轮廓线初始化,然后由上一轮廓线推结果
            for (int k=0;k<=d;k++)
            {
                int x=1<<(t-1);     //当前处理位置左部
                int y=1<<t;     //当前处理位置上部
                for (int j=0;j<maxs;j++)    //状态是轮廓线的状态,凡是轮廓线上方且不与轮廓线相接的都默认已经铺好
                {
                    if (map[i][t]=='1') //能铺
                    {
                        if (k>0&&(j&y))     //放1*1,上面必须为1,且至少占用一个1*1的方块,左边与轮廓线相接,空或不空均可
                            dp[now][k][j]=(dp[now][k][j]+dp[pre][k-1][j])%MOD;
                        if ( t>0 && !(j&x) && (j&y) )  //横放1*2,左边必须为空且不是第一格,上面必须为1,否则此点铺完后上面不与轮廓线相接,空出一个
                            dp[now][k][j|x]=(dp[now][k][j|x]+dp[pre][k][j])%MOD;
                        if (!(j&y))         //竖放2*1,上面必须为零,左边与轮廓线相接,无所谓
                            dp[now][k][j|y]=(dp[now][k][j|y]+dp[pre][k][j])%MOD;
                        if (j&y)            //先空着,上面必须为1,左边与轮廓线相接,无所谓
                            dp[now][k][j-y]=(dp[now][k][j-y]+dp[pre][k][j])%MOD;
                    }
                    else
                    {
                        if (j&y)    //上面为1,左边无所谓,当前格假设用1*1铺好但不占用1*1的数量
                            dp[now][k][j]=(dp[now][k][j]+dp[pre][k][j])%MOD;
                    }
                }
            }
        }
    }
}

int main()
{
    while (cin>>n>>m>>c>>d)
    {
        for (int i=0;i<n;i++)
            scanf("%s",map[i]);
        DP();
        long long int sum=0;
        for (int i=c;i<=d;i++)
            sum=(sum+dp[now][i][(1<<m)-1])%MOD;
        cout<<sum<<endl;
    }
}

hdu4856

题目给定一些管道和管道的起点和终点,可以从任意管道出发,问遍历所有管道一次仅且一次至少需要走多少步。

很容易想到,首先要求出管道两两之间的最短距离,然后得出最少消耗。无论BFS还是什么的构造出最短距离的新的图,然后就是状态压缩dp了。一开始想的搜索,超时了一下午,后来用的状态压缩dp。设dp[i][j]表示当前状态最后一个走的是i,状态为j的情况,用一个长度为n的二进制数j表示n条管道是否走过,然后将所有只走一条管道的状态标记为0,其余标记为无穷大,表示无法到达。然后将状态按从小到大的顺序进行dp,至于为什么按这个顺序,是因为dp的时候当前状态只与当前状态没有走i的时候有关,当前状态没有走i表示的数肯定小于当前状态表示的数,这样才能形成递推关系,且保证了每个当前状态最优(所有能推出当前状态的结果都被检测了一遍去的最小值)。详细看代码。

代码:

#include <iostream>
#include <cstring>
#include <queue>
#include <cstdio>

using namespace std;

int map[20][20];
int dis[20][20];    //最短路
int d[20][20];      //i的终点到j的起点的距离
int sx[20];
int sy[20];
int ex[20];
int ey[20];
int gx[4]={1,-1,0,0};
int gy[4]={0,0,-1,1};
int n,m;
int ans;
int dp[20][1<<15+1];    //当前状态最后走i,状态为j的情况数
int INF=999999;

int shortpath(int x1,int y1,int x2,int y2)
{
    queue <int> mx;
    queue <int> my;
    memset(dis,-1,sizeof(dis));
    dis[x1][y1]=0;
    mx.push(x1);
    my.push(y1);
    int curx,cury,curs;
    while (!mx.empty())
    {
        curx=mx.front();
        cury=my.front();
        mx.pop();
        my.pop();
        for (int i=0;i<=3;i++)
        {
            if (map[curx+gx[i]][cury+gy[i]])
            {
                int nx=curx+gx[i];
                int ny=cury+gy[i];
                if (nx<1||nx>n||ny<1||ny>n)
                    continue ;
                if (dis[nx][ny]==-1||dis[curx][cury]+1<dis[nx][ny])
                {
                    dis[nx][ny]=dis[curx][cury]+1;
                    if (dis[x2][y2]!=-1&&dis[curx][cury]+1>=dis[x2][y2])
                        continue ;
                    mx.push(nx);
                    my.push(ny);
                }
            }
        }
    }
    return dis[x2][y2];
}

int min(int a,int b)
{
    return a>b?b:a;
}

void DP()
{
    int mm=1<<m;
    for (int i=0;i<m;i++)
        for (int t=0;t<mm;t++)
            dp[i][t]=INF;
    for (int i=0;i<m;i++)
        dp[i][1<<i]=0;
    for (int i=1;i<mm;i++)
    {
        for (int t=0;t<m;t++)
        {
            if (i&(1<<t))   //当前状态已经走过t
                continue ;
            for (int j=0;j<m;j++)   //无论是否走过j,如果没走过,则dp[j][i]肯定为无穷大(被上面contin掉了),走过则直接更新
                dp[t][i|(1<<t)]=min(dp[t][i|(1<<t)],dp[j][i]+d[j][t]);      //用j来更新当前状态,看能否更优
        }
    }
}

int main()
{
    while (cin>>n>>m)
    {
        for (int i=1;i<=n;i++)
        {
            for (int t=1;t<=n;t++)
            {
                char a;
                cin>>a;
                if (a=='.')
                    map[i][t]=1;
                else
                    map[i][t]=0;
            }
        }
        for (int i=1;i<=m;i++)
            scanf("%d%d%d%d",&sx[i],&sy[i],&ex[i],&ey[i]);
        for (int i=1;i<=m;i++)
        {
            for (int t=1;t<=m;t++)
            {
                d[i-1][t-1]=shortpath(ex[i],ey[i],sx[t],sy[t]);
                if (d[i-1][t-1]==-1)
                    d[i-1][t-1]=INF;
            }
        }
        ans=INF;
        DP();
        for (int i=0;i<m;i++)
            ans=min(dp[i][(1<<m)-1],ans);
        if (ans>=INF)
            ans=-1;
        cout<<ans<<endl;
    }
}

hdu3681

花了大半天时间才改对,代码能力真是渣渣。

这题目思路也是常规思路,求最短路,重新构图,然后状态dp跑一遍即可。

对于能量池,如果用的话,就是当前节点经过能量池到达另一节点,不用的话,就是两个节点直接到达,如果在两个节点最短路径上存在能量池,则用能量池肯定是一种比不用能量池更优的策略,求max的时候会自动考虑这种情况,将能量池也看做节点,建图之后二分结果跑状态dp,直至找到最小的能跑完的值便是答案,注意细节,输出结果即可,详细见代码。

附代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;

char map[20][20];
int x[20];
int y[20];
int dis[20][20];
int d[20][20];      //新图最短路
int dp[16][1<<15+1];        //老规矩,当前停留节点为i,状态为t时剩余的能量
int n,m,cnt,sum,f,maxs;
int gx[4]={0,0,-1,1};
int gy[4]={1,-1,0,0};
const int INF=999999;

int max(int a,int b)
{
    return a>b?a:b;
}

void shortpath(int x1,int y1)       //bfs跑最短路
{
    queue <int> mx;
    queue <int> my;
    for (int i=0;i<n;i++)
        for (int t=0;t<m;t++)
            dis[i][t]=INF;
    dis[x1][y1]=0;
    mx.push(x1);
    my.push(y1);
    while (!mx.empty())
    {
        int curx=mx.front();
        int cury=my.front();
        mx.pop();
        my.pop();
        for (int i=0;i<4;i++)
        {
            int nx=curx+gx[i];
            int ny=cury+gy[i];
            if (map[nx][ny]=='D')
                continue ;
            if (nx<0||nx>=n||ny<0||ny>=m)
                continue ;
            if (dis[nx][ny]>dis[curx][cury]+1)
            {
                dis[nx][ny]=dis[curx][cury]+1;
                mx.push(nx);
                my.push(ny);
            }
        }
    }
}

bool ok(int a)      //检查是否能跑完
{
    for (int i=0;i<sum;i++)
        for (int t=0;t<maxs;t++)        //初始化,除了起点其他点都不可到达,剩余能量为负值即可
            dp[i][t]=-2000;
    dp[f][1<<f]=a;      //初始化初始节点能量
    int mm=(1<<cnt)-1;      //合法的末状态必须满足的条件(跑完所有Y和F节点)
    for (int i=(1<<f);i<maxs;i++)       //枚举状态,从小到大枚举是因为这种状态dp是从小到大递推过去的,保证无后效性,从起点开始枚举
    {
        for (int j=0;j<sum;j++)     //枚举当前停留节点j
        {
            if (dp[j][i]<0)     //如果当前状态停留在节点时剩余能量小于0,则肯定不满足题意,不能继续走了
                continue;
            if ((i&mm)==mm)     //如果当前状态满足末状态条件,返回true
                return true;
            for (int t=0;t<sum;t++)     //枚举下一节点
            {
                int p=1<<t;
                if (i&p)        //如果当前状态已经包含下一节点,则不用继续走,继续走是无用功
                    continue ;
                dp[t][i|p]=max(dp[t][i|p],dp[j][i]-d[j][t]);
                if (dp[t][i|p]>=0&&t>=cnt)      //如果能到达新状态,且新走到的节点是能量池,则充能
                    dp[t][i|p]=a;
            }
        }
    }
    return false;
}

int main()
{
    while (scanf("%d%d",&n,&m)&&n+m)
    {
        getchar();
        for (int i=0;i<n;i++)
            scanf("%s",map[i]);
        cnt=0;
        for (int i=0;i<n;i++)       //寻找起点和必须到达的点,设在新图的前cnt位
        {
            for (int t=0;t<m;t++)
            {
                if (map[i][t]=='Y'||map[i][t]=='F')
                {
                    x[cnt]=i;
                    y[cnt]=t;
                    if (map[i][t]=='F')
                        f=cnt;
                    cnt++;
                }
            }
        }
        sum=cnt;
        for (int i=0;i<n;i++)       //寻找新图的能量池节点,这些点可到可不到,设在信徒的cnt---sum位
        {
            for (int t=0;t<m;t++)
            {
                if (map[i][t]=='G')
                {
                    x[sum]=i;
                    y[sum]=t;
                    sum++;
                }
            }
        }
        for (int i=0;i<sum;i++)     //求最短路
        {
            shortpath(x[i],y[i]);
            for (int t=0;t<sum;t++)
                d[i][t]=dis[x[t]][y[t]];
        }
        maxs=1<<sum;
        int l=0;
        int h=300;      //遍历全图的消费最多大概是200多,最大值开300没问题
        while (l<=h)        //二分结果
        {
            int mid=(l+h)>>1;
            if (ok(mid))
                h=mid-1;
            else
                l=mid+1;
        }
        if (l>300)
            l=-1;
        printf("%d\n",l);
    }
}


poj3311

状态压缩水题,确定一下终点待的位置就行了,再确定一下起点就保证了起点出发终点截止,然后中间更新是注意可以重复访问就行

附代码:

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

int mp[15][15];
int d[15][15];
int dp[1<<12][15];
int n;
const int INF=0x3f3f3f3f;

int min(int a,int b)
{
    return a>b?b:a;
}

void solve()
{
    int maxs=1<<(n+1);
    for (int i=0;i<maxs;i++)
        for (int t=0;t<=n;t++)
            dp[i][t]=INF;
    dp[0][0]=0;
    for (int i=0;i<maxs;i++)
    {
        for (int t=0;t<=n;t++)
        {
            int x=1<<t;
            if (!(i&x))
                continue;
            for (int k=0;k<=n;k++)
                dp[i][t]=min(dp[i][t],dp[i-x][k]+d[k][t]);  //不用刻意去控制是路过某些店,求最短路时已经求好了
        }
    }
    cout<<dp[maxs-1][0]<<endl;
}

int main()
{
    while (cin>>n&&n)
    {
        for (int i=0;i<=n;i++)
            for (int t=0;t<=n;t++)
                cin>>d[i][t];
        for (int i=0;i<=n;i++)
            for (int t=0;t<=n;t++)
                for (int k=0;k<=n;k++)
                    if (d[t][k]>d[t][i]+d[i][k])
                        d[t][k]=d[t][i]+d[i][k];
        solve();
    }
}


依旧是状态压缩,只是这个题目要求每个点最多走两次,不是一次,而且,最大的坑点是,这个题目有重边!!!坑了多少单纯的骚年。

至于每个点走两次,模拟一个三进制,预处理记录下结果就行,输出时判断状态是否全覆盖了,其实也可以预处理,懒得写了。

附代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int mp[15][15];
int n,m;
int p[15];
int mem[60000][15];
int dp[60000][15];
const int INF=0x3f3f3f3f;

void init()     //预处理一个三进制数和各状态的每位情况
{
    memset(mem,0,sizeof(mem));
    memset(p,0,sizeof(p));
    p[0]=1;
    for (int i=1;i<15;i++)
        p[i]=p[i-1]*3;
    for (int i=0;i<60000;i++)
    {
        int cur=i;
        for (int t=0;t<11;t++)
        {
            mem[i][t]=cur%3;
            cur=cur/3;
        }
    }
}

int min(int a,int b)
{
    return a>b?b:a;
}

void solve()
{
    int maxs=p[n];
    for (int i=0;i<maxs;i++)
        for (int t=0;t<=n;t++)
            dp[i][t]=INF;
    for (int i=0;i<n;i++)
        dp[p[i]][i]=0;
    for (int i=1;i<maxs;i++)
    {
        for (int t=0;t<n;t++)
        {
            if (mem[i][t]==0)
                continue ;
            for (int k=0;k<n;k++)
                dp[i][t]=min(dp[i][t],dp[i-p[t]][k]+mp[k][t]);
        }
    }
    int ans=INF;
    for (int i=0;i<maxs;i++)
    {
        int p=0;
        for (int t=0;t<n;t++)
        {
            if (mem[i][t]==0)
            {
                p=1;
                break;
            }
        }
        if (p)
            continue ;
        for (int t=0;t<n;t++)
            ans=min(ans,dp[i][t]);
    }
    if (ans>=INF)
        ans=-1;
    cout<<ans<<endl;
}

int main()
{
    init();
    while (cin>>n>>m)
    {
        int a,b,c;
        for (int i=0;i<=n;i++)
            for (int t=0;t<=n;t++)
                mp[i][t]=INF;
        for (int i=1;i<=m;i++)
        {
            cin>>a>>b>>c;
            mp[a-1][b-1]=mp[b-1][a-1]=min(mp[a-1][b-1],c);  //重边!!!
        }
        solve();
    }
}


CF453B

要求数字两两互质,那么对于每一个质因子,在这个数的序列中肯定最多出现一次。由于1可以无限取,那么大于等于59的数取了肯定没有取1更优,因此取得数的上限是58。预处理出58以内的所有质因子,共16个,用这16个数去状态dp,以当前取了多少个数处于什么状态作为状态划分,依次向后推理并记录路径,输出答案即可。

附代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <stack>

using namespace std;

int p[20]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
int v[65];
int cnt=16;
int dp[101][1<<18]; //当前状态为s,构造到第t个数
int mark[101][1<<16];
const int INF=0x3f3f3f3f;

void init()
{
    memset(v,0,sizeof(v));
    for (int i=1;i<=60;i++)
    {
        for (int t=0;t<cnt;t++)
        {
            if (i%p[t]==0)
            {
                v[i]|=(1<<t);
            }
        }
    }
}

int min(int a,int b)
{
    return a<b?a:b;
}

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

int main()
{
    int n;
    init();
    while (cin>>n)
    {
        int a[110];
        for (int i=1;i<=n;i++)
            cin>>a[i];
        memset(dp,-1,sizeof(dp));
        dp[0][0]=0;
        for (int i=1;i<=n;i++)    //数是逐步向后推理的,状态是不定的,因此以数向后推作为第一条件
        {
            for (int t=1;t<59;t++)      //枚举当前位用哪个数字
            {
                int s=(~v[t])&((1<<16)-1);
                for (int j=s;;j=(j-1)&s)        //枚举没有用这个数字的状态
                {
                    if (dp[i-1][j]==-1)     //如果不用这个数字的状态不能达到,跳过
                        continue ;
                    if (dp[i][j|v[t]]==-1||dp[i][j|v[t]]>dp[i-1][j]+abs(a[i]-t))       //判断是否更优
                    {
                        dp[i][j|v[t]]=dp[i-1][j]+abs(a[i]-t);
                        mark[i][j|v[t]]=t;      //记录路径
                    }
                    if (j==0)       //处理结束,跳出
                        break;
                }
            }
        }
        stack <int> ms;
        int mm=0;
        for (int i=0;i<(1<<16);i++)
            if (dp[n][i]!=-1&&dp[n][i]<dp[n][mm])
                mm=i;
        int cur=n;
        while (cur>0)
        {
            int x=mark[cur][mm];
            ms.push(x);
            mm^=v[x];
            cur--;
        }
        cout<<ms.top();
        ms.pop();
        while (!ms.empty())
        {
            cout<<" "<<ms.top();
            ms.pop();
        }
        cout<<endl;
    }
}

hdu4906

背包+状态压缩的题目

不得不吐槽理解题意是一件很困难的事情,题意理解了之后,看下数据量就该向状态压缩方向想了,然后再想到完全背包,两者一结合,题目便解出来了

具体题解看代码

附代码:

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;
#define ll long long int
const int mod=1000000000+7;
ll dp[(1<<20)+100];     //dp是一个完全背包的过程,i表示背包背到现在的能表示这么多数的状态的总个数,如5=101,表示当前状态能表示1
                        //和3(第一位和第三位),dp[5]表示背包至今用了这么多数且只能能表示1和5两只的数列个数。

int main()
{
    int T;
    cin>>T;
    while (T--)
    {
        int n,k,l;
        cin>>n>>k>>l;
        int tt=0;
        if (l>k)    //大于k的部分的数肯定是无用的数,不能导致新的可用状态出现
        {
            tt=l-k;
            l=k;
        }
        int w=(1<<k)-1;     //最大状态,就是将1-k的所有数均能表示
        memset(dp,0,sizeof(dp));
        dp[0]=1;        //一个数都没有的时候,任何状态都不能表示且状态数为1
        while (n--)     //总共n个数,一定要用n个数,因此n--
        {
            for (int i=w;i>=0;i--)
            {
                if (dp[i]==0)   //剪枝
                    continue ;
                ll temp=dp[i]*tt%mod;   //当前状态加上一个大于k的无用的数之后得到的新的状态
                ll now=dp[i];
                for (int t=l;t>=1;t--)  //当前状态加上一个能导致新的状态的数,需要更新操作
                {
                    int s=i|(1<<(t-1))|((i<<t)&w);      //能更新的所有状态,1<<(t-1)表示仅用t,(i<<t)表示i+t,这里有点
                                                        //不好理解,加入i=3=11=(1,2),新来的数为2,那么新导致的状态有
                                                        //(1+2,2+2)=1100=(i<<t),细细体会~
                    //cout<<i<<" "<<t<<" "<<s<<endl;
                    dp[s]+=now;
                    dp[s]%=mod;
                }
                dp[i]+=temp;    //为什么是+是因为加上一个0导致的新状态不变,所以原有的dp[i]有效,上面有1<<(t-1)操作,故0
                                //不能在上面一步完成
                dp[i]%=mod;
            }
        }
        ll ans=0;
        int p=(1<<(k-1));
        for (int i=p;i<=w;i=(i+1)|p)    //求包含所有能表示k的状态的结果数
        {
            //cout<<i<<endl;
            ans+=dp[i];
            ans%=mod;
        }
        cout<<ans<<endl;
    }
}
/*
这个题目之所以能这样做是因为其满足完全背包的性质,对一个数列,既可以出现重复的数字,也能因为顺序的不同而判定为不同的
序列,这两点是关键,由于这种情况的存在,题目相当于对(1~k)范围内的数字进行完全背包,背包的顺序就是序列的顺序,背包的
次数就是序列的长度,和完全背包不一样的就是要记录背包至今的所有能表示状态,这一点在数据量上很容易想到状态压缩,然后题目
便能解出来了
*/

zoj3802

这题比赛的时候感觉可能是状态dp,可惜还是没能力写出来。

网上的题解都用二维数组,但我觉得这题用一维数组肯定能表示出来,改了许久bug终于搞定。

具体就是,维护一个递减的序列作为当前选取某些值之后形成的序列的状态,因为如果递增,那么前面的数无论如何也不可能被后面的数合并掉,然后,依次对每个数进行两种判断,选或者不选,不选的话不用更新,选的话,遍历状态,如果新加入的值的小于等于状态的lowbit,那么可以加入形成一个新的序列,更新一下序列值,且这样更新的新状态的二进制表示肯定大于之前状态的二进制表示。如果大于状态的lowbit,那么一旦选用,该状态的所有部分就可以丢掉了,因为不可能合并了,因此找最大价值的即可。最后遍历末状态求最值即可。

具体看代码:

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

int dp[1<<13];
int n;
int v[510];

int lowbit(int a)
{
    return a&(-a);
}

int max(int a,int b)
{
    return a>b?a:b;
}

void solve()
{
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    int m=(1<<13)-1;
    for (int i=1;i<=n;i++)
    {
        int mm=0;
        for (int t=m;t>=0;t--)
        {
            //不选用就不用更新,已经存在dp中,只需要更新选用之后能否造成更优解即可
            if (dp[t]==-1)
                continue ;
            if (lowbit(t)>=v[i])    //形成新的递减序列
            {
                int k=(t+v[i])^t;   //新获得的价值
                dp[t+v[i]]=max(dp[t+v[i]],dp[t]+k);
            }else   //该状态已不能合并,舍弃该状态,取所有这样状态的最大值
                mm=max(dp[t],mm);
        }
        dp[v[i]]=max(mm+v[i],dp[v[i]]);     //能否将仅以当前数形成的序列的状态更新更优
    }
}

int main()
{
    int T;
    cin>>T;
    while (T--)
    {
        cin>>n;
        for (int i=1;i<=n;i++)
            cin>>v[i];
        solve();
        int ans=0;
        for (int i=1;i<(1<<13);i++)
            ans=max(ans,dp[i]);
        cout<<ans<<endl;
    }
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值