首页 > 其他分享 >我们刚刚知道那些题的解法-2

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

时间:2023-07-13 21:46:13浏览次数:47  
标签:DP int sum 那些 se 刚刚 id 解法 mod

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 数组第二种转移赋值出现错误。
  • 递归没设置结束条件

标签:DP,int,sum,那些,se,刚刚,id,解法,mod
From: https://www.cnblogs.com/HeNuclearReactor/p/17552241.html

相关文章

  • 我们刚刚知道那些题的解法-1
    P7735考虑将一条边的贡献记在深度较大的电上,一次修改要改的点太多。想到如果维护每个点的时间,那么一条边是实边当且仅当两个点的时间相同。轻重链剖分,对于一次操作,维护每个节点的时间,维护重链上的所有点的点权。对于一次询问,重边直接把贡献加起来,轻边考虑询问两边的时间,因为轻......
  • 最大公约数和最小公倍数的解法
    最大公约数和最小公倍数的解法什么是最大公约数和最小公倍数?最大公约数(GreatestCommonDivisor,GCD)是指两个或多个整数共有约数中最大的一个。例如,12和18的最大公约数是6,因为它们都可以被6整除,而且没有比6更大的约数。最小公倍数(LeastCommonMultiple,LCM)是指两个或多......
  • 多元融合:流媒体传输网络的全盘解法
    我们在寻找「网络」的全盘解法。音视频数字化在消费领域的红利俨然见顶,而产业级视频应用激活了更多场景下的业务模式。与此同时,音视频客户也从单一的业务需求,趋向于多种业务并行存在的需求。固有的网络能满足新兴的业态吗?延时与成本之间存在区间最优解吗?业务的升级切换如何不再......
  • 2023-7-10 #65 我守着虚构的幻想 那些我珍视的模样
    448QOJ6669Mapa这谁想得到啊??????????????????插出一个模1e9+7下的多项式,保存系数。449CF1456EXOR-ranges感觉挺难的。类似406HDU6358Innocence,我们将每一段拆成\(\log\)个trie上的区间,每一个形如“固定前缀\(S\),长为\(l\)的后缀任选”,并将选择\([l,r]\)内的数改为在这\(\l......
  • 盘点那些 FZQOJ 被 ban 掉的功能
    原链接在【洛谷博客】。目录前言初\(2023\)届(c2023)1.犇犇(卒于2021/07/27)2.一言(和犇犇同时挂的)3.评论(卒于2021/07/28)初\(2024\)届(c2024)1.Python2.7(卒于2022/03/15)2.难度标签(卒于2022/07/21)3.HTML(卒于2023/01/04)初\(2025\)届(c2025)前言身为FZOIer的我们十分的......
  • 那些神奇的转化到坐标或网格上的题目
    [AGC002E]CandyPiles[AGC015E]Mr.AokiIncubator......
  • RNA-seq中的那些统计学问题
    <aname="data-preprocess"><h2>1.数据预处理[<sup>目录</sup>](#content)</h2></a><aname="filt-low-exp-genes"><h3>1.1.过滤低表达的基因[<sup>目录</sup>](#content)</h3></a>>......
  • 盘一盘那些高性能设计的点(一)
    狭义地讲,性能是指软件在尽可能少地占用系统资源的前提下,尽可能高地提高运行速度。谈及性能,我们的关注点不再是软件或者系统的功能,而是在其实现功能过程中所表现出来的资源效率。一、池化思想什么是池化?简单的说就是设置一个公共对象池,对于其中的对象直接复用而不再使用新创建......
  • Qt杂谈5.浅谈Qt程序乱码那些事
    1为啥聊这个?你是否也在为Qt程序乱码的问题发愁?网上查了一大堆文章,十篇文章九篇一样,虽然碰运气把问题解决了,但是下次遇到同样的问题还是无从下手,如果你想从根本上理解并解决这个问题,那么可以看看这篇文章,希望能帮到你。2从代码出发2.1使用的Qt版本本人的测试环境:Qt版本:Desk......
  • 【LeetCode剑指offer#05】回文链表的两种解法+删除链表中间节点(链表的基本操作)
    回文链表给你一个单链表的头节点head,请你判断该链表是否为回文链表。如果是,返回true;否则,返回false。示例1:输入:head=[1,2,2,1]输出:true示例2:输入:head=[1,2]输出:false提示:链表中节点数目在范围[1,105]内0<=Node.val<=9思路将值复制到数组中后用双指针......