CF1103D Professional layer 状压DP

传送门


首先对于所有数求gcd并求出这个gcd含有的质因子,那么在所有数中,只有这一些质因子会对答案产生影响,而且对于所有的数,每一个质因子只会在一个数中被删去。

质因子数量不会超过\(11\),所以考虑暴力的状压DP:设\(f_{i,j,k}\)表示前\(i\)个数中选出\(j\)个数并删去了集合为\(k\)的质因子的最小代价,转移枚举子集并计算转移是否合法(即第\(i\)个数中当前集合质因子的乘积是否超过\(K\)),复杂度\(O(nm3^m)\)

考虑优化:对每一个数将不能对答案造成影响的因子全部丢掉。对于剩下的数相同的一些数,最多只会选按权值从小到大排序之后前质因子个数个产生贡献。

再加上一些奇怪的优化就可以快很多了。时间复杂度\(O(?m3^m)\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<map>
//This code is written by Itst
using namespace std;

#define int long long
inline int read(){
    int a = 0;
    char c = getchar();
    while(!isdigit(c))
        c = getchar();
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return a;
}

inline int gcd(int a , int b){
    int r = a % b;
    while(r){a = b; b = r; r = a % b;}
    return b;
}

#define st first
#define nd second
const int MAXN = 1e6 + 7;
struct zt{
    int num , w;
    bool operator <(const zt a)const{return w < a.w;}
}now[MAXN];
vector < int > in;
int N , K , allGcd , dp[12][1 << 11] , calc[1 << 11];
map < int , vector < int > > lsh;

signed main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    N = read(); K = read();
    for(int i = 1 ; i <= N ; ++i){
        now[i].num = read();
        allGcd = gcd(allGcd , now[i].num);
    }
    for(int i = 1 ; i <= N ; ++i) now[i].w = read();
    if(allGcd == 1) return puts("0") , 0;
    for(int i = 2 ; i <= 1e6 && 1ll * i * i <= allGcd ; ++i)
        if(allGcd % i == 0){
            in.push_back(i);
            while(allGcd % i == 0) allGcd /= i;
        }
    if(allGcd - 1) in.push_back(allGcd);
    int M = in.size();
    for(int i = 1 ; i <= N ; ++i){
        int z = 1;
        for(auto j : in)
            while(now[i].num % j == 0){
                now[i].num /= j;
                z *= j;
            }
        lsh[z].push_back(now[i].w);
    }
    memset(dp , 2 , sizeof(dp));
    dp[0][0] = 0;
    for(auto t : lsh){
        int x = t.st;
        sort(t.nd.begin() , t.nd.end());
        if(t.nd.size() > M) t.nd.resize(M);
        vector < int > num;
        for(int i = 0 ; i < 1 << M ; ++i){
            int times = 1 , p = x;
            for(int j = 0 ; j < M ; ++j)
                if(i & (1 << j))
                    while(p % in[j] == 0){
                        p /= in[j];
                        times *= in[j];
                    }
            num.push_back(times);
        }
        for(auto p : t.nd){
            bool flg = 0;
            for(int i = M - 1 ; i >= 0 ; --i)
                for(int j = 0 ; j < 1 << M ; ++j)
                    if(dp[i][j] <= 1e12){
                        int all = (1 << M) - 1 - j;
                        for(int k = all ; k ; k = (k - 1) & all)
                            if(num[k] <= K)
                                if(dp[i + 1][j | k] > dp[i][j] + p){
                                    dp[i + 1][j | k] = dp[i][j] + p;
                                    flg = 1;
                                }
                    }
            if(!flg) break;
        }
    }
    int minN = 1e18;
    for(int i = 1 ; i <= M ; ++i)
        minN = min(minN , dp[i][(1 << M) - 1] * i);
    if(minN > 1e12) cout << -1;
    else cout << minN;
    return 0;
}

转载于:https://www.cnblogs.com/Itst/p/10472688.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值