我们刚刚知道那些题的解法-2

CF1292F

考虑对于 \(i,j\),如果有 \(a_i|a_j\),那么就从 \(i\)\(j\) 连一条边,考虑形成的弱连通分量之间互相独立,因此可以用一个组合数乘起来,现在考虑一个连通分量。

\(S\) 表示所有入度为 \(0\) 的点,\(T\) 为所有剩下的点组成的集合,那么显然我们有一个删除序列长度的上界 \(|T|-1\)。其实,这个上界是可以达到的,证明只需要这样考虑:现在考虑一个集合 \(M\) 中只有一个数,我们每次找到一个 \(u,v\) 满足 \(u\in S,v\in T\),且 \(u\)\(v,m\in M\) 有边,然后把 \(v\) 加入 \(M\) 中。重复这个操作,显然我们一定可以把所有数都加进来。

现在我们就可以考虑一个 DP,设 \(f_{s,c}\) 表示我们现在的删除序列里面有 \(c\) 个数,这 \(c\) 个数的所有因子组成的集合与 \(S\) 的交集为 \(s\),方案数是多少,好像我们还需要记录一个状态 \(t\) 表示哪些点加进来了。但实际上有了 \(c\),我们不需要记录 \(t\)。考虑如果一个数加进来可以使 \(s\) 变大,那么它显然之前没有加进来过,否则,\(s\) 没有变化,但是我们可以预处理一个 \(cnt_s\) 表示因子集合为 \(s\) 的子集的数的个数,我们可以把满足条件的数一起转移。即 \(f_{s,c}\times cnt_s\to f_{s,c}\)

我们现在有了一个复杂度是 \(O(2^{|S|}|T|^2)\) 的 DP。现在证明 \(|S|\le \frac{N}{4}\),其中 \(N\) 是这个弱连通分量中最大的 \(a_i\)

首先,所有大于 \(\frac{N}{2}\)\(S\) 中的点一定是孤立点,因为它们不可能连到其它点,所以 \(S\) 中的元素只可能是 \(\le \frac{N}{2}\)

其次,设 \(f(x)\) 表示 \(x\) 不断除以 \(2\) 之后得到的奇数,如果有 \(f_(a_i)=f_(a_j)\),那么一定有 \(a_i\)\(a_j\) 互为倍数关系。而 \(\frac{N}{2}\) 个数的 \(f(x)\) 的取值只有 \(\frac{N}{4}\) 种,所以 \(S\) 中的元素个数不会超过 \(\frac{N}{4}\)

代码:

int n,a[N],id[N],rd[N],ans,jie[N],invjie[N];
vi e[N],v,S,T;
int ts[N],cnt[M],f[M][N],sum;
bool vis[N];

inline int ksm(int a,int b,int mod){
    int res=1;while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}return res;
}
inline int inv(int a){return ksm(a,mod-2,mod);}

inline void dfs(int k){
    vis[k]=1;v.pb(k);
    for(int to:e[k])if(!vis[to]){
        // printf("k=%d to=%d\n",k,to);
        dfs(to);
    }
}
inline int DP(){
    for(int x:v){
        // printf("x=%d ",x);
        if(!id[x]) S.pb(a[x]);else T.pb(a[x]);
    }
    // puts("");
    if(T.empty()){
        S.clear();return -1;
    }
    rep(i,0,(int)T.size()-1){
        int nows=0;
        rep(j,0,(int)S.size()-1){
            // printf("T[%d]=%d S[%d]=%d\n",i,T[i],j,S[j]);
            if(T[i]%S[j]==0) nows|=(1<<j);
        }
        cnt[nows]++;ts[i]=nows;
        // printf("ts[%d]=%d\n",i,nows);
    }
    rep(i,0,(int)S.size()-1){
        rep(j,0,(1<<((int)S.size()))-1){
            if((j>>i)&1) cnt[j]+=cnt[j^(1<<i)];
        }
    }
    // printf("cnt[1]=%d\n",cnt[1]);
    rep(i,0,(int)T.size()-1){
        f[ts[i]][1]++;
    }
    rep(i,1,(int)T.size()-1)rep(j,0,(1<<((int)S.size()))-1)if(f[j][i]){
        bool op=0;
        rep(k,0,(int)T.size()-1){
            if((ts[k]&j)==0) continue;
            if((ts[k]&j)==ts[k]){
                if(op) continue;
                else{
                    // puts("");
                    f[j][i+1]=(f[j][i+1]+f[j][i]*(cnt[j]-i)%mod)%mod;op=1;
                }
            }
            else f[j|ts[k]][i+1]=(f[j|ts[k]][i+1]+f[j][i])%mod;
        }
    }
    int ans=f[(1<<((int)S.size()))-1][T.size()];
    rep(i,0,(1<<((int)S.size()))-1)rep(j,1,T.size()) f[i][j]=0,cnt[i]=0,ts[j-1]=0;
    sum+=T.size()-1;ans=ans*invjie[T.size()-1]%mod;
    S.clear();T.clear();
    return ans;
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);rep(i,1,n) read(a[i]);
    jie[0]=1;rep(i,1,n) jie[i]=1ll*jie[i-1]*i%mod;
    invjie[n]=inv(jie[n]);dec(i,0,n-1) invjie[i]=invjie[i+1]*(i+1)%mod;
    rep(i,1,n)rep(j,1,n){
        if(i==j) continue;
        if(a[j]%a[i]==0){
            // printf("%d %d\n",i,j);
            e[i].pb(j);id[j]++;rd[i]++;e[j].pb(i);
        }
    }
    ans=1;sum=0;
    // printf("ans=%d\n",ans);
    rep(i,1,n)if(!vis[i]){
        v.clear();dfs(i);
        int nowans=DP();
        if(nowans==-1) continue;
        ans=ans*nowans%mod;
        // printf("nowans=%d\n",nowans);
    }
    ans=ans*jie[sum]%mod;
    printf("%d\n",ans);
    return 0;
}

错误总结:

  • s,t 忘记清空。
  • 组合数贡献算错。

CF1290F

因为是凸包,所以凸包的形状仅和每个向量的个数有关系,设 \(c_i\) 表示第 \(i\) 个向量选的次数,那么我们可以转化限制如下:

\[\begin{aligned} \sum\limits_{x_i\ge 0}c_ix_i&=\sum\limits_{x_i<0} c_ix_i\\ \sum\limits_{y_i\ge 0}c_iy_i&=\sum\limits_{y_i<0} c_iy_i\\ \sum\limits_{x_i\ge 0}c_ix_i&\le m\\ \sum\limits_{y_i\ge 0}c_iy_i&\le m \end{aligned} \]

考虑 \(m\) 很大,但是 \(n\)\(x_i\) 都很小,这个时候我们要考虑数位 DP,每当出现 \(\sum c_ia_i\le m\) 的时候一定要注意是不是数位 DP。

因为有进位,所以我们从小到大考虑填什么,在二进制下考虑,因为这样枚举 \(n\) 个数某一位填什么的复杂度会小很多。设 \(f_{i, px,py,nx,ny,0/1,0/1}\) 表示考虑了前 \(i\) 位,正数 \(x\) 的进位,正数 \(y\) 的进位,负数 \(x\) 的进位,负数 \(y\) 的进位,正数 \(x\) 的前 \(i\) 位是否小于 \(m\),正数 \(y\) 的前 \(i\) 位是否小于 \(m\)

int n,m,x[N],y[N],f[31][M][M][M][M][2][2];

inline int ge(int a,int b,int c){
    if(a!=b) return (a<b)?0:1;return c;
}
inline int dfs(int p,int px,int py,int nx,int ny,int limx,int limy){
    if(p==30) return (!px&&!py&&!nx&&!ny&&!limx&&!limy);
    int &val=f[p][px][py][nx][ny][limx][limy];if(val!=-1) return val;
    val=0;int dm=(m>>p)&1;
    rep(i,0,(1<<n)-1){
        int npx=px,npy=py,nnx=nx,nny=ny;
        rep(j,0,n-1){
            if((i>>j)&1){
                (x[j]>0)?npx+=x[j]:nnx-=x[j];
                (y[j]>0)?npy+=y[j]:nny-=y[j];
            }
        }
        int dnpx=npx&1,dnpy=npy&1,dnnx=nnx&1,dnny=nny&1;
        if(dnpx==dnnx&&dnpy==dnny){
            val=(val+dfs(p+1,npx>>1,npy>>1,nnx>>1,nny>>1,ge(dnpx,dm,limx),ge(dnpy,dm,limy)))%mod;
        }
    }
    return val;
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(m);rep(i,0,n-1) read(x[i]),read(y[i]);
    mset(f,-1);
    int ans=dfs(0,0,0,0,0,0,0)-1;
    printf("%d\n",(ans+mod)%mod);
    return 0;
}

数位 DP,如果不牵扯到进位,那么从大往小 DP 易做限制,否则的话就从小往大做。

CF1286F

考虑所有操作 \(2\),如果在所有被操作的数之间连边,那么一定没有环,否则还不如全用操作一,所以一定森林。现在我们要把整个集合分成若干个孤立点和尽可能多的森林,答案就是总个数减去树的个数。

假设我们能判断一个集合 \(s\) 是否能够形成一颗树,那么这个问题就解决了。设 \(f_s\) 表示集合 \(s\) 中最多的树的数量,枚举子集转移即可。

需要减枝,所以我们用自己更新别人。也就是说,我们枚举一个集合 \(T\),满足 \(T\) 能形成一颗树且 \(f_T=0\),如果 \(T\) 不能分裂成更小的集合,那么首先枚举到 \(T\) 其 DP 数组值一定为 \(0\)。然后枚举所有包含它的集合。

至于孤立点,我们其实并没有理睬他们,他们的 DP 数组永远是 \(0\)

现在考虑 Check,我们可以把整棵树黑白染色,染成二分图,而左部点点权和与右部点点权和的差值的绝对值一定是小于等于树的大小减 \(1\) 的,而且奇偶性和树的大小减 \(1\) 相同,所以现在我们就考虑如何把一个集合分成两个部分,一个想法是直接枚举,不过复杂度太高。

采用折半搜索,搜索过程中把两边可能的集合差值从小到大排序,然后双指针看是否有一对数满足条件。

值得注意的是,如果集合中所有点的点权和小于等于树的大小减 \(1\),那么这种做法有可能把整棵树划分到一个集合上去,实现中,我们有一个 \(nd\) 表示我们至少需要看到多少种这种答案,如果满足上述条件,把 \(nd\) 从原来的 \(1\) 改成 \(3\) 即可。

可以证明,不管左部点和右部点的个数是多少,只要不为 \(0\),都可以构造出对应的树来。

int n,a[N],f[M],lg[M],b[N],bt,sum[M];

inline vi Get(int nl,int nr){
    vi v;v.clear();
    if(nl==nr){
        if(b[nl]>0){v.pb(-b[nl]);v.pb(b[nl]);}
        else{v.pb(b[nl]);v.pb(-b[nl]);}return v;
    }
    v=Get(nl+1,nr);
    vi v1,v2;v1.clear();v2.clear();
    rep(i,0,(int)v.size()-1) v1.pb(v[i]-b[nl]);
    rep(i,0,(int)v.size()-1) v2.pb(v[i]+b[nl]);
    vi now;now.clear();
    int l=0,r=0;
    while(l<(int)v1.size()&&r<(int)v2.size()){
        if(v1[l]<v2[r]) now.pb(v1[l]),l++;
        else now.pb(v2[r]),r++; 
    }
    // printf("now.size()=%d l=%d r=%d\n",now.size(),l,r);
    while(l<(int)v1.size()) now.pb(v1[l]),l++;
    while(r<(int)v2.size()) now.pb(v2[r]),r++;
    // printf("v1.size()=%d v2.size()=%d\n",v1.size(),v2.size());
    // printf("now.size()=%d\n",now.size());
    return now;
}
inline bool Check(int S){
    if(__builtin_popcount(S)==1){
        if(sum[S]==0) return 1;return 0; 
    }
    int siz=__builtin_popcount(S)-1;
    if((sum[S]-siz)&1) return 0;bt=0;
    rep(i,0,n-1) if((S>>i)&1) b[++bt]=a[i+1];int mid=(1+bt)>>1;
    vi L=Get(1,mid),R=Get(mid+1,bt);
    // printf("mid=%d\n",mid);
    int l=R.size(),r=R.size()-1;
    // puts("L:");for(int x:L) printf("%d ",x);puts("");
    // puts("R:");for(int x:R) printf("%d ",x);puts("");
    int nd=1+(abs(sum[S])<=siz)*2;
    // printf("nd=%d\n",nd);
    for(int i=0;i<L.size();i++){
        while(l&&L[i]+R[l-1]>=-siz) l--;
        while(r>=0&&L[i]+R[r]>siz) r--;
        // printf("l=%d r=%d\n",l,r);
        nd-=min(nd,r-l+1);
        // if(l<=r&&r!=R.size()) return 1;
        // if(r==-1) return 0;
    }
    return (nd==0);
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);rep(i,1,n) read(a[i]),lg[1<<i]=i;
    rep(i,1,n) sum[1<<(i-1)]+=a[i];
    rep(i,0,n-1)rep(j,0,(1<<n)-1) if((j>>i)&1) sum[j]+=sum[j^(1<<i)];
    lg[1]=0;
    // printf("Check(7)=%d\n",Check(7));
    for(int T=0;T<(1<<n);T++){
        if(!f[T]&&Check(T)){
            // puts("Now");
            int t=T^((1<<n)-1);
            for(int S=t;;S=(S-1)&t){
                // printf("T|S=%d T=%d S=%d\n",T|S,T,S);
                cmax(f[T|S],f[S]+1);
                if(!S) break;
            }
        }
        // printf("f[%d]=%d\n",T,f[T]);
    }
    printf("%lld\n",n-f[(1<<n)-1]);
    return 0;
}

如果需要卡常,那么用自己更新别人加上减枝可能是一个很好的方法,因为如果转移 \(f_{to}\to f_{k}\)\(f_{to}\) 不合法,那么用自己更新别人只需要判一次,但是用别人更新自己需要判 \(f_{to}\) 用到的次数次。

CFRound 819E

这个题没做出来真的是比较失败,感觉就差一点。

首先考虑环的种类,一共是有 \(3\) 中,分别为:\((i),(i,j),(i,j,i+1,j+1)\),场上最后一种没有想出来,考虑没有其它的环,可以简单尝试难以构造出长度为 \(3\) 和长度大于 \(4\) 的合法的环。

考虑计算贡献,不妨枚举最后一种环的个数,设为 \(s\),那么首先要从 \(n\) 个点里选出 \(2s\) 个互不相邻的点,考虑每一个这样的方案数都等价于我们从 \(n-2s\) 中选 \(2s\) 个点,然后在每个选出点后面加一个点。于是方案数就是 \(\binom{n-2s}{2s}\),考虑接下来我们把选出的点任意排列,前一个与后一个配对,这样每个方案数都被算重了 \(s!\) 次,所以总方案数为 \(\binom{n-2s}{2s}\frac{2s!}{s!}\),剩下的点都是用第一种和第二种环,考虑设 \(I_k\)\(k\) 个点的方案数,显然有:\(I_k=I_{k-1}+(k-1)I_{k-2}\),所以最后总的方案数为:

\[\sum\limits_{s}\binom{n-2s}{2s}\frac{2s!}{s!}I_{n-4s} \]


int t,n,jie[N],invjie[N],I[N];

inline int ksm(int a,int b,int mod){
    int res=1;while(b){if(b&1)res=1ll*a*res%mod;a=1ll*a*a%mod;b>>=1;}return res;
}
inline int inv(int a){return ksm(a,mod-2,mod);}
inline int C(int n,int m){
    if(n<m) return 0;return jie[n]*invjie[m]%mod*invjie[n-m]%mod;
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(t);
    jie[0]=1;rep(i,1,Len) jie[i]=jie[i-1]*i%mod;
    invjie[Len]=inv(jie[Len]);dec(i,0,Len-1) invjie[i]=invjie[i+1]*(i+1)%mod;
    I[1]=1;I[0]=1;rep(i,2,Len) I[i]=(I[i-1]+(i-1)*I[i-2]%mod)%mod;
    while(t--){
        read(n);
        int ans=0;
        rep(i,0,n/4){
            int nowans=C(n-2*i,2*i)*jie[2*i]%mod*invjie[i]%mod;
            ans=(ans+nowans*I[n-i*4]%mod)%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

P3911

\(cnt_i\) 表示 \(i\) 出现的次数,于是这个题就变得经典了。

HDU5328

有:\(F(n)=F(n-1)+2n-1+G(n-1)\),我们考虑计算 \(G(n)\)

\[\begin{aligned} G(n)=\sum\limits_{i=1}^n\sum\limits_{j=1}^n [\gcd(i,j)+\mathrm{lcm}(i,j)= n]\\ =\sum\limits_{d} \sum\limits_{i=1}^{\left\lfloor\frac{n}{d} \right\rfloor}\sum\limits_{j=1}^{\left\lfloor\frac{n}{d} \right\rfloor}[\gcd(i,j)=1][ijd+d=n] \end{aligned} \]

考虑第二个限制条件等价于 \(ij=\frac{n}{d}-1\),而我们枚举的 \(i,j\) 的上界都是严格大于这个值的,所以等价于枚举所有的 \(i,j\)。因此,设 \(H(n)=\sum_{i|n}[\gcd(i,\frac{n}{i})=1]\),则原式相当于:

\[G(n)=\sum\limits_{d|n}H(\frac{n}{d}-1) \]

其中,\(H(n)\) 相当于 \(2\)\(n\) 质因子个数次幂,这是因为想要互质,\(i\) 如果选择了一种质数,就必须拿完。而 \(G\) 可以调和级数的计算。

CF1279F

考虑设 DP \(f_{i,j}\) 表示前 \(j\) 个数放了 \(i\) 个区间,其中最后一个区间的结尾为 \(j\)\(1\)\(0\) 的个数最多是多少,考虑 \(f_{i,j}\) 关于设 \(f(k)\) 表示放了 \(k\) 个区间得到的结果,那么 \(f(k)\) 是一个凸函数,我们利用 \(wqs\) 二分可以优化这道题。

int n,K,l,a[N],cnt[N][2],f[N],g[N],Ans,maxx,maxxid;
string s;

//最大化 id
inline bool DP(int id,int mid){
    // printf("id=%d mid=%d\n",id,mid);
    rep(i,1,n) f[i]=0,g[i]=0;
    maxx=-INF;
    rep(i,0,l-1) f[i]=cnt[i][id];
    cmax(maxx,f[0]-cnt[0][id^1]);
    maxxid=0;
    rep(i,l,n){
        f[i]=cnt[i-l][id]+maxx-mid+l;
        g[i]=maxxid+1;
        // cmax(maxx,f[i-l+1]-cnt[i-l+1][id^1]);
        if(maxx<f[i-l+1]-cnt[i-l+1][id]){
            maxx=f[i-l+1]-cnt[i-l+1][id];
            maxxid=g[i-l+1];
        }
    }
    // rep(i,0,n){
        // printf("f[%d]=%d g[%d]=%d\n",i,f[i],i,g[i]);
    // }
    maxx=-INF;maxxid=-1;
    rep(i,0,n){
        if(maxx<f[i]+cnt[n][id]-cnt[i][id]){
            maxx=f[i]+cnt[n][id]-cnt[i][id];maxxid=g[i];
        }
    }
    // printf("maxx=%d maxxid=%d\n",maxx,maxxid);
    return maxxid<=K;
}
inline void Solve(int id){
    int L=0,R=n;
    while(L<R){
        int mid=(L+R)>>1;
        if(DP(id,mid)) R=mid;else L=mid+1;
    }
    DP(id,L);cmax(Ans,maxx+L*K);
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(K);read(l);cin>>s;int lens=s.length()-1;
    rep(i,0,lens) if(s[i]>='a'&&s[i]<='z') a[i+1]=0;else a[i+1]=1;
    if(K*l>=n){
        puts("0");return 0;
    }
    rep(i,1,n){
        cnt[i][0]=cnt[i-1][0];cnt[i][1]=cnt[i-1][1];
        cnt[i][a[i]]++;
    }
    Solve(0);
    // printf("Ans=%d\n",Ans);
    Solve(1);
    // printf("Ans=%d\n",Ans);
    printf("%lld\n",n-Ans);
    return 0;
}

/*
+ 统计答案出错
+ 应为 L*K 而非 L*maxxid
+ 预处理出错
+ 忘开 long long
*/

CF1276D

考虑删点序列计数等价于删点方案计数,两个方法不同当且仅当存在至少一条边选择的点不同。因此可以考虑 DP。

一个点有 \(4\) 中状态,分别为:没有被删,在遇到父亲之前被删,在遇到父亲是被删,在遇到父亲之后被删。直接 DP 转移即可。

转移举一个例子,例如考虑 \(f_{k,1}\) 的转移,首先枚举 \(v\) 表示 \(k\) 是由通过 \(v\) 删掉的,那么 \(v\) 一定没有被删,所以 \(v\) 的状态为 \(0,3\),前面点的状态只可能是 \(1,2\),后面点的状态只可能是 \(0,1,3\),所以需要预处理前缀和后缀积。

int n,f[N][4],pre[N],suf[N];
vc<P> e[N];

inline void dfs(int k,int fa){
    for(P to:e[k])if(to.se!=fa){dfs(to.se,k);}
    rep(i,0,(int)e[k].size()-1) pre[i]=suf[i]=0;
    int id=-1;rep(i,0,(int)e[k].size()-1)if(e[k][i].se==fa){id=i;break;}
    int faid=e[k][id].fi;
    e[k].erase(e[k].begin()+id);
    if(e[k].size()==0){
        f[k][0]=1;f[k][2]=1;return;
    }
    rep(i,0,(int)e[k].size()-1){
        if(i==0) pre[i]=(f[e[k][i].se][1]+f[e[k][i].se][2])%mod;
        else pre[i]=1ll*pre[i-1]*((f[e[k][i].se][1]+f[e[k][i].se][2])%mod)%mod;
    }
    suf[e[k].size()]=1;
    dec(i,0,(int)e[k].size()-1){
        suf[i]=1ll*suf[i+1]*((f[e[k][i].se][0]+f[e[k][i].se][1]+f[e[k][i].se][3])%mod)%mod;
    }
    rep(i,0,(int)e[k].size()-1){
        int nowans=1;
        if(i!=0) nowans=nowans*pre[i-1]%mod;
        if(i!=(int)e[k].size()-1) nowans=nowans*suf[i+1]%mod;
        nowans=nowans*(f[e[k][i].se][0]+f[e[k][i].se][3])%mod;
        if(e[k][i].fi<faid){
            f[k][1]=(f[k][1]+nowans)%mod;
        }
        else{
            f[k][3]=(f[k][3]+nowans)%mod;
        }
    }
    f[k][0]=pre[(int)e[k].size()-1];
    f[k][2]=1;
    rep(i,0,(int)e[k].size()-1){
        if(e[k][i].fi<faid){
            f[k][2]=f[k][2]*(f[e[k][i].se][1]+f[e[k][i].se][2])%mod;
        }
        else{
            f[k][2]=f[k][2]*(f[e[k][i].se][0]+f[e[k][i].se][1]+f[e[k][i].se][3])%mod;
        }
    }
    // rep(i,0,3) printf("f[%d][%d]=%d\n",k,i,f[k][i]);
    return;
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);
    rep(i,1,n-1){
        int u,v;read(u);read(v);e[u].pb(mp(i,v));e[v].pb(mp(i,u));
    }
    e[1].pb(mp(0,0));
    rep(i,1,n) sort(e[i].begin(),e[i].end());
    dfs(1,0);
    int ans=0;
    ans=(f[1][0]+f[1][1]+f[1][3])%mod;
    printf("%lld\n",ans);
    return 0;
}

CF1225G

考虑如果能有一组答案,那么一定满足能有一组 \(p_i\) 使得 \(\frac{a_i}{k^{p_i}}\) 全部加起来等于 \(1\)。这是相互等价的。

于是我们可以有一组 \(DP\),设 \(f_{S,x}\) 表示用集合 \(S\),能不能把 \(x\) 表示出来,转移有两种,一种是加一个数,另一种是除以 \(k\),注意着里加一个 \(x\) 是需要枚举最后一个加的是哪一个,因为加入集合的顺序是需要考虑的。可以用 bitset 进行加速。

int n,k,a[N],sum,b[N];
bitset<2001> f[N];
priority_queue<P > q;

inline void ge(int s,int x){
    if(!s) return;
    if(x*k<=2000&&f[s][x*k]==1){
        rep(i,0,n-1) if((s>>i)&1) b[i+1]++; 
        ge(s,x*k);return;
    }
    rep(i,1,n) if(((s>>(i-1))&1)&&x>=a[i]){
        if(f[s^(1<<(i-1))][x-a[i]]){ge(s^(1<<(i-1)),x-a[i]);return;}
    }
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(k);rep(i,1,n) read(a[i]);f[0][0]=1;
    rep(i,1,n) sum+=a[i];
    for(int i=0;i<(1<<n);i++){
        rep(j,1,n) if((i>>(j-1))&1){
            // puts("Here");
            // cout<<f[i^(1<<(j-1))]<<endl;
            // printf("a[j]=%d\n",a[j]);
            f[i]|=(f[i^(1<<(j-1))]<<a[j]);
        }
        dec(j,1,sum/k){
            // f[i][j]=f[i][j*k];
            if(f[i][j*k]){
                // printf("j=%d\n",j);
                f[i][j]=1;
            }
        }
        // printf("i=%d:\n",i);
        // cout<<f[i]<<endl;
    }
    if(!f[(1<<n)-1][1]){puts("NO");return 0;}
    else{
        puts("YES");
        ge((1<<n)-1,1);
        // rep(i,1,n) printf("b[%d]=%d\n",i,b[i]);
        rep(i,1,n) q.push({b[i],a[i]});
        while(q.size()>1){
            P n1=q.top();q.pop();
            P n2=q.top();q.pop();
            printf("%d %d\n",n1.se,n2.se);
            n1.se+=n2.se;
            while(n1.se%k==0){
                n1.se/=k;n1.fi--;
            }
            q.push(n1);
        }
    }
    return 0;
}

犯的错误:

  • DP 数组第二种转移赋值出现错误。
  • 递归没设置结束条件
posted @ 2023-07-13 21:34  NuclearReactor  阅读(10)  评论(0编辑  收藏  举报