CF 1642 F. Two Arrays 随机 + sosdp

文章目录

传送门

题意

给你 n n n个长度为 m m m的数组,每个数组都有一个价值 w i w_i wi,让你选出两个数组他们没有交集且价值和最大,如果没有输出 − 1 -1 1

2 ≤ n ≤ 1 e 5 , 1 ≤ m ≤ 5 , 1 ≤ a i , j , w i ≤ 1 e 9 2\le n\le 1e5,1\le m\le 5,1\le a_{i,j},w_i\le 1e9 2n1e5,1m5,1ai,j,wi1e9

思路

看到 m m m很小,很容易向状压地方靠,假设 a a a很小,那么这个题就很简单了,我们将每个数组状压成一个二进制,让后 s o s d p sosdp sosdp求一下子集最小值,让后遍历即可获得答案。

但是这个题 a a a高达 1 e 9 1e9 1e9,但是我们发现其最终答案是一对之间,那么我们将 a a a随机映射到 0 − 15 0-15 015之间的某个数,注意同一个数一定映射到相同数,不同数可能映射到相同数,我们发现这样操作后对于答案的两个数组他们都映射到不同数上的概率为 0.0188 0.0188 0.0188,这也是答案正确的概率,虽然这个数很小,但是我们运行 200 200 200次,期望概率就达到 2 2 2以上了,基本可以认为是正确的。

复杂度 P ( 250 ∗ 16 ∗ ( 1 < < 16 ) ) P(250*16*(1<<16)) P(25016(1<<16))

t r i c k : trick: trick遇到很大的数可以将其映射到小范围的数上进行乱搞。

#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define Mid (tr[u].l+tr[u].r>>1)
#define pb push_back
using namespace std;

const int N=500010,INF=0x3f3f3f3f,mod=1e9+7;
typedef long long LL;

int n,m;
int a[N][6],w[N];
int b[N][6];
int f[1<<21];
mt19937 rnd(time(0));
int mp[N],id[N];
vector<int>v;

template <class T>
bool read(T &ret)//输入
{
    char c;
    int sgn;
    T bit=0.1;
    if(c=getchar(), c==EOF)
        return 0;
    while(c!='-' && c!='.' && (c<'0' || c>'9'))
        c=getchar();
    sgn=(c=='-')? -1:1;
    ret=(c=='-')? 0:(c-'0');
    while(c=getchar(), c>='0' && c<='9')
        ret=ret*10+(c-'0');
    if(c==' ' || c=='\n')
    {
        ret*=sgn;
        return 1;
    }
    while(c=getchar(), c>='0' && c<='9')
        ret+=(c-'0')*bit, bit/=10;
    ret*=sgn;
    return 1;
}

int find(int x) {
    return lower_bound(v.begin(),v.end(),x)-v.begin();
}

void solve() {
    read(n); read(m);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            read(a[i][j]);
            v.pb(a[i][j]);
        }
        read(w[i]);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            a[i][j]=find(a[i][j]);
        }
    }
    srand(20000926);
    int ans=2e9+7;
    for(int _=1;_<=250;_++) {
        for(int i=0;i<N;i++) id[i]=rand()%16;
        memset(f,0x3f,sizeof(f));
        for(int i=1;i<=n;i++) {
            int state=0;
            for(int j=1;j<=m;j++) {
                b[i][j]=id[a[i][j]];
                state|=1<<b[i][j];
            }
            f[state]=min(f[state],w[i]);
        }
        int all=1<<16;
        for(int j=0;j<16;j++) {
            for(int i=0;i<all;i++) {
                if(!(i>>j&1)) f[i|(1<<j)]=min(f[i|(1<<j)],f[i]);
            }
        }
        for(int i=0;i<all;i++) {
            int j=(all-1)^i;
            if(f[i]!=0x3f3f3f3f&&f[j]!=0x3f3f3f3f) ans=min(ans,f[i]+f[j]);
        }
    }
    printf("%d\n",ans==2e9+7? -1:ans);
}

int main() {
    LL f1,f2;
    f1=f2=1;
    for(int i=15;i>=15-10+1;i--) f1*=i;
    for(int i=1;i<=10;i++) f2*=15;
    printf("%.10f\n",1.0*f1/f2);
	int _=1;
	while(_--) {
		solve();
	}
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值