2025.4.6 2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site

比赛链接

Solved: 9/13

Rank: 4

Rank(vp): 25


I. Cyclic Apple Strings

题意:给一个 01 串,每次可以任意平移一个子串,求将其从小到大排序的最少操作次数。

答案就是 10 的出现次数。

void solve(){
    string a; cin >> a;
    int n = a.length(), cnt = 0;
    for (int i=1; i<n; ++i)
    {
        if (a[i] == '0' && a[i-1] == '1') ++cnt;
    }
    cout << cnt << '\n';
}

K. Party Games

题意:\(1\)\(n\) 排成一列,两人轮流取数,只能取头尾的数且要求取完后剩下的数异或和为 0,不能取的人输。问先手后手谁赢。

显然最多只能取一次。然后众所周知 \(1\)\(n\) 的异或和有通项:\(s_{4k} = 4k, s_{4k+1} = 1, s_{4k+2} = 4k+3, s_{4k+3} = 0\),所以 \(4k,4k+1\) 赢,\(4k+2,4k+3\) 输。

void solve(){
    int n; cin >> n;
    cout << (n % 4 == 1 || n % 4 == 0 ? "Fluttershy" : "Pinkie Pie") << '\n';
}

B. Countless Me

题意:给一个序列,每次可以给一个数加 \(x\) 并给另一个数减 \(x\),操作次数不限,求序列按位或的最小值。

这个操作的本质就是保持序列和不变的情况下使按位或最小。显然尽量让每个数相等(设为 \(A\))且剩下的数是相等的这些数的“子集”。
但如果总和不整除 lowbit(A) 是不能使按位或等于 \(A\) 的。所以要判断一下是否整除。如不整除则答案是 \(A+1\)

void solve(){
    int n; cin >> n;
    vector <int> a(n); cin >> a;
    ll sum = 0;
    for (int i=0; i<n; ++i) sum += a[i];
    ll t = (sum+n-1)/n;
    if (t==0) cout << 0 << '\n';
    else if (sum % (t&-t) == 0) cout << t << '\n';
    else cout << t+1 << '\n';
}

F. Custom-Made Clothes

题意:有一个未知的 \(n\times n\) 方阵,满足从左到右递增,从上到下递增。每次可以询问 \(i,j,x\) 回答 \(a_{i,j}\leq x\),求方阵第 \(k\) 大元素。要求询问次数 \(O(n\log n)\)

二分答案。\(\leq x\) 的数一定形成一个“倒三角”形状,因此可以 \(O(n)\) 次询问来 check。

bool query(int i, int j, int x)
{
    cout << "? " << i << ' ' << j << ' ' << x << endl;
    int res;
    cin >> res;
    return res;
}

int n, k;
bool check(int x)
{
    int sum = 0;
    for (int i=1, j=n; i<=n; ++i)
    {
        while (j>0 && !query(i, j, x)) --j;
        sum += j;
    }
    return sum >= k;
}

int main(){
    cin >> n >> k, k = n*n-k+1;
    int l=1, r=n*n, ans;
    while (l<=r)
    {
        int mid = (l+r)>>1;
        if (check(mid)) ans=mid, r=mid-1;
        else l=mid+1;
    }
    cout << "! " << ans << endl;
}

D. ICPC

题意:给一个序列,定义 \(f(s,t)\) 为从第 \(s\) 个数出发每次向左或向右走一个数在 \(t\) 步内能访问到的数的总和。对所有 \(1\leq s\leq n,1\leq t\leq 2n\)\(f(s,t)\)

dp。\(f(s,t)\) 可以从 \(f(s+1,t+1)\)\(f(s-1,t+1)\) 转移(折返走)。

const int N = 5005;
int n;
ll a[N], f[N][N*2], g[N][N*2];
int main(){
    ios::sync_with_stdio(false);cin.tie(0);
    cin >> n;
    for (int i=1; i<=n; ++i) cin >> a[i], a[i] += a[i-1];
    for (int i=1; i<=n; ++i)
        for (int j=1; j<=n*2; ++j)
        {
            int l = max(1, i-j), r = min(n, i+j);
            g[i][j] = max(a[i]-a[l-1], a[r]-a[i-1]);
        }
    for (int i=1; i<=n; ++i)
        for (int j=1; j<=n*2; ++j)
            f[i][j] = max(f[i-1][j-1], g[i][j]);
    for (int i=n; i>=1; --i)
        for (int j=1; j<=n*2; ++j)
        {
            g[i][j] = max(g[i+1][j-1], g[i][j]);
            f[i][j] = max(f[i][j], g[i][j]);
        }
    ll ans = 0;
    for (int i=1; i<=n; ++i)
    {
        ll sum = 0;
        for (int j=1; j<=n*2; ++j)
            sum ^= (j * f[i][j]);
        ans ^= sum+i;
    }
    cout << ans << '\n';
}

M. Merge

题意:给一个集合,每次可以讲其中两个相邻的数合成为它们的和。求任意次合成后形成的从大到小排序后字典序最大的集合。

贪心,每次考虑最大的数,设为 \(x\)。若 \(x+1\) 可以合成,则合成 \(x+1\) 并与 \(x\) 进一步合成为 \(2x+1\);否则考虑 \(x-1\)。若都不存在且不能合成则 \(x\) 只能出现在最终的序列中。

const int N = 2e5+5;
int n;
ll x;
map <ll, int> c;
vector <ll> d;
vector <ll> erased;
bool find(ll x)
{
    if (c.count(x) && c[x] > 0)
    {
        --c[x], erased.push_back(x);
        return 1;
    }
    if (x>1 && (x&1))
    {
        if (find(x/2) && find(x/2+1)) return 1;
    }
    return 0;
}
bool solve(ll x)
{
    erased.clear();
    if (find(x))
    {
        ++c[x];
        return 1;
    }
    else
    {
        for (ll y: erased) ++c[y];
    }
    return 0;
}
int main(){
    ios::sync_with_stdio(false);cin.tie(0);
    cin >> n;
    for (int i=1; i<=n; ++i) cin >> x, ++c[x];
    while (!c.empty())
    {
        ll x = c.rbegin()->first;
        if (c[x] < 0) return -1;
        --c[x];
        if (solve(x+1))
        {
            --c[x+1], ++c[2*x+1];
        }
        else if (solve(x-1))
        {
            --c[x-1], ++c[2*x-1];
        }
        else
        {
            d.push_back(x);
        }
        while (!c.empty() && c.rbegin()->second == 0)
        {
            auto it = c.find(c.rbegin()->first);
            c.erase(it);
        }
    }
    sort(all(d));
    cout << d.size() << '\n';
    for (int i=d.size()-1; i>=0; --i) cout << d[i] << ' ';
    cout << '\n';
}

E. Boomerang

题意:给一棵树。定义 \(N(r,d)\) 为所有距离 \(r\) 不超过 \(d\) 的点的集合。给定 \(r,t_0\),对所有 \(k\),求最小的 \(t\) 使得存在 \(r'\) 满足 \(N(r',k(t-t_0))\supset N(r,t)\)

覆盖子图等价于覆盖子图的直径。因此只需对每个 \(t\) 求出 \(N(r,t)\) 的直径。维护当前直径并在每次加一个点时更新直径即可。

const int N = 2e5+5;
int n, x, y, s, t0, dep[N], fa[N][18];
vector <int> e[N];
void adde(int x, int y)
{
    e[x].push_back(y), e[y].push_back(x);
}
void dfs(int u, int f)
{
    for (int i=1; i<=17; ++i) fa[u][i] = fa[fa[u][i-1]][i-1];
    for (int v: e[u]) if (v != f)
        fa[v][0] = u, dep[v] = dep[u] + 1, dfs(v, u);
}
int lca(int x, int y)
{
    if (dep[x] < dep[y]) swap(x, y);
    int t = dep[x] - dep[y];
    for (int i=17; ~i; --i) if (t>>i&1) x = fa[x][i];
    if (x==y) return x;
    for (int i=17; ~i; --i) if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}
int dis(int u, int v)
{
    return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}

vector <int> a, b;
bool vis[N];
int S, T, len[N*2], cur = 0, ans[N];

int main(){
    ios::sync_with_stdio(false);cin.tie(0);
    cin >> n;
    for (int i=1; i<n; ++i) cin >> x >> y, adde(x, y);
    dfs(1, 0);
    cin >> s >> t0;
    vis[s] = 1;
    a.push_back(s);
    S = T = s;
    for (int i=1; i<=n*2; ++i)
    {
        b.clear();
        for (int u: a)
        {
            for (int v: e[u]) if (!vis[v])
            {
                b.push_back(v), vis[v] = 1;
                if (dis(v, S) > cur) T = v, cur = dis(v, S);
                if (dis(v, T) > cur) S = v, cur = dis(v, T);
            }
        }
        len[i] = (cur+1)/2;
        a = b;
    }
    for (int i=1, k=n; i<=n; ++i)
    {
        int L = len[t0+i];
        while (k>0 && k*i >= L) ans[k] = t0+i, --k;
    }
    for (int i=1; i<=n; ++i) cout << ans[i] << ' ';
    cout << '\n';
}

C. TreeBag and LIS

题意:给定 \(x\),求一个长度不超过 \(10^5\) 的数字序列,满足最长上升子序列组成的数字之和恰好等于 \(x\)\(x\leq 10^{13}\)

注意到 \(89\times (10^5)^2 < 10^{13}\),所以至少要构造出长度为 3 的子序列。

考虑如下形式的序列:\(77\dots788\dots899\dots94566\dots60233\dots3\)

其答案为 \(789ijk + 456x + 23y\)

枚举 \(i\)\(j\),直接计算最大的 \(k\),然后 exgcd 算 \(x,y\) 即可。

void exgcd(ll a,ll b,ll& x,ll& y){
	if(!b)return x=1,y=0,void();
	exgcd(b,a%b,y,x);
	y-=x*(a/b);
}

ll u = 789, v = 456, w = 23, m = 1e5, n = 1e3;
ll s;
int main(){
    // 789ijk + 456x + 23y
    cin >> s;
    if (s <= 9*m)
    {
        while (s>=9) cout << '9', s-=9;
        cout << s << '\n';
        return 0;
    }
    for (int i=1; i<=n; ++i)
        for (int j=1; j<=n; ++j)
        {
            ll k = s / (u * i * j);
            if (k > m) continue;
            ll r = s - u * i * j * k;
            ll x, y;
            exgcd(v, w, x, y);
            y = (y*r % v + v) % v;
            x = (r - w * y) / v;
            if (x>=0 && y>=0 && i+j+k+x+y+4 <= m)
            {
                for (int t=0; t<i; ++t) cout << '7';
                for (int t=0; t<j; ++t) cout << '8';
                for (int t=0; t<k; ++t) cout << '9';
                cout << "45";
                for (int t=0; t<x; ++t) cout << '6';
                cout << "02";
                for (int t=0; t<y; ++t) cout << '3';
                cout << '\n';
                return 0;
            }
        }
}

G. Pack

题意:有 \(n\)\(a\)\(m\)\(b\),打包,每包若干个 \(a\) 和若干个 \(b\),且总价值恰好为 \(k\)。装尽可能多的包。求所有打包方案中剩余的 \(a\)\(b\) 个数之和的最小值。

设每包有 \(x\)\(a\)\(y\)\(b\),则 \(x = x_0 + bt, y = y_0 - at\)
答案是 \(n+m-(x+y)\min\{\lfloor\frac nx\rfloor, \lfloor\frac my\rfloor\}\)

下取整的取值只有 \(O(\sqrt n + \sqrt m)\) 个,整除分块即可。(注意这里 \(x\) 是递增的而 \(y\) 是递减的)

void exgcd(ll a,ll b,ll& x,ll& y){
	if(!b)return x=1,y=0,void();
	exgcd(b,a%b,y,x);
	y-=x*(a/b);
}

ll n, m, a, b, s;
void solve(){
    cin >> n >> m >> a >> b >> s;
    ll x, y;
    ll d = __gcd(a, b);
    a /= d, b /= d, s /= d;
    exgcd(a, b, x, y);
    ll x0 = (x * s % b + b) % b;
    ll y0 = (s - a * x0) / b;
    int lim = y0 / a;
    ll ans = n+m;
    for (ll i=0, j; i<=lim; i = j+1)
    {
        x = x0 + i * b;
        y = y0 - i * a;
        ll p = min(x ? n/x : INF, y ? m/y : INF);
        ll nx = x ? (n/x ? n / (n/x) : INF) : 0;
        ll ny = y ? m / (m/y+1) + 1 : 0;
        j = min((nx-x0)/b, (y0-ny)/a);
        ans = min(ans, min(n+m - (x0+y0 + (b-a) * i) * p, n+m - (x0+y0 + (b-a) * j) * p));
    }
    cout << ans << endl;
}
posted @ 2025-04-06 20:28  EssnSlaryt  阅读(184)  评论(0)    收藏  举报