天梯赛选拔赛错题分析和补题

比赛链接:https://www.luogu.com.cn/contest/314613#problems
L1-5
1.字符串的输入,如果无空格用cin,有空格用getline,如果前面有cin,后面要用getline的话,要在cin完后,加上cin.ignore()。
2.如果在循环外已经定义了a变量,那么如果在循环内再次定义该变量,就会死循环。

void solve()
{
    string t;
    cin >> n;
    cin.ignore();
    string s1 = "haha", s2 = "hehe", s3 = "[doge]";
    for (int i = 1; i <= n; i++)
    {
        getline(cin, s);
        bool ok = 0, ok2 = 0, ok3 = 1;
        if (s == "")
        {
            cout << "Are you kidding me?" << endl;
            continue;
        }
        for (int j = 0; j < s.size(); j++)
        {
            if (s[j] != ' ')
            {
                ok3 = 0;
                break;
            }
        }
        if (ok3)
        {
            cout << "Are you kidding me?" << endl;
            continue;
        }
        int r1 = s.find(s1), r2 = s.find(s2);
        if (r1 != string::npos || r2 != string::npos)
        {
            ok = 1;
        }
        int len = s.size();
        int r3 = s.find(s3);
        while (r3 != string::npos)
        {
            if (r3 + 6 == len)
            {
                ok2 = 1;
            }
            r3 = s.find(s3, r3 + 1);
        }
        if (ok && ok2)
        {
            cout << s << endl;
        }
        else if (ok && !ok2)
        {
            s = s + ' ' + s3;
            cout << s << endl;
        }
        else
        {
            cout << s << endl;
        }
    }
}

L2-1
用树状数组,前缀和代表有多少个还在,二分查询第一个等于k的地方即为答案

// 树状数组
int tr[M];
int lb(int i)
{
    return i & (-i);
}
void add(int i, int x)
{
    for (; i <= n; i += lb(i))
    {
        tr[i] += x;
    }
}
int sum(int i)
{
    int ans = 0;
    for (; i > 0; i -= lb(i))
    {
        ans += tr[i];
    }
    return ans;
}
int que(int l, int r)
{
    return sum(r) - sum(l - 1);
}
void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        add(i, 1);
    }
    while (m--)
    {
        cin >> k;
        l = 1, r = n;
        int res = n + 1;
        while (l <= r)
        {
            int mid = l + ((r - l) >> 1);
            if (sum(mid) >= k)
            {
                res = min(res, mid);
                r = mid - 1;
            }
            else
            {
                l = mid + 1;
            }
        }
        cout << res << " ";
        add(res, -1);
    }
    cout << endl;
}

L2-2

用线性筛得出1到1e6数的不重复的质因数个数,质数p的质因数个数为1,那么i*p的质因数个数应该为1+i的质因数个数。
因为范围为1e6,2的20次方就大于1e6了,所以质因数很少,所以我们用一个数组来存,adj[i]意为有i个不同质因数的数有哪些。
那么我们可以用二分来计算,upper_bound求第一个大于r的数位置,lower_bound求第一个大于等于l的数的位置,两者相减为个数,若个数为1那么就说明i对答案有贡献,累加可得。

int cnt = 0;
int pri[M];
int vis[M];
int v[M];
void ols(int n)
{
    v[1] = 0;
    for (ll i = 2; i <= n; i++)
    {
        if (!vis[i])
        {
            pri[++cnt] = i;
            v[i] = 1;
        }
        for (ll j = 1; (ll)(i * pri[j]) <= n; j++)
        {
            vis[i * pri[j]] = 1;
            v[i * pri[j]] = 1 + v[i];
            if (i % pri[j] == 0)
            {
                break;
            }
        }
    }
}
vi adj[M];
void solve()
{
    ols(1e6);
    for (int i = 1; i <= 1e6; i++)
    {
        adj[v[i]].push_back(i);
    }
    for (int i = 0; i <= 24; i++)
    {
        sort(all(adj[i]));
    }
    int q;
    cin >> q;
    int cnt = 0;

    while (q--)
    {
        cin >> l >> r;
        int ans = 0;
        for (int i = 0; i <= 24; i++)
        {
            if (adj[i].size() == 0)
                continue;
            int d = upper_bound(all(adj[i]), r) - lower_bound(all(adj[i]), l);
            if (d == 1)
                ans += i;
        }
        cout << ans << endl;
    }
}

L3-1

我们需要求的是在能到达终点的时候,保证这条路上花费最大的那次尽量小,这句话是不是很熟悉,没错,可以使用二分,二分花费,检测mid花费能不能完成到达终点的任务,也就是最大不超过mid花费,并且保证最后到达终点的耗油量少于h就行了,在使用dij的时候,将花费大的边剪掉。

struct node
{
    int to, w;
};
vector<node> adj[M];
void solve()
{
    int h;
    cin >> n >> m >> h;
    vi a(n + 1, 0);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        adj[u].push_back({v, w});
        adj[v].push_back({u, w});
    }

    auto check = [&](int mid) -> bool
    {
        vi ans(n + 1, 1e18);
        priority_queue<pii, vector<pii>, greater<>> p;
        ans[1] = 0;
        if (a[1] > mid)
        {
            return 0;
        }
        p.push({0, 1});
        while (!p.empty())
        {
            auto [c, i] = p.top();
            p.pop();
            for (auto [to, w] : adj[i])
            {
                if (a[to] > mid)
                {
                    continue;
                }
                if (ans[to] > w + c)
                {
                    ans[to] = w + c;
                    p.push({w + c, to});
                }
            }
        }
        if (ans[n] <= h)
        {
            return 1;
        }
        else
            return 0;
    };
    l = 0, r = 2e9;
    int res = r;
    while (l <= r)
    {
        int mid = l + ((r - l) >> 1);
        if (check(mid))
        {
            res = mid;
            r = mid - 1;
        }
        else
        {
            l = mid + 1;
        }
    }
    if (res == 2e9)
    {
        cout << "MenWaiShuZhe" << endl;
    }
    else
    {
        cout << res << endl;
    }
}

L3-2

提取一下关键信息,题目告诉了我同一世界不能开传送门,而且有距离的限制,这个限制就是说两个相同世界的国家不可以有相同的邻居,这就是二分图匹配,什么意思呢,就是说有两堆点,堆内部不可以连边,只可以与另外一堆连边,而且一个点只能连一个点。

如何计算匹配的结果?可以枚举连的边数,最小是0,最大是min(n,m),然后每一个边数得到的结果累加就是匹配的结果。

那如何计算固定边数下两堆连的方案数呢?C(n,i)C(m,i)(i!),意思就是从两堆中各挑出i个点,第一个点能配i个点,第二个可以配(i-1)个,以此类推就是(i!),所以这个解释就很明了了。

注意!,刚刚只是计算了两堆点的方案数,我们易得出,一共有三堆,(a,b),(b,c),(a,c),而且这三堆的计算是独立的,所以采用乘法原理相乘就行了。

int fac[M];
int ifac[M];
void init()
{
    fac[0] = 1;
    for (int i = 1; i <= 5000; i++)
    {
        fac[i] = fac[i - 1] * i % mod;
    }
    ifac[5000] = ksm(fac[5000], mod - 2, mod);
    for (int i = 4999; i >= 0; i--)
    {
        ifac[i] = ifac[i + 1] * (i + 1) % mod;
    }
}
int ci(int n, int k)
{
    return fac[n] * ifac[n - k] % mod * ifac[k] % mod;
}
int a, b, c;
int f(int x, int y)
{
    int ans = 0;
    for (int i = 0; i <= min(x, y); i++)
    {
        ans = (ans + ci(x, i) * ci(y, i) % mod * fac[i]) % mod;
    }
    return ans;
}
void solve()
{
    init();
    cin >> a >> b >> c;
    cout << f(a, b) * f(b, c) % mod * f(a, c) % mod << endl;
}

L2-3

一看是个环,然后很容易就知道不是贪心而是dp,所以是一道很容易看出来的区间dp,dp[i][l],表示从i开始l长度所能获得的能量,那么只有两个变量一个是起点一个是长度,所以状态转移方程就是dp[i][l]=max(dp[i+1][l-1]+ia[i],dp[i][l-1]+ia[i+l-1]),前者表示取左端点,后者表示取右端点,那么只需找出最大的dp[i][n]就是结果,最小的起始点也易得。

void solve()
{
    int h = 0;
    cin >> h >> n;
    vi a(n + 1);
    memset(d2, 0, sizeof(d2));
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    vi b(2 * n + 1);
    for (int i = 1; i <= 2 * n; i++)
    {
        if (i <= n)
            b[i] = a[i];
        else
            b[i] = a[i - n];
        d2[i][1] = b[i] * n;
    }
    for (int i = 2; i <= n; i++)
    {
        int st = n - i + 1;
        for (int l = 1; l + i - 1 <= 2 * n; l++)
        {
            d2[l][i] = max(d2[l + 1][i - 1] + st * b[l], d2[l][i - 1] + st * b[l + i - 1]);
        }
    }
    int mx = INT_MIN;
    int mi = INT_MAX;
    for (int k = 1; k <= n; k++)
    {
        int now = b[k] + d2[k + 1][n - 1];
        if (now > mx)
        {
            mx = now;
            mi = k;
        }
        else if (now == mx)
        {
            mi = min(mi, k);
        }
    }
    cout << mx;
    if (h)
        cout << ' ' << mi << endl;
    else
        cout << endl;
}

L3-3

这个题就是tarjan强连通分量求scc缩点的板子,tarjan可以将有向有环图转换成有向无环图,然后在新的无环图上进行拓扑,这样就可以求得每个缩点可以获取的最大利润,再拿着最大利润去找最大的siz,就可以得到结果。

vi adj[M];
vi ad2[M];
int w[M];
int dfn[M], low[M], tim = 0;
int stk[M], vis[M], pos = 0;
int scc[M], siz[M], cnt = 0;
int dp[M], si[M];
void tarjan(int x)
{
    tim++;
    dfn[x] = tim, low[x] = tim;
    pos++;
    stk[pos] = x, vis[x] = 1;
    for (int i : adj[x])
    {
        if (!dfn[i])
        {
            tarjan(i);
            low[x] = min(low[x], low[i]);
        }
        else if (vis[i])
        {
            low[x] = min(low[x], dfn[i]);
        }
    }
    if (dfn[x] == low[x])
    {
        int te;
        cnt++;
        do
        {
            te = stk[pos--];
            vis[te] = 0;
            scc[te] = cnt;
            siz[cnt]++;
        } while (te != x);
    }
}
void solve()
{
    cin >> n >> m;
    vi a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++)
    {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
    }
    for (int i = 1; i <= n; i++)
    {
        if (!dfn[i])
            tarjan(i);
    }
    vi ind(cnt + 1, 0);
    for (int i = 1; i <= n; i++)
    {
        w[scc[i]] += a[i];
        for (int j : adj[i])
        {
            int ha = scc[i];
            int hb = scc[j];
            if (ha != hb)
            {
                ad2[scc[i]].push_back(scc[j]);
                ind[hb]++;
            }
        }
    }
    queue<int> p;
    for (int i = 1; i <= cnt; i++)
    {
        si[i] = siz[i];
        dp[i] = w[i];
        if (ind[i] == 0)
        {
            p.push(i);
        }
    }

    while (p.size())
    {
        auto i = p.front();
        p.pop();
        for (int j : ad2[i])
        {
            ind[j]--;
            if (ind[j] == 0)
            {
                p.push(j);
            }
            if (dp[i] + w[j] > dp[j])
            {
                dp[j] = max(dp[j], dp[i] + w[j]);
                si[j] = si[i] + siz[j];
            }
            else if (dp[i] + w[j] == dp[j])
            {
                si[j] = max(si[j], si[i] + siz[j]);
            }
        }
    }
    int ans = 0;
    int mx = 0;
    for (int i = 1; i <= cnt; i++)
    {
        if (ans < dp[i])
        {
            ans = max(ans, dp[i]);
            mx = si[i];
        }
        else if (ans == dp[i])
        {
            mx = max(mx, si[i]);
        }
    }
    cout << ans << ' ' << mx << endl;
}
posted @ 2026-03-18 13:34  Lambda_L  阅读(9)  评论(0)    收藏  举报