CF contest 888 problem E 【思维 + 状压 + 中途相遇法(折半搜索)】

来源:互联网 发布:用mac输入英文破折号 编辑:程序博客网 时间:2024/06/05 14:18

传送门
//这道题和LA 2965非常像,不过更简单一点,
//题意就是从n个数中选取一些数使得他们加起来 % m 尽可能的大,
很容易有一种想法就是二进制枚举, 但是2^35次方非常大 , 所以我们需要用到中途相遇法, 即我们二进制枚举前一部分, 把所有可能的和存起来, 然后再二进制枚举后一部分, 对于后一部分每一个我们枚举出来的和, 在前面二分找出离和当前和相加最大并不超过m-1的数(因为我们枚举了0的, 所以一定可以找出来.) , 这样的好处就是我们将复杂度降低到了2^(n/2)log(2^(n-n/2)), 这样我们就可以A这道题了….

AC Code

const int maxn = 2e5+5;int cas=1;int a[40];ll l_sum[maxn];int len;int Find(ll x) {   //二分找小于等于x的第一个数    int l = 1, r = len - 1;    while(l <= r) {        int mid = (l+r) >> 1;        if(l_sum[mid] == x) return x;        else if(l_sum[mid] > x) r = mid - 1;        else l = mid + 1;    }    return l_sum[l-1];}void solve(){    int n,m;    while(~scanf("%d%d",&n,&m)) {        for (int i = 0 ; i < n; i++) {            scanf("%d",&a[i]);        }        int len1 = n / 2; int cnt = 0;        int len2 = n - len1;        for(int i = 0; i < (1<<len1); i++){            ll sum  = 0;            for (int j = 0; j < len1; j++) {                if(i&(1<<j)) {                    sum += a[j];                }            }            l_sum[cnt++] = sum % m;        }        sort(l_sum,l_sum+cnt);        len = unique(l_sum,l_sum+cnt) - l_sum;        ll ans = 0;        for(int i = 0;i < (1<<len2); i++) {            ll sum = 0;            for (int j = 0 ; j < len2 ; j++) {                if(i&(1<<j)) {                    sum += a[len1+j];                }            }            sum %= m;            ans = max(ans,sum+Find(m-1-sum));        }        cout << ans << endl;    }}
阅读全文
0 0
原创粉丝点击