2021牛客多校第五场

B:Boxes
概率签到,根据样例找找规律,显然的结论的min(sum, c + x)
减去x后发现好像是 \(\sum_{i=1}^{n-1} \frac {x_i} {2^i}\) 这个样子的,猜猜结论最小的期望,那么不就是先拿花费小的,搞个前缀和好像和样例对的上
有人害我!!!

void run() {
    int n = rd();
    double c, sum = 0; scanf("%lf", &c);
    for(int i = 0; i < n; ++ i) {
        scanf("%lf", &w[i]);
        sum += w[i];
    }
    sort(w, w + n);
    for(int i = 1; i <= n; ++ i)    pre[i] = pre[i - 1] + w[i-1];
    double ans = 0, mul = 2.0;
    for(int i = n - 1; i >= 1; -- i, mul *= 2)  ans += pre[i] / mul;
    printf("%.10lf\n", min(sum, ans /* sum / n*/ + c));
}

D : Double Strings
简单画一画样例就会发现和组合数有关系
对于 \(s1\) 中每一位,枚举 \(s2\) 中每一位,如果该位大于它,显然会产生贡献
贡献的形式是 \(s1_i < s2_j~ -》 ~x * \sum_{k=0}^{min(len1-i, len2-j)} C(len1, k) * C(len2, k)\)
后面的那项明显是一个卷积,有组合数范德蒙特卷积 \(\sum_{i=0}^n C(m1, i)*C(m2, n-i) = C(m1 + m2, n)\)
所以转为 \(\sum_{i=0}^{x} C(x, i)*C(y, i) = \sum C(x, x - i)*C(y, i) = C(x + y, x)\)
而前面的系数是前面 每位相同子序列的个数,画了画样例,发现了递推关系,\(x_{i, j} = x_{i, j - 1} + x_{i - 1, j} - (s1_i != s2_j) * x_{i - 1, j - 1}\)
原理是对于当前项的值,是前项和上一轮的项贡献来的,但是两者有重叠部分,重叠部分是前项的上一轮,而当 \(s1_i = s2_j\) 时,会新增加项,新增加的是当前项 \(s1_i = s2_j\) 必选的一些情况,那么其实就是 前项的上一轮后面加上 \(s1_i = s2_j\),那么就抵消了,不需要减去了,所以我们得到的结论就是求 a中前i个字符和b中前j个字符,相同的子序列个数是 \(dp[i][j] = dp[i][j - 1] + dp[i - 1][j] - (a_i != b_j) * dp[i - 1][j - 1]\)

const int maxn = 1e4 + 10;
const int mod = 1e9 + 7;
int fac[maxn], ifac[maxn], inv[maxn];
inline int C(int n, int m) {
    return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
inline void init() {
    fac[0] = fac[1] = ifac[0] = ifac[1] = inv[1] = 1;
    for(int i = 2; i < maxn; ++ i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
        ifac[i] = ifac[i - 1] * inv[i] % mod;
    }
    return ;
}

//范德蒙特卷积:\sum_{i=0}^n C(m1, i)*C(m2, n-i) = C(m1 + m2, n)
//所以:\sum_{i=0}^{x} C(x, i)*C(y, i) = \sum C(x, x - i)*C(y, i) = C(x + y, x)
inline void qmod(int &a) {
    (a >= mod) && (a -= mod);
}
char s1[maxn], s2[maxn];
int num[2][maxn];

void run() {
    init();
    scanf("%s %s", s1 + 1, s2 + 1);
    int len1 = strlen(s1 + 1), len2 = strlen(s2 + 1);
    int ans = 0;
    for(int i = 0; i <= len2; ++ i) num[0][i] = 1;
    for(int i = 1; i <= len1; ++ i) {
        int id = (i & 1) ^ 1;
        for(int j = 1; j <= len2; ++ j)
            if(s2[j] > s1[i])
                qmod(ans += num[id][j - 1] * C(len1 - i + len2 - j, len1 - i) % mod);

        num[id ^ 1][0] = 1;
        for(int j = 1; j <= len2; ++ j) {
            qmod(num[id ^ 1][j] = num[id ^ 1][j - 1] + num[id][j]);
            if(s2[j] != s1[i]) qmod(num[id ^ 1][j] -= num[id][j - 1] - mod);
        }
    }
    printf("%lld\n", ans);
    return ;
}

signed main() {
    run();
    return 0;
}


D: Greater Integer, Better LCM
题目的意思就是给你两个数,\(x\)\(y\),求最小的大于 \(x,y\)\(x‘, y’\), 满足 \(lcm(x', y') = c\)
显然我们需要枚举每种因子在哪个数中,枚举因子子集的复杂度是 \(2^n\)
然后要求出当前有这个因子的大于 \(x, y\) 的最小数,直接暴力dfs枚举所有情况带上外面枚举子集复杂度会达到 \(3^n\)
主要是递归复杂度太大了,那么考虑减小枚举复杂度,就有折半搜索,将所有因数的相乘处理成两部分,分别排序,那么枚举两者的乘积即可,非递归加上有序剪枝就可以过了

char IO;
template <class T=int> T rd(){
    T s=0; int f=0;
    while(!isdigit(IO=getchar())) f|=IO=='-';
    do s=(s<<1)+(s<<3)+(IO^'0');
    while(isdigit(IO=getchar()));
    return f?-s:s;
}
const int maxn = 1e4 + 10;
const int mod = 1e9 + 7;
inline __int128 ksm(__int128 a, int b) {
    __int128 res = 1;
    while(b) {
        if(b & 1)   res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

void put(__int128 x) {
    if(x > 9) put(x / 10);
    putchar(x % 10 + '0');
}
int n;
__int128 bit[1 << 18];
__int128 a[2], mul[2];
struct node{
    int p, q;
    __int128 pr;
}p[1 << 18];

node ned[2][20]; int cnt[2];
void out(__int128 x, string s) {
    cout << s;
    put(x); puts("");
}

__int128 get(__int128 x, int pos) {
    static __int128 lps[1 << 18], rps[1 << 18], m;
    int cntl = 0, cntr = 0; lps[0] = 1; rps[0] = 1;
    for(int i = 0; i * 2 <= cnt[pos] - 2; ++ i) {
        for(int j = 0, up = cntl; j <= up; ++ j) {
            m = ned[pos][i].p;
            for(int k = 1; k <= ned[pos][i].q; ++ k, m *= ned[pos][i].p)
                lps[++ cntl] = lps[j] * m;
        }
    }
    for(int i = cnt[pos] - 1; i * 2 > cnt[pos] - 2; -- i) {
        for(int j = 0, up = cntr; j <= up; ++ j) {
            m = ned[pos][i].p;
            for(int k = 1; k <= ned[pos][i].q; ++ k, m *= ned[pos][i].p)
                rps[++ cntr] = rps[j] * m;
        }
    }

    sort(lps, lps + cntl + 1);
    sort(rps, rps + cntr + 1);

    __int128 res = lps[cntl] * rps[cntr] - x;
    for(int i = 0, j; i <= cntl; ++ i) {
        for(j = 0; j <= cntr && lps[i] * rps[j] < x; ++ j);
        if(j <= cntr)   res = min(res, lps[i] * rps[j] - x);
    }
    return res;
}

void run() {
    n = rd();
    int limit = (1 << n) - 1; bit[0] = 1;
    for(int i = 0; i < n; ++ i) {
        p[1 << i].p = rd(); p[1 << i].q = rd();
        p[1 << i].pr = ksm((__int128)p[1 << i].p, p[1 << i].q);
    }

    a[0] = rd<__int128>(), a[1] = rd<__int128>();//lcm(a + x, b + y) =
    //for(int i = 1; i <= limit; ++ i)    bit[i] = bit[i - (i & -i)] * p[(i & -i)].pr;
    __int128 ans = -1;
    for(int i = 0; i <= limit; ++ i) {
        //__int128 x = bit[i], y = bit[limit - i];//-> x >= a, y >= b
        cnt[0] = cnt[1] = 0; mul[0] = mul[1] = 1;
        for(int j = 0; j < n; ++ j) {
            if(!((1 << j) & i)) ned[0][cnt[0]++] = p[1 << j]; mul[0] *= p[1 << j].pr;
            else ned[1][cnt[1]++] = p[1 << j]; mul[1] *= p[1 << j].pr;
        }
        __int128 _x = (a[0] + mul[1] - 1) / mul[1] * mul[1];
        __int128 _y = (a[1] + mul[0] - 1) / mul[0] * mul[0];
        __int128 tmp = _x - a[0] + _y - a[1];
        tmp += get(_x / mul[1], 0) * mul[1];
        tmp += get(_y / mul[0], 1) * mul[0];

        if(ans == -1)    ans = tmp;
        else    ans = min(ans, tmp);
    }
    put(ans);
    return ;
}

这道题换个思路,用状压的思维去写的话会比较简单
不考虑每种因子按堆来看,独立来看,那么我们枚举的状态是每种因子的状态
这样我们的状压就可以派上用场了,通过状压预处理出来每个状态大于 \(a\)\(b\)的最小花费
那么接下来再枚举 \(a\) 的每个状态 \(i\)\(b\) 就是 \(limit^i\),那么只需要判断一下这个状态的合法性就行了,合法性就是必须要连续的一种因子要么全拿,要么全不拿

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//#define int long long
#define _int __int128
#define inf 0x3f3f3f3f//1061109567
#define infl 0x3f3f3f3f3f3f3f3f//4557430888798830399
#define _inf _int(infl*2) * _int(infl*2)//83080705224710788855960234521465996804
void put(_int x) {
    if(x > 9) put(x / 10);
    putchar(x % 10 + '0');
}
char IO;
template <class T=int> T rd(){
    T s=0; int f=0;
    while(!isdigit(IO=getchar())) f|=IO=='-';
    do s=(s<<1)+(s<<3)+(IO^'0');
    while(isdigit(IO=getchar()));
    return f?-s:s;
}
#define dbg(x...) \
    do { \
        cout << #x << " -> "; \
        err(x); \
    } while(0)
void err() {
    cout << endl;
}
template<class T, class ...Ts>
void err(const T &arg, const Ts &...args) {
    cout << arg << " ";
    err(args...);
}

const int maxn = 2e5 + 10;
const int mod = 1e9 + 7;
_int bit[1 << 18], fa[1 << 18], fb[1 << 18], a, b;
int c[20], m, mul[1 << 18];
bool check(int s) {
    for(int i = 1; i < m; ++ i)
        if(c[i] == c[i - 1] && (bool(s & (1 << i)) != bool(s & (1 << i - 1))))   return false;
    return true;
}

//
void run() {
    int n = rd();
    for(int i = 0; i < n; ++ i) {
        int p = rd(), q = rd();
        while(q--)  c[m++] = p;
    }
    for(int i = 0; i < m; ++ i) mul[1 << i] = c[i];
    int limit = (1 << m) - 1;   bit[0] = 1;
    memset(fa, 0x3f, sizeof fa);    memset(fb, 0x3f, sizeof fb);
    a = rd<_int>(); b = rd<_int>();
    for(int i = 0; i <= limit; ++ i) {
        if(i)   bit[i] = bit[i - (i & -i)] * mul[(i & -i)];
        if(bit[i] >= a) fa[i] = bit[i] - a;
        if(bit[i] >= b) fb[i] = bit[i] - b;
    }
    for(int i = limit; i >= 1; -- i)
        for(int j = 0; j < m; ++ j) if(i & (1 << j)) {
            fa[i ^ (1 << j)] = min(fa[i], fa[i ^ (1 << j)]);
            fb[i ^ (1 << j)] = min(fb[i], fb[i ^ (1 << j)]);
        }
    _int ans = fb[limit] + fa[limit];
    for(int i = 0; i <= limit; ++ i) {
        if(!check(i))    continue;
        ans = min(ans, fa[i] + fb[limit ^ i]);
    }
    put(ans);    puts("");
}

signed main() {
    run();
    return 0;
}

posted @ 2021-07-31 22:04  wlhp  阅读(9)  评论(0)    收藏  举报