河南萌新联赛2024第(三)场题解

先给出比赛链接:
https://ac.nowcoder.com/acm/contest/87865

B 正则表达式

按题意取出四个数判断是否满足x[0,255]

Show Code B

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n , ans = 0;
    cin >> n;
    for (int k = 1; k <= n; ++ k) {
        bool flag = 1;
        string s , str = "";
        cin >> s;
        int len = s.size() , cur = 0;
        vector< int > a(4);
        for (int i = 0; i < len; ++ i) {
            if (s[i] != '.') {
                str += s[i];
            }
            if (s[i] == '.' || i == len - 1) {
                a[cur] = stoi(str);
                str = "";
                cur ++;
            }
        }
        for (int i = 0; i < 4; ++ i) {
            if (a[i] > 255 || a[i] < 0) flag = 0;
        }
        if (flag) ans ++;
    }
    cout << ans << "\n";
}




C Circle

题目问n个圆能把平面分割成几个区域

在已n个圆分割出f(n)个区域的情况下,再画一个圆要使增加的区域最多。
则最优方案一定是新圆穿过这n个圆每个两次
dnd2dn=2n,nndnf(n)+dn=f(n+1)
f(n)dnn+2
nSn=na1+n(n2)d/2
f(n)=n2n+2

注意n=0时,也算有一个区域,特判即可

Show Code C

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    auto f = [&](ll n) {
        if (n) {
            return n * n - n + 2;
        } else {
            return 1ll;
        }
    };
    int tt = 1;
    cin >> tt;
    while (tt--) {
        ll n;
        cin >> n;
        cout << f(n) << " ";
    }
} 




E 区间

线段树求区间最大子段和板子题
只需将原数组状态为白色时赋值为1,黑色赋值为1e9,根据数据范围连续的白色对应的子段和一定不会超过1e9,即一个黑色就能截断白色,这样算区间内连续的白色,就能转化为算区间内连续的子段和了

线段树求区间最大子段和板子在此不过多赘述

Show Code E

struct info {
    int l , r;
    ll sum , lmss , rmss , mss;
};
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    auto ls = [&](int x) { return x << 1; };
    auto rs = [&](int x) { return x << 1 | 1; };
    int n , q , op , l , r , x;
    cin >> n >> q;
    vector< ll > a(n + 2 , 1);
    vector< info > tr(4 * n + 4);
    auto re = [&](int x) { 
        if (a[x] == 1) {
            a[x] = -1e9;
        } else {
            a[x] = 1;
        }  
    };
    auto pushup = [&](info & t , info l , info r) {
        t.sum = l.sum + r.sum;
        t.lmss = max(l.lmss , l.sum + r.lmss);
        t.rmss = max(r.rmss , r.sum + l.rmss);
        t.mss = max(max(l.mss , r.mss) , l.rmss + r.lmss);
    };
    auto build = [&](auto self , int l , int r , int p) -> void {
        tr[p] = {l , r , a[l] , a[l] , a[l] , a[l]};
        if (l == r) return ;
        int mid = (l + r) >> 1;
        self(self , l , mid , ls(p));
        self(self , mid + 1 , r , rs(p));
        pushup(tr[p] , tr[ls(p)] , tr[rs(p)]);
    };
    auto change = [&](auto self , int u , int x , int w) -> void {
        if (tr[u].l == tr[u].r) {
            tr[u] = {x , x , w , w , w , w};
            return ;
        }
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (x <= mid) {
            self(self , ls(u) , x , w);
        } else {
            self(self , rs(u) , x , w);
        }
        pushup(tr[u] , tr[ls(u)] , tr[rs(u)]);
    };
    auto query = [&](auto self , int u , int l , int r) {
        if (l <= tr[u].l && tr[u].r <= r) return tr[u];
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (r <= mid) return self(self , ls(u) , l , r);
        if (l > mid) return self(self , rs(u) , l , r);
        info T;
        pushup(T , self(self , ls(u) , l , mid) , self(self , rs(u) , mid + 1 , r));
        return T;
    };
    build(build , 1, n , 1);
    while (q--) {
        cin >> op;
        if (op == 1) {
            cin >> x;
            re(x);
            change(change , 1 , x , a[x]);
        } else {
            cin >> l >> r;
            info ansinfo = query(query , 1 , l , r);
            ll ans = ansinfo.mss;
            ans = max(ans , 0ll);
            cout << ans << "\n";
        }
    }
}




F 累加器

存在一个累加器,每次可以对一个数进行加 1 操作,此时在二进制下有一些位发生改变,对一个数 x 进行 y 次累加操作,总共有多少位发生改变。

我们只需算出0y变化的次数再减去0x变化的次数再相减就行
可以发现第n位的数01变化的循环是2n
设第n位的变化次数为f(n)
当f(n)为奇数时,f(n+1)则为(f(n)-1) / 2
当f(n)为偶数时,f(n+1)则为f(n) / 2
归纳可得f(n+1)为f(n)整除2
而f(1)可以知道就是本身
那么我们就能O(log(n))算出0y变化的次数和0x变化的次数
这个复杂度是可以容忍的

Show Code F

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    auto bitprefix = [&](ll n) { // 拆位前缀和
        vector< ll > res(64);
        res[0] = n;
        for (int i = 1; i <= 61; ++ i) {
            res[i] = res[i - 1] / 2ll;
        }
        return res;
    };
    auto query = [&](ll l , ll r) {
        ll res = 0;
        vector< ll > sl = bitprefix(l);
        vector< ll > sr = bitprefix(r);
        for (int i = 0; i <= 61; ++ i) {
            res += (sr[i] - sl[i]);
        }
        return res;
    };
    int tt = 1;
    cin >> tt;
    while (tt--) {
        int x , y;
        cin >> x >> y;
        ll ans = query(x , x + y);
        cout << ans << "\n";
    }
}




G 求值

a,b,c,n,w,x+y+z=n,x,y,zN
使|xa+yb+zcw|

abc

xa+yb+zc
na+y(ba)+z(ca)
由于n最大是1e6
那么我们就能遍历y或者z
但是遍历y再遍历z的复杂度达到了O(n2)
这是不可以容忍的
有没有什么办法可以优化呢?
可以想到二分优化掉一重循环,
但是能否二分呢?
我们可以拆掉绝对值,那么问题就转化成了
xa+yb+zcwwxa+yb+zc

nanawnaw

na<w,z
f(y)=na+y(ba)+z(ca),z,y
可以判断f(z)具有单调性,即,可二分
那么只需要遍历z,再二分y(y[0,nyz])求出最大的小于w的数
使y+1就是最小的大于w的数(可能不存在)
判断一下这两个数哪个数与w最接近即可

Show Code G

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    auto binsearch = [&](ll y, ll maxn, ll m) {
        ll res = 0;
        ll l = 0, r = maxn;
        while (l <= r) {
            ll mid = (l + r) >> 1;
            if (mid * y > m) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        return r;
    };
    int tt = 1;
    cin >> tt;
    while (tt--) {
        ll A, B, C, n, w ,ans = 1e18;
        ll a, b, c;
        cin >> A >> B >> C >> n >> w;
        a = min(min(A , B) , C);
        c = max(max(A , B) , C);
        b = A + B + C - A - C;
        ll ba = b - a;
        ll ca = c - a;
        ll abc = n * a;
        if (abc >= w) {
            ans = abc - w;
        } else { // w > abc
            for (ll z = 0; z <= n; ++ z) {
                ll cur = abc + z * ca;
                ans = min(ans , abs(cur - w));
                ll y = binsearch(ba , n - z , w - cur);
                cur += y * ba;
                ans = min(ans , abs(cur - w));
                if (y < n - z) {
                    cur += ba;
                    ans = min(ans , abs(cur - w));
                }
            }
        }
        cout << ans << "\n";
    }
}




H 魔法

求从(1,1)走到(n,m)所需用的最小魔法次数,(hp需>h)
魔法次数显然具有单调性,可二分使用的最小魔法次数
只需判断当前二分到的魔法次数能否使得到(n,m)点的血量大于h
由于只能往下或者往右走,即当前点的状态不会被后续点影响,完全由之前的点决定,即具有无后效性,满足DP的条件

我们可以设dp[i][j][k]为在(i,j)点时使用k次魔法的的血量
dp到(n,m)点判断一下是否大于h就可写出check

状态转移方程可分为使用魔法和不使用魔法,各自还可以再分为从上面下来和从左边过来

x 表示当前二分到的魔法使用次数
f(i,j,k)=min{aij+min(f(i1,j,k),f(i,j1,k)),使min(f(i1,j,k1),f(i,j1,k1)),使

Show Code H

constexpr int inf = 0x3f3f3f3f;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n, m, h;
    cin >> n >> m >> h;
    vector< vector< int >> a(n + 1, vector< int >(m + 1));
    vector< vector< int >> dp(n + 1, vector< int >(m + 1));
    vector< vector< int >> pre(n + 1, vector< int >(m + 1));
    for (int i = 1; i <= n; ++ i) {
        for (int j = 1; j <= m; ++ j) {
            cin >> a[i][j];
        }
    }
    auto check = [&](int x) {
        int res = 0;
        vector< vector< vector< int >>> dp(n + 1, vector< vector< int >>(m + 1 , vector< int >(n * m + 16 , inf)));
        dp[1][1][0] = 0;
        for (int i = 1; i <= n; ++ i) {
            for (int j = 1; j <= m; ++ j) {
                for (int k = 0; k <= x; ++ k) {
                    dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k] + a[i][j]);
                    dp[i][j][k] = min(dp[i][j][k], dp[i][j - 1][k] + a[i][j]);
                    if (k) {
                        dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k - 1]);
                        dp[i][j][k] = min(dp[i][j][k], dp[i][j - 1][k - 1]);
                    }
                }
            }
        }
        for (int k = 0; k <= x; ++ k) {
            if (dp[n][m][k] < h) {
                res = 1;
            }
        }
        return res;
    };
    int l = 0, r = n * m;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (check(mid)) {
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    cout << l << "\n";
}




I 游戏

最短路板子题

只需把图按状态分为两个,不妨设g[1]表示拿到钥匙后能走的(由于拿到钥匙后,原本不需要钥匙的边也能走,所以g[1]包括了原本不需要钥匙的边),g[0]表示没有钥匙才能走的图,只需分别在g[0]跑一下dij判断能否从1点到达终点;在g[0]跑dij能否从1点跑到k点,在g[1]跑一下能否从k点到达终点
不能则输出-1,能则输出最小值即可

Show Code I

constexpr ll inf = 0x3f3f3f3f3f3f3f3f;
class edge {
public:
    ll v, w;
};
struct node {
    ll dis, u;
    bool operator>(const node& a) const { return dis > a.dis; }
};
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n, m, k;
    cin >> n >> m >> k;
    vector< vector< vector< edge >>> g(2, vector< vector< edge >>(n + 1, vector< edge >(0) ) );
    auto dij = [&](int begin, int end, int op) {
        vector< ll > dis(n + 1 , inf); 
        vector< ll > vis(n + 1);
        priority_queue< node, vector< node >, greater< node > > q;
        dis[begin] = 0;
        q.push({0, begin});
        while (!q.empty()) {
            int u = q.top().u;
            q.pop();
            if (vis[u]) continue;
            vis[u] = 1;
            for (auto ed : g[op][u]) {
                int v = ed.v, w = ed.w;
                if (dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    q.push({dis[v], v});
                }
            }
        }
        return dis[end];
    };
    for (int i = 1; i <= m; ++ i) {
        ll a, b, c, op;
        cin >> a >> b >> c >> op;
        if (op == 1) {
            g[0][a].push_back({b, c});
            g[0][b].push_back({a, c});
        }
        g[1][a].push_back({b, c});
        g[1][b].push_back({a, c});
    }
    ll ans1 = dij(1, n, 0); 
    ll ans2 = dij(1, k, 0) + dij(k, n, 1);
    if (ans1 >= inf && ans2 >= inf) {
        cout << -1 << "\n";
    } else {
        cout << min(ans1, ans2) << "\n";
    }
}




J keillempkill学姐の卷积

按题意模拟即可

Show Code C

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n , m , len;
    cin >> n >> m;
    len = m - n + 1;
    vector< vector< ll >> ck(n + 1 , vector< ll >(n + 1)); // Convolution kernel
    vector< vector< ll >> ma(m + 1 , vector< ll >(m + 1)); // matrix
    vector< vector< ll >> ansl(len , vector< ll >(len)); // matrix
    for (int i = 1; i <= n; ++ i) {
        for (int j = 1; j <= n; ++ j) {
            cin >> ck[i][j];
        }
    }
    for (int i = 1; i <= m; ++ i) {
        for (int j = 1; j <= m; ++ j) {
            cin >> ma[i][j];
        }
    }
    auto mul = [&] (int x , int y) {
        ll res = 0;
        for (int i = 1; i <= n; ++ i) {
            for (int j = 1; j <= n; ++ j) {
                res += ck[i][j] * ma[i + x][j + y];
            }
        }
        return res;
    };
    for (int i = 0; i < len; ++ i) {
        for (int j = 0; j < len; ++ j) {
            ansl[i][j] = mul(i , j);
        }
    }
    for (int i = 0; i < len; ++ i) {
        for (int j = 0; j < len; ++ j) {
            cout << ansl[i][j] << " \n"[j == len - 1];
        }
    }
}




K 暴食之史莱姆

一个数往左边最多能吃掉的个数就是吃掉左边最近的一个比它小的数所吃的个数,往右边同理
要是中间有比它大的
就吃掉那个最近的一个比它小的数从而变成那个数
由于可以随意左右移动,所以当前这个数所能吃的最大数量就是吃左边与吃右边相加
可以用单调栈维护左边最近的一个比当前小的数

Show Code K

constexpr int inf = 0x3f3f3f3f;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n;
    cin >> n;
    vector< int > a(n + 2 , inf);
    vector< int > eat1(n + 2);
    vector< int > eat2(n + 2);
    vector< int > eat(n + 2);
    map< int, int > mp1;
    map< int, int > mp2;
    vector< int > sta1(n + 2);
    vector< int > sta2(n + 2);
    sta1[0] = -1; 
    sta2[0] = -1;
    int top1 = 1, top2 = 1;
    //在单调sta中插入x同时retun插入前的栈顶元素
    auto f = [&](int op, int x) { 
        ll res;
        if (op == 1) {
            while (top1 >= 1 && sta1[top1] > x) {
                top1--;
            }
            res = sta1[top1];
            top1++;
            sta1[top1] = x;
        } else {
            while (top2 >= 1 && sta2[top2] > x) {
                top2--;
            }
            res = sta2[top2];
            top2++;
            sta2[top2] = x;
        }
        return res;
    };
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    mp1[a[1]] = 1;
    sta1[1] = a[1];
    for (int i = 2; i <= n; ++ i) { // left
        int pre = f(1, a[i]);
        if (pre >= 0) {
            eat1[i] = eat1[mp1[pre]] + 1;
        }
        mp1[a[i]] = i;
    }
    mp2[a[n]] = n;
    sta2[1] = a[n];
    for (int i = n - 1; i >= 1; -- i) { // right
        int pre = f(2, a[i]);
        if (pre >= 0) {
            eat2[i] = eat2[mp2[pre]] + 1;
        }
        mp2[a[i]] = i;
    }
    for (int i = 1; i <= n; ++ i) { // all
        eat[i] = eat1[i] + eat2[i];
        cout << eat[i] << " \n"[i == n];
    }
}




L SSH

按题意模拟,可以考虑用map建立字符串与set数组的映射,再对set用count判断是否存在某个字符,可以使代码简洁很多

Show Code L

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int m , n , q;
    cin >> m >> n >> q;
    map< string, string > mp1; // pri , pub
    map< string, set< string >> mp2; // user , ip
    map< string, set< string >> mp3; // pub , user
    for (int i = 1; i <= m; ++ i) {
        string pub , pri;
        cin >> pub >> pri;
        mp1[pri] = pub;
    }
    for (int i = 1; i <= n; ++ i) {
        string ip;
        int k;
        cin >> ip >> k;
        for (int j = 1; j <= k; ++ j) {
            string user;
            int t;
            cin >> user >> t;
            mp2[user].insert(ip);
            for (int i3 = 1; i3 <= t; ++ i3) {
                string pub;
                cin >> pub;
                mp3[pub].insert(user);
            }
        }
    }
    while (q--) {
        string user , ip , pri;
        cin >> user >> ip >> pri;
        string pub = mp1[pri];
        if (mp3[pub].count(user) == 1 && mp2[user].count(ip) == 1) {
            cout << "Yes\n";
        } else {
            cout << "No\n";
        }
    }
}

(PS:菜菜,目前只写了10题的题解)

posted @   Proaes  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示