河南萌新联赛2024第(一)场:河南农业大学

A造数

思路:

将n看成二进制,倒着操作将n变为0即可

赛时的想法也是看成二进制,正着从0加到n,乘2就是向前移位,加1就是把0变1,加2就是添一个1...(还是倒着好想些)

 
void solve() {
    int n;
    cin >> n;
    if (n == 0) {
        cout << 0;
        return ;
    }
    if (n == 1) {
        cout << 1;
        return ;
    }
    string s;
    while (n) {
        if (n % 2) s.push_back('1');
        else s.push_back('0');
        n /= 2;
    }
    int ans = s.size() - 1;
    for (int i = 0; i < s.size(); ++i) {
        if (s[i] == '1') ans ++;
    }
    ans --;
    cout << ans;
}
 

B爱探险的朵拉

思路:

首先建图,可能会出现多个环,一种是结尾为自环的,一种是没有自环的章鱼图,可以分别来统计

对于结尾为自环的情况,可以统统建立反边,从出现自环的点开始bfs,求出最长路径,答案为最长路径

对于没有自环的章鱼图,首先要找出环,可以用dfs找出,接着求出环上最长的链,可以让环上的点为起点bfs求出最长链,答案为环的个数+最长链长度

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1), in(n + 1);
    vector<vector<int> > b(n + 1);
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        //  b建反边
        b[a[i]].push_back(i);
        //  in表示是否被指向
        in[a[i]] = 1;
    }
    vector<int> st(n + 1), vis(n + 1);
    int ans = 0;

    //  求出结尾为自环的路径长度
    for (int i = 1; i <= n; ++i) {
        if (a[i] == i) {
            //  bfs求最长的路径,用反边
            queue<PII> q;
            q.push({i, 1});
            vis[i] = 1;
            while (q.size()) {
                auto [u, cnt] = q.front();
                q.pop();
                ans = max(ans, cnt);
                for (auto v:b[u]) {
                    if (vis[v]) continue;
                    vis[v] = 1;
                    q.push({v, cnt + 1});
                }
            }
        }
    }

    //  章鱼图:求出环,求出环上最长链
    for (int i = 1; i <= n; ++i) {
        if (vis[i] || st[i]) continue;
        //  p记录环上的一点
        int p = 0;
        auto dfs = [&] (int u, auto dfs) -> void {
            if (p != 0) return ;
            if (st[a[u]]) {
                p = a[u];
                return ;
            }
            st[a[u]] = 1;
            dfs(a[u], dfs);
            st[a[u]] = 0;
        };
        st[i] = 1;
        //  dfs求出环上一点
        dfs (i, dfs);
        st[i] = 0;
        
        //  g存环上的点
        vector<int> g;
        g.push_back(p);
        int t = a[p];
        vis[p] = 1;
        while (t != p) {
            g.push_back(t);
            vis[t] = 1;
            t = a[t];
        }
        int m = g.size();
        ans = max(ans, m);
        
        //  bfs求环上最长链,分别从环上每个点出发
        queue<PII> q;
        for (auto v:g) {
            q.push({v, 0});
            vis[v] = 1;
            while (q.size()) {
                auto [u, cnt] = q.front();
                q.pop();
                ans = max(ans, m + cnt);
                for (auto z:b[u]) {
                    if (vis[z] || st[z]) continue;
                    q.push({z, cnt + 1});
                    vis[z] = 1;
                }
            }
        }
    }
    cout << ans;
}

C有大家喜欢的零食吗

思路:

经典的二分图匹配问题,直接用匈牙利求

void solve() {
    int n;
    cin >> n;
    vector<vector<int> > ve( 2 * n + 1);
    for (int i = 1; i <= n; ++i) {
        int k;
        cin >> k;
        for (int j = 0; j < k; ++j) {
            int u;
            cin >> u;
            ve[i].push_back(u + n), ve[u + n].push_back(i);
        }
    }
    vector<int> st(2 * n + 1), to(2 * n + 1);
    auto dfs = [&](int u, auto dfs)->bool {
        for (auto v:ve[u]) {
            if (st[v]) continue;
            st[v] = 1;
            if (!to[v] || dfs(to[v], dfs)) {
                to[v] = u;
                return true;
            }
        }
        return false;
    };
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        std::fill(st.begin(), st.end(), 0);
        if (dfs(i, dfs)) ans ++;
    }
    if (ans == n) cout << "Yes";
    else {
        cout << "No\n";
        cout << n - ans;
    }
}
 

D小蓝的二进制询问

思路:

用前缀和来做,求[1, r]的值,先将数转换为二进制

从最高位开始,假设已经统计了当前位前面的1的个数now,若当前位为1,且前面不变,那区间一定包含了这一位为0,后面为全为1的二进制数,若后面的1的个数为k,则有2k个数,且前面1的个数已经知道了,那么要多加贡献now * 2k。对于全为1的二进制数的贡献:一位一位的求,令某一位为1,则这一位的贡献为2k - 1,所有位的加起来就是k * 2k-1

所以枚举到这一位带来的贡献为now * 2k + k * 2k-1

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define PII pair<int, int>
const int N = 1e6 + 5, mod = 998244353, Mod = 1e9 + 7;


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

void solve() {
    int l, r;
    cin >> l >> r;
    string sl, sr;
    l --;
    auto to2 = [](int x) {
        string t;
        while (x) {
            if (x % 2) t.push_back('1');
            else t.push_back('0');
            x /= 2;
        }
        return t;
    };
    auto P = [=] (string x) {
        int now = 0;
        int ans = 0;
        for (int i = x.size() - 1; i >= 0; --i) {
            if (x[i] == '0') continue;
            if (i > 0) ans = (i * ksm(2, i - 1) % mod + ans) % mod;
            ans = (ans + now * ksm(2, i) % mod) % mod;
            now ++;
        }
        ans = (ans + now) % mod;
        return ans;
    };
    sl = to2(l), sr = to2(r);
    int ans = (P(sr) - P(sl) + mod) % mod;
    cout << ans << '\n';

}


signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }

    return 0;
}

E奇妙的脑回路

F两难抉择新编

思路:

枚举,类似质数筛的方法,nlogn

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    int ans = -1, sum = 0;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        sum ^= a[i];
    }
    for (int i = 1; i <= n; ++i) {
        int t = sum ^ a[i];
        for (int j = 1; j <= n / i; ++j) {
            int x = a[i] + j, y = a[i] * j;
            ans = max({ans, t ^ x, t ^ y});
        }
    }
    cout << ans;
}
 

G旅途的终点

思路:

维护当前畅游国家数需要消耗的最少生命力,标记使用了神力的国家

首先是有一个按消耗生命力排序的国家序列,优先对消耗生命力多的国家使用神力

若当前需要消耗的最少生命力不小于m,说明需要减少畅游国家,畅游国家的顺序已经给出了,那就减去最后一个,要更新需要消耗的生命力,首先看是否使用了神力,若使用了神力,说明需要在将一个原本消耗生命力的国家转变为使用神力,那就从已有的国家序列中选择满足条件的,若没有使用神力,则直接从消耗生命力里减去,知道需要消耗的最少生命力小于m

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define PII pair<int, int>
const int N = 1e3 + 5, mod = 998244353;

int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

bool cmp(PII x, PII y) {
    if (x.first != y.first) return x.first > y.first;
    return x.second < y.second;
}

void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    vector<int> a(n);
    multiset<int> se;
    __int128 sum = 0;
    vector<PII> b(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        sum += a[i];
        b[i].first = a[i], b[i].second = i;
    }
    if (k >= n) {
        cout << n;
        return ;
    }
    vector<int> st(n);
    sort(b.begin(), b.end(), cmp);
    for (int i = 0; i < k; ++i) {
        st[b[i].second] = 1;
        sum -= b[i].first;
    }
    if (sum < m) {
        cout << n;
        return ;
    }
    int p = k;
    for (int i = n - 1; i >= 0; --i) {
        if (st[i]) {
            st[i] = 0;
            while (p < n && b[p].second >= i) {
                p ++;
            }
            st[b[p].second] = 1;
            sum -= b[p ++].first;
        } else sum -= a[i];
        if (sum < m) {
            cout << i;
            return ;
        }
    }
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T = 1;
//    cin >> T;
    while (T--) {
        solve();
    }

    return 0;
}

H两难抉择

思路:

肯定是操作最大的数,分别判断哪个操作后大即可

void solve() {
    int n;
    cin >> n;
    vector<int> ve(n);
    for (int i = 0; i < n; ++i) cin >> ve[i];
    sort(ve.begin(), ve.end());
    if (ve.back() + n > ve.back() * n) {
        ve.back() += n;
    } else ve.back() *= n;
    int sum = 0;
    for (auto v:ve) sum += v;
    cout << sum;
}

I除法移位

思路:

分子尽可能大即可

void solve() {
    int n, t;
    cin >> n >> t;
    vector<int> a(n + 1);
    int ma = -1;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        ma = max(ma, a[i]);
    }
    if (a[1] == ma) cout << 0;
    else {
        int maa = -1, ans = 0;
        for (int i = n, j = 1; i >= 1 && j <= t; --i, ++j) {
            if (a[i] > maa) {
                maa = a[i], ans = j;
            }
        }
        cout << ans;
    }
}

J最大矩阵匹配

思路:

动态规划,首先把图上下翻转,变成箭头指向左上

f[i][j]表示(i,j)为箭头尾部的箭头最大长度,记f[i - 1][j - 1]为k,如果s[i - k][j]、s[i][j - k]、s[i - k][j - k]都为1,那么(i,j)就可以由f[i - 1][j - 1]延长,即f[i][j] = f[i - 1][j - 1] + 1

这里当(i,j)无法延长(i - 1,j - 1)的箭头时,且k大于2时,(i - 1, j - 1)结尾的最长箭头矩阵中的其他部分全为0,所以(i,j)无法再构成长度大于2的箭头,那就特判下是否可以构成长度为2或1的箭头

#include <bits/stdc++.h>

using namespace std;

//#define int long long
#define PII pair<int, int>
const int N = 4e5 + 5, mod = 998244353;

int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

vector<string> ve;
vector<vector<int> > f, g;
void solve() {
    int n, m;
    cin >> n >> m;
    ve = vector<string> (n + 1);
    for (int i = n; i >= 1; --i) {
        cin >> ve[i];
        ve[i] = ' ' + ve[i];
    }

    int ans = 0;

    f = vector<vector<int> > (n + 1, vector<int> (m + 1));
    g = vector<vector<int> > (n + 1, vector<int> (m + 1));

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            g[i][j] = g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1] + (ve[i][j] == '1');
        }
    }

    auto get = [=] (int x1, int y1, int x2, int y2) {
        return g[x2][y2] - g[x1 - 1][y2] - g[x2][y1 - 1] + g[x1 - 1][y1 - 1];
    };

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            int k = f[i - 1][j - 1];
            if (ve[i - 1][j - 1] == '1' && ve[i - 1][j] == '1' && ve[i][j - 1] == '1' && ve[i][j] == '1') f[i][j] = 2;
            else if (ve[i][j] == '1') f[i][j] = 1;
            if (ve[i][j] == '1' && ve[i][j - k] == '1' && ve[i - k][j] == '1' && get(i - k, j - k, i, j) == 3 * k + 1) {
                f[i][j] = max(f[i - 1][j - 1] + 1, f[i][j]);
            }
            ans = max(ans, f[i][j]);
        }
    }
    cout << ans;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T = 1;
//    cin >> T;
    while (T--) {
        solve();
    }

    return 0;
}

K图上计数(Easy)

思路:

任意删边、合并点,当连通块个数去一半是相乘最大

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define PII pair<int, int>
const int N = 1e6 + 5, mod = 998244353, Mod = 1e9 + 7;

int fa[N];
int find(int x) {
    if (x != fa[x]) fa[x] = find(fa[x]);
    return fa[x];
}
void solve() {
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
    }
    if (n == 0) {
        cout << 0;
        return ;
    }
    if (n == 1) {
        cout << 0;
        return ;
    }
    int a = n / 2, b = n - a;
    cout << a * b;

}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T = 1;
//    cin >> T;
    while (T--) {
        solve();
    }

    return 0;
}

L图上计数(Hard)

思路:

首先是直接tarjan求缩边,然后除去缩边外对所有边进行并查集合并,求出每个连通块的个数后,由于n的范围为4e5,那连通块个数的种类数不超过1e3,就可以用多重背包求合并的连通块个数是否存在

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define PII pair<int, int>
const int N = 4e5 + 5, mod = 998244353;

int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

struct E {
    int u, v;
};
int dfn[N], low[N], ct, fa[N];
struct EE {
    int fa, cnt;
}t[N];
vector<vector<int> > ve;
map<PII, int> mp;
void tarjan(int u)
{
    t[u].fa = u, t[u].cnt = 1;
    low[u] = dfn[u] = ++ct;
    for (auto v : ve[u])
    {
        if (!dfn[v])
        {
            fa[v] = u;
            tarjan(v);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u])
                mp[{u, v}] = mp[{v, u}] = 1;
        }
        else if (v != fa[u])
            low[u] = min(low[u], dfn[v]);
    }
}
int find(int x) {
    if (x != t[x].fa) t[x].fa = find(t[x].fa);
    return t[x].fa;
}

void solve() {
    int n, m;
    cin >> n >> m;
    vector<E> g(m);
    ve = vector<vector<int> > (n + 1);
    for (int i = 0; i < m; ++i) {
        cin >> g[i].u >> g[i].v;
        ve[g[i].u].push_back(g[i].v), ve[g[i].v].push_back(g[i].u);
    }
    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) tarjan(i);
    }
    for (int i = 0; i < m; ++i) {
        if (!mp.count({g[i].u, g[i].v})) {
            int u = find(g[i].u), v = find(g[i].v);
            if (u != v) t[u].fa = v, t[v].cnt += t[u].cnt;
        }
    }
    map<int, int> mpp;
    for (int i = 1; i <= n; ++i) {
        int u = find(i);
        if (i == u) mpp[t[u].cnt] ++;
    }

    vector<int> w;
    for (auto [a, b]:mpp) {
        int k = 1;
        while (k <= b) {
            w.push_back(k * a);
            b -= k;
            k *= 2;
        }
        if (b) {
            w.push_back(b * a);
        }
    }
    vector<int> f(n + 1);
    f[0] = 1;
    for (int i = 0; i < w.size(); ++i) {
        for (int j = n; j >= w[i]; --j) {
            f[j] |= f[j - w[i]];
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; ++i) {
        if (f[i]) {
            ans = max(ans, i * (n - i));
        }
    }
    cout << ans;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T = 1;
//    cin >> T;
    while (T--) {
        solve();
    }

    return 0;
}

 

posted @ 2024-07-17 20:52  bible_w  阅读(36)  评论(0编辑  收藏  举报