状压DP十六连测

【状压DP】状态压缩动态规划入门超详解 状压DP一、概述1.状态压缩2.使用条件3.状压DP二、位运算三、例题引入入门例题【例1】填满棋盘入门例题【例二】玉米地 感觉好多讲状压DP的博客都有点乱,我就结合各路大佬的博客,加上我自己的理解,总结出一篇博客来,供初学者参考 一、概述 1.状态压缩 状态压缩就是使用某种方法,简明扼要地以最小代价来表示某种状态,通常是用一串01数字(二进制数)来表示各个点的状态。这就要求使用状态压缩的对象的点的... 阅读详情

DP应该先学插头
状压DP十六连

A[寿司晚宴]

开始想到了反演,设 f(d) f ( d ) 表示两边选出来的公因数为d的方案数,设 F(d) F ( d ) …
好的介绍一个套路,按照最大的质因子分类(没错这不是洲阁筛)
我们发现小于 500−−−√ 500 的质数只有8个,可以搞一个 216 2 16 的状态表示一种方案
让后,我们设 T(x) T ( x ) 表示x的最大质因子并将2~n按照T排序
对于这些T相同的数,它们不能被两个人同时拥有,所以分开dp即可
注意要用滚动数组a

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n,w[10000],t,vis[10000],a[10000];
int b[10000],s[10000],M,f[311][311],g[2][311][311];
inline bool c1(int i,int j){ return b[i]==b[j]?i<j:b[i]<b[j]; }
int main(){
    for(int i=2;i<=500;++i){
        if(!vis[i]) w[++t]=i;
        for(int j=1;j<=t && i*w[j]<=500;++j){
            vis[i*w[j]]=1;
            if(i%w[j]==0) break; 
        }
    }
    for(int i=1;w[i]<20;++i)
        for(int j=w[i];j<=500;j+=w[i]) s[j]|=(1<<i-1);
    for(int i=9;i<=t;++i)
        for(int j=w[i];j<=500;j+=w[i]) b[j]=i;
    scanf("%d%d",&n,&M); --n;
    for(int i=1;i<=n;++i) a[i]=i+1;
    sort(a+1,a+1+n,c1);
    f[0][0]=1;
    for(int i=1,j=1;i<=n;i=j){
        while(j<=n && b[a[i]]==b[a[j]]) ++j;
        if(!b[a[i]]){
            for(;i<j;++i){
                for(int S=0;S<256;++S)
                    for(int T=0;T<256;++T)
                        if(f[S][T]){
                            int _S=S|s[a[i]],_T=T|s[a[i]];
                            if(!(S&_T)) g[0][S][_T]=(g[0][S][_T]+f[S][T])%M;
                            if(!(_S&T)) g[0][_S][T]=(g[0][_S][T]+f[S][T])%M;
                            g[0][S][T]=(g[0][S][T]+f[S][T])%M;
                        }
                for(int S=0;S<256;++S)
                for(int T=0;T<256;++T) f[S][T]=g[0][S][T];
                memset(g,0,sizeof g);
            }
        } else {
            memcpy(g[0],f,sizeof f);
            memcpy(g[1],f,sizeof f);
            for(;i<j;++i){
                for(int S=255;~S;--S)
                    for(int T=255;~T;--T){
                        int _S=S|s[a[i]],_T=T|s[a[i]];
                        if(!(_S&T)) g[0][_S][T]=(g[0][_S][T]+g[0][S][T])%M;
                        if(!(S&_T)) g[1][S][_T]=(g[1][S][_T]+g[1][S][T])%M;
                    }
            }
            for(int S=0;S<256;++S)
            for(int T=0;T<256;++T) f[S][T]=((long long)g[0][S][T]+g[1][S][T]-f[S][T]+M)%M;
        }
    }
    int A=0;                
    for(int S=0;S<256;++S)
        for(int T=0;T<256;++T) if(!(S&T)) A=(A+f[S][T])%M;
    printf("%d\n",A);
}



B[Bill的挑战]

并不是字符串题,也不是FFT题
设 fi,S f i , S 表示匹配到了i位,匹配的串为S的方案数
转移的时候直接搞常数很大会T,所以预处理所有字符串在第i位和字符j的匹配情况

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 1000003
#define LL long long
using namespace std;
int n,m,k,f[55][1<<16],c[1<<16],t[60][27];
char s[20][100];
int _18520(){
    scanf("%d%d",&m,&k);
    memset(f,0,sizeof f); memset(t,0,sizeof t);
    for(int i=0;i<m;++i) scanf("%s",s[i]);
    n=strlen(s[0]);
    f[0][(1<<m)-1]=1;
    for(int i=0;i<n;++i)
        for(int j=0;j<m;++j)
            if(s[j][i]=='?') for(int k=0;k<26;++k) t[i][k]|=(1<<j);
            else t[i][s[j][i]-'a']|=(1<<j);
    for(int i=0;i<n;++i)
        for(int S=0;S<(1<<m);++S) if(f[i][S])
            for(int j=0;j<26;++j) (f[i+1][S&t[i][j]]+=f[i][S])%=M;
    LL A=0;
    for(int S=0;S<(1<<m);++S) if(c[S]==k) A=(A+f[n][S])%M;
    printf("%d\n",A);
}
int main(){
    for(int i=1;i<1<<15;++i) c[i]=c[i>>1]+(i&1);
    int T; for(scanf("%d",&T);T--;_18520());
}



C[奖励关]

我艹这个期望dp贼鸡儿难
我们设,现在到了第i关,手上已经有了物品集合S,之后最多还能得到的答案为 fi,S f i , S
首先答案肯定是 f1,∅ f 1 , ∅ ,考虑转移
枚举本关给出的物品j,我们有两种选择拿或者不拿,就有

fi,S=∑nj=1max(fi+1,S,fi+1,S∪j+valuej),S⊂prejn f i , S = ∑ j = 1 n m a x ( f i + 1 , S , f i + 1 , S ∪ j + v a l u e j ) , S ⊂ p r e j n

至于为什么不可以正着做,很简单,不知道上一个状态是否合法

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n,m,v[100],s[100];
double f[100][1<<17];
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;++i){
        scanf("%d",v+i);
        for(int x;;s[i]|=(1<<x-1)){
            scanf("%d",&x); if(!x) break;
        }
    }
    for(int i=m;i;--i)
        for(int S=0;S<(1<<n);++S){
            for(int j=1;j<=n;++j)
                if((S&s[j])==s[j]) f[i][S]+=max(f[i+1][S],f[i+1][S|(1<<j-1)]+v[j]);
                else f[i][S]+=f[i+1][S];
            f[i][S]/=n;
        }
    printf("%.6lf\n",f[1][0]);
}



D[一双木棋]

和上面题一样,考虑倒推
设当前局面为S,记 f(S) f ( S ) 表示由这个局面开始两者分数之差的最大值
那么如果是A操作,肯定有转移 f(S)=max(f(S′)+valuex) f ( S ) = m a x ( f ( S ′ ) + v a l u e x )
否则就是 f(S)=min(f(S′)−valuey) f ( S ) = m i n ( f ( S ′ ) − v a l u e y )
这里讲一下怎么表示状态,有两种方法
1.记录每一行填了多少个,那就是一个10位的11进制数,需要用map,由于限制,总状态数很少
2.将已经填满的格子的轮廓线用二进制表示
第一种比较好写

#include<map>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define LL long long
using namespace std;
map<LL,int> f; LL bas[20];
int n,m,a[12][12],b[12][12];
inline int dp(LL S){
    if(f.count(S)) return f[S];
    int t[20]={0},c,o=0,r; LL T=S; t[0]=m;
    for(c=0;S;S/=11){
        t[++c]=S%11;
        o^=t[c]&1;
    }
    r=o?1e9:-1e9;
    for(int i=1;i<=n;++i)
        if(t[i]<t[i-1]){
            if(o) r=min(r,dp(T+bas[i])-b[i][t[i]+1]);
            else r=max(r,dp(T+bas[i])+a[i][t[i]+1]);
        }
    return f[T]=r;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j) scanf("%d",a[i]+j);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j) scanf("%d",b[i]+j);
    bas[1]=1;
    for(int i=2;i<=n;++i) bas[i]=bas[i-1]*11;
    for(int i=1;i<=n;++i) *bas+=m*bas[i];
    f[*bas]=0;
    printf("%d\n",dp(0));
}



E[Vladik and cards]

直接设计状态非常不好,因为需要记录当前数及其出现次数,复杂度爆炸
我们假设已经知道了每个数至少出现L次,那么每个数只可能出现至多L+1次
记 fi,S f i , S 表示已经选了数的集合为S,到了第i位的最大答案
转移的时候,枚举加入的数k,一次性加入L个k或者L+1个k,这里需要预处理每种数的位置方便二分找下一个状态
最后在外层二分L,判断是否有合法的答案即可

#include<stdio.h>
#include<string.h>
#include<algorithm> 
using namespace std;
int n,w[8][1010],c[8],f[256][1010];
inline void gmx(int&x,int y){ x<y?x=y:0; }
inline int ok(int L){
    memset(f,0,sizeof f);
    f[0][0]=1; int ans=-1;
    for(int i=0;i<=n;++i)
        for(int S=0;S<256;++S)
            if(f[S][i]){
                for(int k=0;k<8;++k) if(~S&(1<<k)){
                    int u=lower_bound(w[k]+1,w[k]+c[k]+1,i)-w[k]+L-1;
                    if(u>c[k]) continue;
                    gmx(f[S|(1<<k)][w[k][u]],f[S][i]+L);
                    if(++u>c[k]) continue;
                    gmx(f[S|(1<<k)][w[k][u]],f[S][i]+1+L);
                }
            if(S==255) ans=max(ans,f[S][i]-1);
        }
    if(~ans) return ans; return 0;
}
int main(){ 
    scanf("%d",&n);
    for(int x,i=1;i<=n;++i){
        scanf("%d",&x); --x;
        w[x][++c[x]]=i;
    }
    int l=0,r=n>>3;
    for(int m;l<r;){
        m=l+r+1>>1;
        if(ok(m)) l=m;
        else r=m-1;
    }
    printf("%d\n",ok(l));
}



F[学校食堂Dining]

注意到 bi b i 很小,考虑从这里入手
设计状态为 fi,S,last f i , S , l a s t 表示1~i-1这些人都拿到了饭时,S表示i~i+7这些人拿饭的情况,last表示上一个拿到饭的人和i的相对位置(可能为负数)
转移,首先就是 fi,S,last→fi+1,S>>1,last−1, f i , S , l a s t → f i + 1 , S >> 1 , l a s t − 1 , 这个是当i已经拿到饭的情况
否则,枚举下一个拿到饭的人转移,注意考虑后面人的忍耐度即可

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int f[1010][1<<8][20],n,m,t[1010],b[1010];
inline void g(int& x,int y){ (x>y||!x)?x=y:0; }
inline int val(int i,int j){ return i?t[i]^t[j]:0; }
int _18520(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d%d",t+i,b+i);
    memset(f,0,sizeof f); f[1][0][7]=1;
    for(int i=1;i<=n;++i)
        for(int S=0;S<(1<<8);++S)
            for(int j=-8;j<8;++j) if(f[i][S][j+8]){
                if(S&1) g(f[i+1][S>>1][j+7],f[i][S][j+8]);
                else{
                    int r=n;
                    for(int k=0;k<8;++k)
                        if(!(S&(1<<k))){
                            if(i+k>r) break;
                            r=min(r,i+k+b[i+k]);
                            g(f[i][S|(1<<k)][k+8],f[i][S][j+8]+val(i+j,i+k));
                        }
                }
            }
    int A=1<<29;
    for(int j=-8;j;++j) if(f[n+1][0][j+8]) g(A,f[n+1][0][j+8]);
    printf("%d\n",--A);
}
int main(){
    int T;
    for(scanf("%d",&T);T--;_18520());
}



G[围棋]

欢乐的插头dp
首先考虑转移方式,我们可以逐格转移,而不是逐行否则复杂度爆炸
设计一个状态 fi,j,S,t f i , j , S , t 表示做到了第i行第j格,轮廓线上的状态是S(三进制),当前匹配到了模板的第t位
转移的时候,枚举填入的数字,让后用kmp求出新的t’,注意到S的第j位在下次转移没用了,可以直接覆盖掉
让后很好的TLE了
分析问题:S的状态太多了,有 312>500000 3 12 > 500000 种
精简一下S,我们发现不需要记录S具体是什么,而只需要知道S这一位是否可以和模板的第一行完全匹配即可,这样的话,我们改一下状态,设 Fi,j,S,t1,t2 F i , j , S , t 1 , t 2 表示到了第i行第j格,当前行和模板的第一行匹配到了t1格,和模板的第二行匹配到了t2格,所以只需要只需要S>>j&1和t2=m不同时满足就是一个合法状态了
这样状态数变成了 O(nmc2∗2m) O ( n m c 2 ∗ 2 m ) ,空间还是不够就用滚动吧

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define LL long long
#define M 1000000007 
using namespace std;
LL f[2][8][8][10000];
int n,m,c,q,p[14],t[2][6],nt[2][10]={0}; char z[10];
inline void ad(LL& x,LL y){ x=(x+y)%M; }
inline LL pow(LL x,LL k,LL s=1){
    for(;k;x=x*x%M,k>>=1) k&1?s=s*x%M:0;
    return s;
}
int _18520(){
    scanf("%s",z);
    for(int i=0;i<c;++i) t[0][i]=z[i]=='X'?0:(z[i]=='B'?1:2);
    scanf("%s",z);
    for(int i=0;i<c;++i) t[1][i]=z[i]=='X'?0:(z[i]=='B'?1:2);
    memset(nt,0,sizeof nt);
    for(int i=1,j;i<c;++i)
        for(j=i;j;) if(t[0][j=nt[0][j]]==t[0][i]){ nt[0][i+1]=j+1; break; }
    for(int i=1,j;i<c;++i)
        for(j=i;j;) if(t[1][j=nt[1][j]]==t[1][i]){ nt[1][i+1]=j+1; break; }
    memset(f,0,sizeof f);
    f[0][0][0][0]=1; int T=1; int AL=(1<<m-c+1);
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            T^=1; memset(f[T^1],0,sizeof f[T^1]);
            for(int S=0;S<AL;++S)
                for(int x=0;x<=c;++x)
                    for(int y=0;y<=c;++y) if(f[T][x][y][S]){
                        for(int k=0;k<3;++k){
                            int dx=x,dy=y,dS=S;
                            while(dx && (dx>=c || t[0][dx]!=k)) dx=nt[0][dx];
                            if(t[0][dx]==k) ++dx;
                            while(dy && (dy>=c || t[1][dy]!=k)) dy=nt[1][dy];
                            if(t[1][dy]==k) ++dy;
                            if(dy==c&&(S&(1<<j-c+1))) continue;
                            if(dx==c&&!(dS&(1<<j-c+1))) dS|=(1<<j-c+1);
                            if(dx!=c&&(dS&(1<<j-c+1))) dS^=(1<<j-c+1);
//                          printf("%d %d %d\n",dx,dy,S+(((dx==c)<<j-c)-(wj<<j-c)));
                            ad(f[!T][dx][dy][dS],f[T][x][y][S]);
                        }
                    }
        }
        memset(f[T],0,sizeof f[T]);
        for(int S=0;S<AL;++S)
            for(int x=0;x<=c;++x)
                for(int y=0;y<=c;++y)
                    ad(f[T][0][0][S],f[!T][x][y][S]);
        T^=1;
    }
    LL A=pow(3,n*m);;
    for(int S=0;S<AL;++S) ad(A,-f[!T][0][0][S]);
    printf("%lld\n",(A+M)%M);
}
int main(){
    scanf("%d%d%d%d",&n,&m,&c,&q);
    for(int i=*p=1;i<=m;++i) p[i]=p[i-1]*3;
    for(int T=q;T--;_18520());
}



H[Mutation]

讲真不是dp,不过真的很妙,角度新奇
首先我们考虑一下怎么会产生贡献
对于每个字符i,和他后面的所有j字符中最靠前的那个,才有可能产生贡献
条件就是这些中间的字符都被清除,而且i和j没有被清除
搞清楚这个了之后就考虑快速统计答案了,设 F(S) F ( S ) 表示清除了S集合的字符,最后的贡献是多少
那么首先枚举每一对可以产生贡献的字符对 (i,j) ( i , j ) ,设其价值为 v v ,i和j之间的字符集合为S
显然 F(S) F ( S ) 会被加上 v v ,而F(T) (T⊃S)也有可能被加上 v v ,我们先给所有T⊇S的 F(T) F ( T ) 加上 v v
但是如果有i∈T或者 j∈T j ∈ T 那么这个贡献是没有的,要减去,也就是 T⊇S∪i T ⊇ S ∪ i 和 T⊇S∪j T ⊇ S ∪ j 这样的要减去一个 v v
最后我们发现对于T⊇S∪i∪j被减多了一次,还要加回来
整个过程都在差分数组上面完成,最后做一次前缀和就是答案,这里不要忘记了加上去掉某一类数字的花费

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n,k,T,t[26],f[1<<23],v[26][26],s[26]; char c[200010];
int main(){
    scanf("%d%d%d",&n,&k,&T);
    scanf("%s",c+1);
    for(int i=0;i<k;++i) scanf("%d",t+i);
    for(int i=0;i<k;++i)
        for(int j=0;j<k;++j) scanf("%d",v[i]+j);
    for(int i=0;i<k;++i) f[1<<i]=t[i];
    memset(s,-1,sizeof s); int S=0;
    for(int i=1;i<=n;++i){
        int x=c[i]-'A'; S|=(1<<x);
        for(int j=0;j<k;++j)
            if(~s[j]){
                if(!(s[j]&(1<<j)) && !(s[j]&(1<<x))){
                    f[s[j]]+=v[j][x];
                    f[s[j]|(1<<x)]-=v[j][x];
                    f[s[j]|(1<<j)]-=v[j][x];
                    f[s[j]|(1<<x)|(1<<j)]+=v[j][x];
                }
                s[j]|=(1<<x);
            }
        s[x]=0;
    }
    for(int i=0;i<k;++i)
        for(int j=0;j<(1<<k);++j)
            if(j&(1<<i)) f[j]+=f[j^(1<<i)];
    int A=0;
    for(int j=0;j<(1<<k);++j)
        if((j&S)==j && f[j]<=T && (j^S)) A++;
    printf("%d\n",A);
}



I[Formula 1]

终于到了开头提到的插头dp经典题了(如果不会请移步cdq课件)
记 fi,j,S f i , j , S 表示现在路径填到了第i行第j个格子,S是一个三进制数,对于每一位
如果为0,表示当前位置轮廓线没有插头
如果为1,表示当前位置轮廓线插头是一条路径的起点
如果为2,表示当前位置轮廓线插头是一条路径的终点
(有一点是显然的,每一段路径必然之和轮廓线相交偶数次,这相当于一个括号序列)
那么,我们可以根据第j位和第j+1位有没有插头来确定当前格子填什么
有四种情况
1.j和j+1均无插头,那么创建两个新插头作为一条路径(┏型情况)
2.j有插头,可以填━或者填┓,有两种情况
3.j+1有插头,可以填┃或者┗,也有两种情况
4.j和j+1均有插头,那么只能填┛,但是请注意这有可能将闭合整个路径,所以除非此格子是整个棋盘最后一个非空格子,否则不能闭合另外两条路径
让后注意一下石头的情况,只能是和情况1一样的,否则不合法,剩下暴力转移就可以辣

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define LL long long
using namespace std;
int n,m,p[20]; LL f[2][1600000]; char s[20][20],lst[20][20]={0};
int main(){
    scanf("%d%d",&n,&m);
    for(int i=*p=1;i<20;++i) p[i]=p[i-1]*3;
    for(int i=0;i<n;++i) scanf("%s",s[i]);
    for(int i=n-1;i;--i)
        for(int j=m-1;~j;--j) if(s[i][j]=='.'){ lst[i][j]=1; goto next; }
    next:
    f[0][0]=1; int T=1;
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            T^=1; memset(f[T^1],0,sizeof f[T^1]);
            for(int S=0,_S;S<p[m+1];++S) if(f[T][S]){
                int t[15]={0},w[15],d[15],c=0;
                for(int k=0;k<=m;++k){
                    t[k]=S/p[k]%3;
                    if(t[k]==1) d[++c]=k;
                    else if(t[k]==2){ w[k]=d[c]; w[d[c--]]=k; }
                }
                if(s[i][j]=='*'){
                    if(!t[j] && !t[j+1]) f[!T][S]+=f[T][S];
                    continue;
                }
                if(!t[j] && !t[j+1]){
                    f[!T][S+p[j]+2*p[j+1]]+=f[T][S];
                } else if(!t[j+1]){
                    f[!T][S]+=f[T][S];
                    f[!T][S-p[j]*t[j]+t[j]*p[j+1]]+=f[T][S];
                } else if(!t[j]){
                    f[!T][S]+=f[T][S];
                    f[!T][S-p[j+1]*t[j+1]+t[j+1]*p[j]]+=f[T][S];
                } else {
                    if(t[j]==1 && t[j+1]==2){
                        if(lst[i][j]) f[!T][S-p[j]-2*p[j+1]]+=f[T][S]; continue;
                    }
                    else if(t[j]==2 && t[j+1]==1){ f[!T][S-2*p[j]-p[j+1]]+=f[T][S]; }
                    else if(t[j+1]==1 && t[j]==1){ f[!T][S-p[j]-p[j+1]-p[w[j+1]]]+=f[T][S]; }
                    else if(t[j+1]==2 && t[j]==2){ f[!T][S-2*p[j]-2*p[j+1]+p[w[j]]]+=f[T][S]; }
                }
            }
        }
        memset(f[T],0,sizeof f[T]);
        for(int S=0;S<p[m];++S) f[T][S*3]=f[!T][S];
        T^=1;
    }
    printf("%lld\n",f[T][0]);
}



J[Eat the Trees]

上面题的弱化版
三进制改成二进制记录有没有插头就可以辣
也不用管路径闭合什么的,反正不限条数

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define LL long long
using namespace std;
LL f[12][12][1<<12]; int n,m,s[12][12];
int _18520(int T){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;++i)
        for(int j=0;j<m;++j)
            scanf("%d",s[i]+j);
    memset(f,0,sizeof f); f[0][0][0]=1;
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j)
            for(int S=0;S<(1<<m+1);++S){
                int l=(1<<m),u=(1<<j);
                if(s[i][j]==0){
                    if((~S&u) && (~S&l)) f[i][j+1][S]+=f[i][j][S];
                } else if((S&l)&&(S&u)){ f[i][j+1][S-u-l]+=f[i][j][S]; }
                else if(S&l){
                    f[i][j+1][S]+=f[i][j][S];
                    f[i][j+1][S-l+u]+=f[i][j][S];
                } else if(S&u){
                    f[i][j+1][S]+=f[i][j][S];
                    f[i][j+1][S-u+l]+=f[i][j][S];
                } else f[i][j+1][S+u+l]+=f[i][j][S];
            }
        for(int S=0;S<(1<<m);++S) f[i+1][0][S]=f[i][m][S];
    }
    printf("Case %d: There are %I64d ways to eat the trees.\n",T,f[n][0][0]);
}
int main(){
    int T; scanf("%d",&T);
    for(int i=1;i<=T;++i) _18520(i);
}



K[Black & White]

我艹这个题真的是坑,辣鸡插头dp
是本联测最难的题,主要问题就是输出方案
还是像I题一样的套路,设 fi,j,S,C f i , j , S , C 表示到了i,j,轮廓线上面的颜色为C,连通性状态为S
S是类似于一个集合表示法的东西,一个八进制数,拆开每一位代表每个格子所处联通块编号
这个时候就发现状态数爆炸了,无法转移。
无用状态很多,可以换成bfs的方式,每次转移一格搞一个哈希表存有用状态
转移有一个比较关键的地方:如果当前格子可以把他上面那个格子所在的联通块独立出来那么就不能填进去,还有就是2*2的同色块不能有
每个状态记录一下方案,方便输出答案
细节非常的多所以还是建议参考一下别人的code吧

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 199999
#define N 210110
using namespace std;
char s[120][120];
int n,m,T,ps[100][N],o[100][N];
struct HASH{
    int h[N],cnt,nt[N],f[N],s[N],c[N];
    inline void clr(){ cnt=0; memset(h,-1,sizeof h); }
    inline void push(int S,int C,int v,int blk,int lst,int opt){
        int H=(S*13+C)%M;
        for(int i=h[H];~i;i=nt[i])
            if(s[i]==S && c[i]==C){ f[i]+=v; return; }
        f[cnt]=v; nt[cnt]=h[H]; s[cnt]=S; c[cnt]=C; h[H]=cnt;
        ps[blk][cnt]=lst; o[blk][cnt]=opt; ++cnt;
    }
} F[2];
inline void df(int i,int j,int o){
    for(int K=0;K<F[T].cnt;++K){
        int S=F[T].s[K],C=F[T].c[K],b[20],c[20],dS=0;
        int u=(i&&((C>>j)&1)==o),l=(j&&((C>>j-1)&1)==o),ul=i&&j&&(C>>m&1)==o;
        if(l&u&ul) continue; dS=S; //2*2判断
        if(i==n-1&&j==m-1&&!u&&!l&&ul)//最后一格特判
            continue;
        for(int t=0;t<m;++t){ c[t]=C>>t&1; b[m-t-1]=dS&7; dS>>=3; }
        if(i && !u){//如果可能将u所在联通块独立
            int c1=0,c2=0;
            for(int t=0;t<m;++t)
                c1+=(b[t]==b[j]),c2+=(c[t]!=o);
            if(c1==1 && (c2>1||i<n-1||j<m-2)) continue;
        }
        if(u&l){//上面和左边联通
            int v=b[j];
            for(int t=0;t<m;++t)
                if(b[t]==v) b[t]=b[j-1];
        }
        if(!u&&l) b[j]=b[j-1];
        if(!u&&!l) b[j]=m;
        if(c[j]) C|=1<<m; else C&=~(1<<m);
        if(o) C|=1<<j; else C&=~(1<<j);
        memset(c,-1,sizeof c);
        for(int t=dS=0,k=-1;t<m;++t)//重标号
            dS=(dS<<3)|(~c[b[t]]?c[b[t]]:c[b[t]]=++k);
        F[!T].push(dS,C,F[T].f[K],i*m+j,K,o);
    }
}
void print(int k){
    for(int i=n-1;~i;i--)
        for(int j=m-1;~j;j--){
            if(o[i*m+j][k]) s[i][j]='#'; else s[i][j]='o';
            k=ps[i*m+j][k];
        }
    for(int i=0;i<n;++i) puts(s[i]);
}
inline void _18520(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;++i) scanf("%s",s[i]);
    F[0].clr(); F[0].push(0,0,1,0,0,0); T=0;
    for(int i=0;i<n;++i){
        for(int j=0;j<m;++j){
            F[!T].clr();
            if(s[i][j]!='#') df(i,j,0);
            if(s[i][j]!='o') df(i,j,1);
            T^=1;
        }
    }
    int res=0,printed=0;
    for(int K=0;K<F[T].cnt;++K){
        int S=F[T].s[K];
        for(int j=0;j<m;++j) if((S>>j*3&7)>1)goto end;
        res+=F[T].f[K];
        printed=K;
        end:;
    }
    printf("%d\n",res);
    if(res) print(printed); puts("");
}
int main(){
    int T;
    for(scanf("%d",&T);T--;_18520());
}



L[Fish]

暴雨过后的彩虹?(这是个flag)
一看就是非常sb的题啊(雾)
设 f(S) f ( S ) 表示S出现的可能性,S为存活鱼的集合
让后?枚举哪条鱼死了就可以了

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define db double
using namespace std;
int n,c[1<<20]; db f[1<<19],p[20][20];
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            scanf("%lf",p[i]+j);
    for(int i=1;i<(1<<n);++i) c[i]=c[i>>1]+(i&1);
    f[(1<<n)-1]=1;
    for(int S=(1<<n)-1;S;--S)
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                if(i!=j && S&(1<<i) && S&(1<<j))
                    f[S^(1<<j)]+=f[S]*p[i][j]/(c[S]*(c[S]-1)>>1);
    for(int i=0;i<n;++i) printf("%.6lf ",f[1<<i]);
}



M[Traveling Graph]

也不是一个特别困难的题,只是一开始想错了
首先发现是一个一笔画问题,要求要回到起点,可以走重复的边
先跑个floyd求一求最短路
按照小奥的知识,把度数为奇数的点找出来,每次找两个连起来
这里用状压dp来做就可以了,注意不连通的点,如果度数为0就不管否则-1
其实还有更牛逼的一般带权图匹配,复杂度比这个好很多

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define inf (1<<29)
#define LL long long
using namespace std;
int n,m,s[20][20],d[20]; LL f[1<<20],S,A;
inline void g(LL& x,LL y){ x>y?x=y:0; } 
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j) s[i][j]=inf;
    for(int x,y,c;m--;){
        scanf("%d%d%d",&x,&y,&c); --x; --y; A+=c;
        s[x][y]=s[y][x]=min(s[x][y],c); d[x]++; d[y]++;
    }
    for(int k=0;k<n;++k)
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                s[i][j]=min(s[i][j],s[i][k]+s[k][j]);
    for(int i=0;i<n;++i) s[i][i]=0;
    for(int i=1;i<n;++i) if(d[i]&&s[0][i]==inf) return 0&puts("-1");
    m=1<<n; for(int i=0;i<m;++i) f[i]=inf; f[0]=0;
    for(int S=0,i;S<m;++S){
        for(i=0;i<n;++i) if((d[i]&1)&&(S&(1<<i))) break;
        if(i>=n) f[S]=0;
        for(i=0;i<n;++i) if((d[i]&1)&&(~S&(1<<i)))
            for(int j=i+1;j<n;++j) if((d[j]&1)&&(~S&(1<<j))&&s[i][j]<inf)
                g(f[S|(1<<i)|(1<<j)],f[S]+s[i][j]);
    }
    if(f[m-1]>=inf) return 0&puts("-1");
    printf("%d\n",A+f[m-1]);
}



N[A Simple Task]

和上面那题差不多了
首先要记录当前环内有哪些元素,和环尾是哪个
因为一个环有多种表示方式,我们强制将环内编号最小的那个点设为环头
每次从环尾更新,如果可以走到环头那么就更新答案
注意,即使规定了环头一个环依然有两种走法,所以最后答案要/2

#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define LL long long
using namespace std;
struct E{ int S,lst; }t;
struct edge{ int v,nt; } G[1010];
int n,m,h[110],cnt; LL f[1<<20][21]={0},ans=0;
inline void adj(int x,int y){
    G[++cnt]=(edge){y,h[x]}; h[x]=cnt;
    G[++cnt]=(edge){x,h[y]}; h[y]=cnt;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int x,y,i=1;i<=m;++i){
        scanf("%d%d",&x,&y);
        --x; --y; adj(x,y);
    }
    for(int i=0;i<n;++i) f[1<<i][i]=1;
    for(int S=1;S<(1<<n);++S)
        for(int i=0;i<n;++i) if(f[S][i]){
            int y=0; for(int j=0;j<n;++j) if(S&(1<<j)){ y=j; break; }//找环头
            for(int j=h[i];j;j=G[j].nt)
                if(G[j].v==y) ans+=f[S][i];
                else if(G[j].v>y && ~S&(1<<G[j].v)) f[S|(1<<G[j].v)][G[j].v]+=f[S][i];
        }
    printf("%lld\n",ans-m>>1);//去掉长度为2的答案(不合法)
}



O[Square Subsets]

就是问你有多少个子集乘积为平方数
注意到 ai a i 非常小,所以肯定直接记录质因数的次数mod 2的情况就可以啦
用线性筛预处理每个数的质因数的次数,方便转移

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define LL long long
#define M 1000000007
using namespace std;
int w[100],t,vis[110],f[2][1<<19];
int n,b[110],s[110],c[72],v[100010]; LL pow[100010];
inline void ad(int& x,LL y){ x=(x+y)%M; } 
int main(){
    scanf("%d",&n); *pow=1;
    for(int i=1;i<=n;++i){
        scanf("%d",v+i); ++c[v[i]]; pow[i]=pow[i-1]*2%M;
    }
    for(int i=2;i<=70;++i){
        if(!vis[i]) w[++t]=i;
        for(int j=1,k;j<=t && (k=i*w[j])<=70;++j){
            vis[k]=1;
            if(i%w[j]==0) break;
        }
    }
    for(int i=1;i<=t;++i){
        for(int k=w[i];k<=70;k*=w[i]) 
            for(int j=k;j<=70;j+=k) s[j]^=(1<<i-1);
    }
    f[0][0]=1;
    for(int i=1;i<=70;++i){
        if(c[i]){
            for(int S=0;S<(1<<19);++S)
                if(f[0][S]){
                    ad(f[1][S^s[i]],f[0][S]*pow[c[i]-1]%M);
                    ad(f[1][S],f[0][S]*pow[c[i]-1]%M);
                }
            swap(f[0],f[1]); memset(f[1],0,sizeof f[1]);
        }
    }
    printf("%d\n",(f[0][0]-1+M)%M);
}



P[集合选数]

首先可以注意到,对于两个数a和ka,如果k有除了2和3以外的质因数那么这两个数选不选是互相不影响的
所以我们对于每个 a≠0(mod 2),a≠0(mod 3) a ≠ 0 ( m o d   2 ) , a ≠ 0 ( m o d   3 ) 搞一个类似这样的矩阵

a3a9a⋮2a6a18a⋮4a12a36a⋮………(1) (1) a 2 a 4 a … 3 a 6 a 12 a … 9 a 18 a 36 a … ⋮ ⋮ ⋮

那么问题就变成了在矩阵中取不能相邻的数的方案数,直接存储每一列状态转移就可以了

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 1000000001
#define LL long long
using namespace std;
LL A=1,f[20][1<<12];
int n,s[20][20],c[20];
bool vis[1<<22];
inline LL cal(int x){
    memset(s,0,sizeof s);
    memset(f,0,sizeof f);
    s[0][0]=x;
    for(int j=1;;++j){
        vis[s[0][j]=s[0][j-1]*3]=1;
        if(s[0][j]>n){ *c=j; break; }
    }
    int m=0;    
    for(int i=1;;++i){
        vis[s[i][0]=s[i-1][0]<<1]=1;
        if(s[i][0]>n){ m=i; break; }
        for(int j=1;;++j){
            vis[s[i][j]=s[i][j-1]*3]=1;
            if(s[i][j]>n){ c[i]=j; break; }
        }
    }
    for(int S=0;S<(1<<*c);++S) f[0][S]=!(S&(S<<1));
    for(int i=0;i<m;++i)
        for(int S=0;S<(1<<c[i]);++S) if(f[i][S])
            for(int T=0;T<(1<<c[i+1]);++T)
                if(!(S&T) && !(T&(T<<1))) f[i+1][T]=(f[i][S]+f[i+1][T])%M;
    return f[m][0];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        if(!vis[i]) A=A*cal(i)%M;
    printf("%lld\n",A);
}
【状压DP】易懂讲解状态压缩/状态压缩DP 本文是状态压缩DP的总结 阅读详情

相关推荐

数据结构与算法之状压DP剪枝

数据结构与算法之状压DP剪枝

weixin_47225948的博客 466

【学习笔记】简单的连通性状压DP——插头DP(不学以为是天书)

不学不会,一学还是不会。

5617

算法详解(六)---动态规划(DP)

本文介绍了动态规划(DP)的基本概念和常见应用。动态规划通过分解问题为重叠子问题并存储解来提高效率,具有最优子结构、重叠子问题和无后效性三大特征。文章详细讲解了斐波那契数列、爬楼梯问题、最长公共子序列(LCS)和最小路径和等经典DP问题的解题步骤,包括状态定义、转移方程、边界条件和计算顺序,并提供了C++实现代码及空间优化版本。这些案例展示了DP在解决最优化和计数问题中的强大能力。

whc的博客 731

状态压缩动态规划——状压dp

状压dp:意思是将状态进行压缩,从而更容易地写出状态转移方程通常做法:将每个状态(一个集合)用二进制表示,每个位的1就代表着这个编号的元素存在,0就代表着这个编号的元素不存在,如二进制100110,即是集合{1,2,5}的压缩通过状态压缩,我们可以使得状态通过位运算来判断是否可以转移。

Se_ren_di_pity的博客 2631

算法第十六期——动态规划(DP)之线性DP

线性dp

荷叶田田的博客 1495

【技术解析】LUT-Fuse:如何通过可学习查找表与蒸馏技术,为边缘设备解锁实时图像融合

本文解析了ICCV 2025提出的LUT-Fuse技术,它通过可学习查找表与知识蒸馏,解决了边缘设备上图像融合的质量、速度与功耗“不可能三角”。该方法将复杂计算预编译为高速查找表,在Jetson等设备上实现100+ FPS的实时红外-可见光图像融合,为无人机、安防等应用提供了高效解决方案。

code8的博客 494

状态压缩DP 图文详解(一)

超详细的讲解,有图有真相的状压DP真的不来看看吗?

m0_64226820的博客 2万+

价值投资/指标选股(akshare)

利用akshare的数据源,可以获取所有的股票数据以及公开的财务数据,可以进行条件筛选,获取满足条件的股票。 这套模板可以用于价值投资,指标选股。选股周期为日线级别。然后结果可以邮箱发送。 import time import akshare as ak ## A 股上市公司的实时行情数据 stock_zh_a_spot_df = ak.stock_zh_a_spot() #print(stock_zh_a_spot_df) ##取前300测试 ##取前300测试 df_stock =...

qq_26742269的博客 1万+

第十六课:数位dp、状压dp、树形dp、记忆化搜索

dp模板题

weixin_64197656的博客 651

VS2015编译配置GDAL(图文并茂、超详细!!!)

文章目录一、GDAL介绍二、下载GDAL三、修改源代码四、工具命令提示符操作五、编译GDAL六、安装GDAL七、在VS2015的项目中配置GDAL八、添加环境变量九、测试是否配置成功 一、GDAL介绍 GDAL(Geospatial Data Abstraction Library)是一个在XMIT许可协议下的开源栅格空间数据转换库。它利用抽象数据模型来表达所支持的各种文件格式。它还有一系列命令行工具来进行数据转换和处理。OGR是GDAL项目的一个分支,功能与GDAL类似,只不过它提供对矢量数据的支持。因此

weixin_46196863的博客 3756

BurpSuite+安卓模拟器双保险:避开真机抓包限制的完整配置指南

本文详细介绍了如何通过BurpSuite与安卓模拟器(如雷电、夜神)协同工作,绕过真机抓包限制的完整配置指南。从环境准备、网络拓扑配置到证书部署和流量分析,提供了一套高效的解决方案,帮助安全测试人员稳定捕获手机数据包,提升测试效率。

Apple的专栏 436

Uestc1291 上天的卿学姐【状压dp】

Description 众所周知,卿学姐十分擅长数据结构。 一天卿学姐开始研究起二维偏序的问题,卿学姐三下五除二就写了个树状数组解决了。 于是卿学姐开始做三维的问题,搞了个树套树也是过了。 欲求不满的卿学姐直接开始搞五维的偏序,仔细思索之后,卿学姐研究出一种用分块加bitset的做法。 峰回路转,沈宝宝问感觉自己要上天的卿学姐16维偏序怎么做,卿学姐现在还在研究六维偏序,不得不将这个问题...

cdsszjj的博客 418

基于Transformer的CFD代理模型:AeroTransformer原理、实现与优化

计算流体动力学(CFD)仿真是航空航天、汽车工程等领域的关键技术,但其高计算成本限制了设计优化效率。代理模型通过机器学习方法学习输入参数与流场输出间的映射关系,能实现秒级预测,大幅加速仿真流程。Transformer架构凭借其全局注意力机制,能有效建模流场中的长程物理关联,克服了传统卷积神经网络在捕捉全局依赖上的局限。通过预训练与微调策略,模型能先在大规模多样化数据集上学习通用流体规律,再针对特定任务快速适配,显著提升了小样本下的泛化能力与工程实用价值。AeroTransformer项目正是这一技术路线的典

weixin_33509203的博客 358

LightOJ 1021 - Painful Bases(状压DP)

大致题意:求 ’0‘ ~ ‘F' 的排序,组成16进制数,能被K整除,求排列的方案数 思路: 基础状压,注意从低位到高位DP,因为全是F在最高位时对于的十六进制会爆long long //#pragma comment(linker, "/STACK:1024000000,1024000000") #include #include #include #include #inc

kalilili的专栏 560

用STM32F103C8T6和TB6612驱动模块,从零搭建一辆能避障循迹的智能小车(附完整代码)

本文详细介绍了如何使用STM32F103C8T6和TB6612驱动模块从零搭建一辆能避障循迹的智能小车。内容涵盖硬件选型、电路设计、底层驱动配置以及智能行为算法实现,并附有完整代码示例,帮助开发者快速掌握嵌入式开发的核心技术。

weixin_42641741的博客 444

【Qwen2微调实战】LLaMA-Factory框架对Qwen2-7B模型的微调实践

在人工智能的浪潮中,大型语言模型(LLMs)正逐渐成为研究和应用的热点。它们在自然语言处理(NLP)的各个领域,如文本生成、翻译、问答等任务中展现出了卓越的能力。Qwen2-7B模型,作为该领域的佼佼者,不仅因其庞大的参数量和强大的表示能力受到关注,更因其在微调后能够更好地适应特定任务而备受关注。本文将详细介绍如何利用LLaMA-Factory这一高效的微调框架对Qwen2-7B进行微调,以期达到提升模型在特定任务上的表现。

寻道AI,探索AI无限可能! 2万+

蓝桥杯 回路计数(状压DP)

f[i][j]表示走到j点时,状态为i的方案数量 M=1<<21 为22位的二进制数,M-1为21位1,表示最终状态 转移方程为: (转移方程的约束条件见代码注释) #include<iostream> #include<algorithm> #include<map> #include<cstring> #include<queue> using namespace std; typedef long long ll; ..

weixin_50533561的博客 1352

YOLO26优化:注意力魔改 | EMA:基于跨空间学习的高效多尺度注意力,效果优于ECA、CBAM、CA

加入EMA注意力,一种基于跨空间学习的高效多尺度注意力,效果优于ECA、CBAM、CA等经典注意力。

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 332

ABC194-F -状压,数位dp

题目大意: 一个十六进制数nnn.问你有多少个不含前导0的小于等于nnn的数里面恰好有kkk个不同的数. ∣n∣≤2e5|n| \leq 2e5∣n∣≤2e5 题目思路: 首先,kkk个不同的数。我们就记录dpdpdp的一个维度为不同的数。然后转移的时候分与前面相同和与前面不同两种情况分别转移即可。但是这里显然得再套一个数位dp的模板。这里题解应用的递推版数位dpdpdp: 做法: 1.令dp(i,j)dp(i,j)dp(i,j)为填写完前iii个数且恰好有jjj不同的数的方案数。并且没顶到上界。 那么这个

qq_35577488的博客 417

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

JZOJ 6493. 【GDOI2020模拟03.04】迷宫(状压DP)

JZOJ 6493. 【GDOI2020模拟03.04】迷宫 题解 这道题的题面比较玄学,乍一看还以为是道PJ难度的最短路。。。 其实,题目的设定十分有趣,你被放进了迷宫里,只能分清门(A/B/C/D),而不能分清房间(0除外),也就是说,你只知道当前可能所在的房间集合,和整个迷宫的结构,而并不知道具体在哪个房间,不过可以推算每一步可能所在的房间集合,希望最少的步数能保证走出这个迷宫(走出0号房...

ZSJZ_liuzian的博客 366

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

[RK3588-Android12] 解析DP/HDMI接口EDID数据的十六进制转换技巧

本文详细介绍了在RK3588 Android12平台上,解析DP/HDMI接口EDID数据时,将原始二进制数据转换为可读十六进制的核心技巧。通过修改内核edid_show函数,开发者可以实时获取格式化的EDID信息,从而高效调试显示器兼容性问题,如分辨率、刷新率及色彩空间支持。

berry的博客 861

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

备战省赛组队训练赛第十六场 Winner Winner

题目描述 The FZU Code Carnival is a programming competetion hosted by the ACM-ICPC Training Center of Fuzhou University. The activity mainly includes the programming contest like ACM-ICPC and strive to pr...

c___c18的博客 365

状压dp入门_信息学入门第三十五课(状压dp)

在n×n 的棋盘里面放 K 个国王,使他们互不攻击,求共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上的每一个格子,共8个格子。输入格式只有一行,包含两个数N,K ( 1 <=N<=9, 0 <= K <= N * N)输出格式所得的方案数作业:P1441,P1896(会方法即可)我是培淇,如果你来到了这里,你一定也是信息学或者数学上的...

weixin_42140710的博客 209

蓝桥杯 第五十六天 数位dp

目录度的数量数字游戏 度的数量 这一题主要是题意比较搞人,第一个break不太好理解 def dp(x): if x==0: return 0 nums=[] while x>0: nums.append(x%b) x//=b ans=0 last=0 for i in range(len(nums)-1,-1,-1): x=nums[i] if x!=0:

aixiaoxiao13的博客 2591

【正点原子FPGA连载】第十六章DP彩条显示实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.htmlDP接口即DisplayPort接口,DisplayPort是由视频电子标准协会(VESA)发布的显示接口。作为DVI的继任者,DisplayPort将在传输视频信号的同时加入对高清音频信号传输的支持,同时支持更高的分辨率和

weixin_55796564的博客 5471

数位DP Ⅱ

本节题目:数位之间和的关系。一般需要多加一维状态f[i][j][k]:表示长度为i,第一位是i,数位和等于k的数列个数 Seven Segment Display 题意:每个数字都有一个对应的权值,求l~r(十六进制数)之间所有数的所有数字的权值和预处理数组有点麻烦,f[i][j]表示长度为i,第一个数是j的所有数列的数位之和的总和,f[i][j]=f[i-1][0~15]+j*(长度为i-1的...

weixin_43769146的博客 65
上一篇: 高斯消元模板
下一篇: SPFA的两个优化
扩展的灰
博客等级 码龄12年 60粉丝 342原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值