USACO2026 JAN1 Silver 简要题解

预估难度:*1600, *2100, *1900。对应洛谷黄蓝绿。

2.5 h 慢慢悠悠 AK 了,中间还去洗了个澡,应该是比较简单的 Silver 了。

A

打表发现编号为 \(1\) 的牛最多会成为 \(100\) 次队头,所以 \(op = 1\) 的询问可以暴力做。

\(op = 2\) 的询问则需要发现成为队头的时间的递推式,具体而言,假设 \(t\) 时刻某头牛成为了队头,那么当 \(T'=t + 1 + \lfloor\dfrac{t + 1}{2}\rfloor\) 时这头牛第二次成为队头。所以可以先求出编号为 \(x\) 的牛下次成为队头是什么时候,然后不断推回去,直到不能推为止。由 \(T\) 推导 \(t\) 可以用一个偷懒的方式,先忽略下取整的限制,正常解出方程,然后在解附近的几个整数全都 check 一遍即可。最后根据“编号为 \(x\) 的牛第一次成为队头的时间为 \(3x - 1\)”的结论可以得到牛的编号,结论的证明可以自己打表手模。

时间复杂度 \(O(TB)\),其中 \(B = 100\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef __int128 i128;
using pi = pair<int, int>;
void solve1(ll x, ll t)
{
    if(t <= 2 * x - 1)
    {
        cout << x << "\n";
        return;
    }
    if(t <= 3 * x - 1)
    {
        cout << x - (t - (2 * x - 1)) << "\n";
        return;
    }
    ll nowt = 3 * x - 1;
    while(1)
    {
        ll to = nowt + (nowt + 1) / 2 + 1;
        if(to >= t) break;
        nowt = to;
    }
    cout << (nowt + 1) / 2 - (t - nowt - 1) << "\n";
}
bool check(ll x, ll y)
{
    return (x + (x + 1) / 2 + 1 == y);
}
void solve2(ll x, ll t)
{
    if(x > t / 2)
    {
        cout << x << "\n";
        return;
    }
    t += x;
    while(1)
    {
        ll y = (2 * t - 3) / 3;
        bool isfind = 0;
        for(ll i = y - 2; i <= y + 2; i++)
        {
            if(check(i, t))
            {
                t = i;
                isfind = 1;
                break;
            }
        }
        if(!isfind) break;
    }
    cout << (t + 1) / 3 << "\n";
}
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        ll op, x, y;
        cin >> op >> x >> y;
        if(op == 1) solve1(x, y);
        else solve2(x, y);
    }
    return 0;
}

B

先考虑如何判断是否有解。

显然可以图论建模,然后对每个连通块随出一颗生成树出来,递推出每个数与根的关系 \(kx+b\),其中 \(k\in\{1, -1\}\)

对于剩余的边,若 \(k_u\ne k_v\),判断是否满足 \(b_u+b_v=w\);若 \(k_u= k_v\),则可以直接推出根的值 \(\dfrac{w - b_u-b_v}{2k}\),判断合法性,对这个连通块打上一个标记即可。

现在考虑加了区间限制之后的做法,对每个连通块考虑,此时若根的值已经确定,则可以直接计算贡献;否则遍历连通块里的每一个点,根据与根的关系推出这个点满足要求时根节点的值的区间 \([l', r']\),然后在根节点用 map 维护差分贡献区间,最后在根节点处把最大的差分值当做该连通块的最大贡献即可。

难点在于代码。时间复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef __int128 i128;
using pi = pair<int, int>;
using pii = pair<pi, int>;
const int N = 200005, M = 400005;
ll n, m, lx[N], rx[N], kx[N], bx[N], extval[N], haveval[N];
vector<pi> g[N];
vector<pii> gx;
struct DSU{
    int fa[N];
    void init()
    {
        for(int i = 1; i <= n; i++) fa[i] = i;
    }
    int findf(int x)
    {
        if(fa[x] != x) fa[x] = findf(fa[x]);
        return fa[x];
    }
    void combine(int x, int y)
    {
        int fx = findf(x), fy = findf(y);
        fa[fx] = fy;
    }
} dsu;
void dfs(int u, int fa)
{
    for(auto eg : g[u])
    {
        ll v = eg.fi, w = eg.se;
        if(v == fa) continue;
        kx[v] = -kx[u];
        bx[v] = w - bx[u];
        dfs(v, u);
    }
}
map<ll, ll> tot[N];
void solve()
{
    cin >> n >> m;
    dsu.init();
    for(int i = 1; i <= n; i++)
    {
        g[i].clear();
        tot[i].clear();
    }
    gx.clear();
    memset(kx, 0, sizeof(kx));
    memset(bx, 0, sizeof(bx));
    memset(extval, 0, sizeof(extval));
    memset(haveval, 0, sizeof(haveval));
    for(int i = 1; i <= n; i++) cin >> lx[i];
    for(int i = 1; i <= n; i++) cin >> rx[i];
    for(int i = 1; i <= m; i++)
    {
        ll u, v, w;
        cin >> u >> v >> w;
        if(dsu.findf(u) == dsu.findf(v))
        {
            gx.push_back({{u, v}, w});
            continue;
        }
        g[u].push_back({v, w});
        g[v].push_back({u, w});
        dsu.combine(u, v);
    }
    for(int i = 1; i <= n; i++)
    {
        if(dsu.findf(i) == i)
        {
            kx[i] = 1;
            bx[i] = 0;
            dfs(i, 0);
        }
    }
    for(auto eg : gx)
    {
        ll u = eg.fi.fi, v = eg.fi.se, w = eg.se;
        int anc = dsu.findf(u);
        if(kx[u] != kx[v])
        {
            if(bx[u] + bx[v] != w)
            {
                cout << "-1\n";
                return;
            }
            continue;
        }
        ll ext = 0;
        if(kx[u] == -1)
        {
            if((bx[u] + bx[v] - w) % 2)
            {
                cout << "-1\n";
                return;                
            }
            ext = (bx[u] + bx[v] - w) / 2;
        }
        else
        {
            if((w - bx[u] - bx[v]) % 2)
            {
                cout << "-1\n";
                return;                
            }
            ext = (w - bx[u] - bx[v]) / 2;            
        }
        if(haveval[anc] && ext != extval[anc])
        {
            cout << "-1\n";
            return;
        }
        extval[anc] = ext;
        haveval[anc] = 1;
    }
    ll ans = 0;
    for(int i = 1; i <= n; i++)
    {
        int anc = dsu.findf(i);
        if(haveval[anc])
        {
            ll val = kx[i] * extval[anc] + bx[i];
            if(lx[i] <= val && val <= rx[i]) ans++;
            continue;
        }
        if(kx[i] > 0)
        {
            tot[anc][lx[i] - bx[i]]++;
            tot[anc][rx[i] - bx[i] + 1]--;
        }
        else
        {
            tot[anc][bx[i] - rx[i]]++;
            tot[anc][bx[i] - lx[i] + 1]--;
        }
    }
    for(int i = 1; i <= n; i++)
    {
        int anc = dsu.findf(i);
        if(haveval[anc]) continue;
        if(anc != i) continue;
        ll pre = 0, mx = 0;
        for(auto itm : tot[i])
        {
            pre += itm.se;
            mx = max(mx, pre);
        }
        ans += mx;
    }
    cout << ans << "\n";
}
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}

C

题意相当于是给定了所有长度为 \(k\) 的区间的异或和,求出 \(1\) 的个数的最大值、最小值。

对区间 \([l, r], [l + 1, r + 1]\) 考虑,若两区间 \(r\) 值相同,说明 \(b_l=b_{r + 1}\);否则说明 \(b_l\ne b_{r + 1}\)。这显然可以用并查集维护。

在满足上面的要求后,最后满足 \(\oplus_{i = 1}^k b_i=r_1\) 的要求就能使得字符串合法。但是我们先不考虑这个限制,先贪心地对每个连通块贪出最优解,然后再调整方案使其满足这个限制。容易发现我们最后最多只会调整一个连通块,所以在调整的代价里选取一个最小值 / 最大值即可。

时间复杂度 \(O(n)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef __int128 i128;
using pi = pair<int, int>;
const int N = 1000005, inf = 0x3f3f3f3f;
int n, k, a[N];
int fa[2 * N], smsz[2 * N], sza[2 * N];
void init()
{
    for(int i = 1; i <= 2 * n; i++)
    {
        fa[i] = i;
        smsz[i] = 1;
        if(i <= n) sza[i] = 1;
        else sza[i] = 0;
    }
}
int findf(int x)
{
    if(fa[x] != x) fa[x] = findf(fa[x]);
    return fa[x];
}
void combine(int x, int y)
{
    int fx = findf(x), fy = findf(y);
    if(fx == fy) return;
    fa[fx] = fy;
    smsz[fy] += smsz[fx];
    sza[fy] += sza[fx];
}
void solve()
{
    cin >> n >> k;
    init();
    for(int i = 1; i <= n - k + 1; i++)
    {
        char c;
        cin >> c;
        a[i] = c - '0';
    }
    if(k == 1)
    {
        int ans = 0;
        for(int i = 1; i <= n; i++) ans += a[i];
        cout << ans << " " << ans << "\n";
        return;
    }
    for(int i = 1; i < n - k + 1; i++)
    {
        int u = i, v = i + k;
        if(a[i] == a[i + 1])
        {
            combine(u, v);
            combine(u + n, v + n);
        }
        else
        {
            combine(u, v + n);
            combine(u + n, v);
        }
    }
    int mn = 0, mx = 0, choose = 0, mxchange = -inf;
    for(int i = 1; i <= k; i++)
    {
        int anc = findf(i);
        int va = sza[anc], vb = smsz[anc] - sza[anc];
        if(va > vb)
        {
            choose ^= 1;
            mx += va;
            mxchange = max(mxchange, vb - va);
        }
        else
        {
            mx += vb;
            mxchange = max(mxchange, va - vb);
        }
    }
    if(choose != a[1]) mx += mxchange;
    mxchange = inf;
    choose = 0;
    for(int i = 1; i <= k; i++)
    {
        int anc = findf(i);
        int va = sza[anc], vb = smsz[anc] - sza[anc];
        if(va > vb)
        {
            mn += vb;
            mxchange = min(mxchange, va - vb);
        }
        else
        {
            choose ^= 1;
            mn += va;
            mxchange = min(mxchange, vb - va);
        }
    }
    if(choose != a[1]) mn += mxchange;
    cout << mn << " " << mx << "\n";
}
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}
posted @ 2026-01-10 02:50  KS_Fszha  阅读(18)  评论(0)    收藏  举报