2023.8.2学习记录

双向搜索 --- meet in the middle 算法

主要思想:把一个搜索分成两个搜索,分别搜索,最后将结果合并。

暴力搜索的复杂度往往是指数级的,而改用 meet in the middle 算法后复杂度的指数可以减半。

P4799 [CEOI2015 Day2] 世界冰球锦标赛

题目大意:n张球赛门票对应价格(n <= 40),身上有m块钱(m <= 1e18),问有多少种方案看球赛。
最开始考虑暴搜和背包,都会T。
最后还得是双向搜索,把n分成左右两块进行搜索,分别记录所有方案所花费的钱。
最后合并答案:枚举一边的所有方案花费,从另一边找到一个花费,若是左+右<=m,则是一种可行方案。

vector<ll>lf, rg;
int flag;
ll a[50] = { 0 };
ll m;
void dfs(int i, int n, ll sum) {
    if (i == n) {
        if (flag) rg.push_back(sum);
        else lf.push_back(sum);
        return;
    }
    if (sum + a[i + 1] <= m)
        dfs(i + 1, n, sum + a[i + 1]);
    dfs(i + 1, n, sum);
}

void solve() {
    int n;
    cin >> n >> m;
    rep(i, 1, n) cin >> a[i];
    sort(a + 1, a + 1 + n);   //排序优化后面的搜索
    dfs(0, n / 2, 0);
    flag = 1;
    dfs(n / 2, n, 0);
    sort(rg.begin(), rg.end()); //对要进行二分搜索的数组排序
    ll ans = 0;
    for (ll x : lf) {   //
        int id = upper_bound(rg.begin(), rg.end(), m - x) - rg.begin();
        ans += id;
    }
    cout << ans << endl;
}
P5691 [NOI2001] 方程的解数

k1*x1^p1 + k2*x2^p2 +...+ kn*xn^pn = 0,x为未知数,由于(n <= 6, x <= 150),又给了6s,首先想到的肯定是暴搜,但是也还是会TLE。
折半搜索优化,怎么用呢,把原式转化为k1*x1^p1 + k2*x2^p2 +...+ k(n/2)*x(n/2)^p(n/2)=-k(n/2+1)*x(n/2+1)^p(n/2+1)...- k(n - 1)*x(n - 1)*p(n - 1) - kn*xn^pn。
把n分成两次搜,第一次用map存好每一个值,第二次查看有没有该值的相反数即可。

int ans;
int flag;  //标记是第一次还是第二次
int n, x;
int k[7], p[7];
map<int, int>mp;

int ksm(int a, int b) {
    int ret = 1;
    while (b) {
        if (b & 1) {
            ret *= a;
        }
        a *= a;
        b >>= 1;
    }
    return ret;
}

void dfs(int number, int nowber, int sum) {
    if (number == nowber) {
        if (flag) ans += mp[-sum];  //第二次
        else mp[sum]++;   //第一次
        return;
    }
    for (int i = 1; i <= x; i++) {  //枚举x的取值
        dfs(number + 1, nowber, sum + k[number + 1] * ksm(i, p[number + 1]));
    }
}

void solve() {
    cin >> n >> x;
    rep(i, 1, n) cin >> k[i] >> p[i];
    dfs(0, n / 2, 0); // 1
    flag = 1;
    dfs(n / 2, n, 0); // 2
    cout << ans << endl;
}

算是初步了解了一下这个算法。

题解
牛客多校5 -- H

 

题目大意:给定n个奶酪(n <= 200),大小为a,质量为b,有m次机会去拿这些奶酪(m <= 1e5),每次最多拿满sz大小的奶酪(sz递增)。
在奶酪前时,只能拿掉当前奶酪或打洞过去,被打了洞的奶酪不能拿。问m次后最多拿多少质量的奶酪。
思路:当m > n时,只需要考虑后面n次拿奶酪即可,在第每个奶酪前时,只有两种情况,拿或不拿,就是背包问题,
所以易得到dp转移方程为 dp[j][k]=max(dp[j][k], dp[j - 1][k - a[i]] + b[i])。i为第几个奶酪,j是第几次拿,k是第j次拿了多少。

int a[205], b[205];
int n, m;

void solve() {
    int ans = 0;
    cin >> n >> m;
    rep(i, 1, n) cin >> a[i] >> b[i];
    vector<int>sz(m + 1);
    rep(i, 1, m) cin >> sz[i];
    if (m > n) {
        sz.erase(sz.begin() + 1, sz.end() - n);
        m = n;
    }
    vector<vector<int> >dp(m + 1, vector<int>(201));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = sz[j]; k >= a[i]; k--) {   // 背包
                dp[j][k] = max(dp[j][k], dp[j][k - a[i]] + b[i]);
            }
        }
        for (int j = 1; j <= m - 1; j++) {   //处理出下次拿的初始情况
            for (int k = sz[j]; k >= 0; k--)
                dp[j + 1][0] = max(dp[j + 1][0], dp[j][k]);
        }
    }
    for (int i = 0; i <= 200; i++) {
        ans = max(dp[m][i], ans);
    }

    cout << ans << endl;
}
E. Tracking Segments

 题目大意:给你一个长度为n的数组(初始都为0),m个数段,q次更新,每次更新为一个下标,把数组中该下标上的值改为1,每次更新的下标不同。好数段:1的个数严格大于0的个数。问最少在第几次更新后会有至少一个好数段。若q次更新后仍没有好数段,则输出-1。
做法:二分 + 前缀和
二分答案 : 二分最小更新次数得答案。

int inp[N];
PII ds[N];
int n, m, q;

bool check(int mid) {
    vector<int>sum(n + 1, 0);
    for (int i = 1; i <= mid; i++) {  //初始化,前面操作过的也要记上
        sum[inp[i]] = 1;
    }
    for (int i = 1; i <= n; i++) sum[i] += sum[i - 1];  //前缀和,每个区间的1的个数
    for (int i = 0; i < m; i++) {
        int x = ds[i].first, y = ds[i].second;
        if (sum[y] - sum[x - 1] > ((y - x + 1) / 2)) {
            //   cout << "l r : " << x << ' ' << y << endl; 
            return true;
        }
    }
    return false;
}

void solve() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        cin >> ds[i].first >> ds[i].second;
    }
    cin >> q;
    for (int i = 1; i <= q; i++) {
        cin >> inp[i];
    }
    int l = 1, r = q + 1;
    while (l < r) {   //二分
        int mid = l + r >> 1;
        //  cout << "l r : " << l << ' ' << r << endl;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    if (r == q + 1) l = -1;
    cout << l << endl;
}
牛客萌新赛5 --- 幂运算

如果要求解 a ^ b % p , b 的数值非常大的时候, 就需要用到欧拉函数来降幂。
这题需要求解的是2^(2^n)%p, n的范围是le6, 所以要进行降幂。

ll ksm(ll a, ll b, ll p) {
    ll ans = 1;
    while (b) {
        if (b & 1) {
            ans *= a;
            ans %= p;
        }
        a *= a;
        a %= p;
        b >>= 1;
    }
    return ans;
}

ll euler_phi(ll n) {   //欧拉函数
    ll m = sqrtl(n);
    ll ans = n;
    for (ll i = 2; i <= m; i++)
        if (n % i == 0) {
            ans = ans / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    if (n > 1) ans = ans / n * (n - 1);
    return ans;
}

void solve() {
    ll n, p;
    cin >> n >> p;
    ll eul = euler_phi(p);
    ll m = ksm(2, n, eul);
    cout << ksm(2, m, p) << endl;
}
牛客萌新赛5 - F

题目大意:给定n个点(x, y, v坐标和扩散速度),m次查询(在t时刻有多少个点)(n,m,t <= 1e3)
每一个点像圆一样扩散,两个点相遇后会合并为一个点,合并不影响扩散。
思路:计算出每两个点的相遇时间,并记录在每一个时刻都有哪两个相遇,按照时间先后合并相遇的点,得出在该时刻,少了多少个点。
做法:遍历n^2,计算出所有两个点相遇时间,并存好每个时间点有哪些点相遇,遍历0-1000时刻,用并查集进行合并操作,每合并一次,该时刻减少一个点,最后前缀和得出所有时刻有多少个点。

struct node {
    int x, y, v;
}a[1005];

int Dtim(node x, node y) {  //计算两个墨点相遇时间
    double dis = sqrt(sqr(x.x - y.x) + sqr(x.y - y.y));
    if (x.v + y.v == 0) return 1001;   //遇不到
    double t0 = dis / (x.v + y.v);
    return ceil(t0);
}

int t[1005];
int pre[1005];
vector<PII>zz[1005];

int find(int f) {
    if (pre[f] == f) return f;
    return pre[f] = find(pre[f]);
}

void solve() {
    int n, m;
    cin >> n;
    rep(i, 1, n) {
        cin >> a[i].x >> a[i].y >> a[i].v;
        pre[i] = i;
    }
    t[0] = n;
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {  //把每两个相遇的时间算出来
            //把在这个时间相遇的两个点存起来
            int t0 = Dtim(a[i], a[j]);
            if (t0 <= 1000 && t0 >= 0)  //把相遇时间在需要考虑范围的两个墨点存起来
                zz[t0].push_back({ i, j });
        }
    }
    for (int i = 0; i <= 1000; i++) {  //遍历每一个时间点
        for (auto x : zz[i]) { //用并查集把相遇的墨滴合并
            int u = x.first, v = x.second;
            u = find(u), v = find(v);
            if (u != v) {
                t[i]--;
                pre[u] = v;
            }
        }
    }
    for (int i = 1; i <= 1000; i++) { //前缀和处理
        t[i] += t[i - 1];
        if (t[i] == 1) break;   //最终状态是1
    }
    cin >> m;
    while (m--) {
        int inp;
        cin >> inp;
        cout << (t[inp] == 0 ? 1 : t[inp]) << endl;
    }
}

继续加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

akb000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值