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;
}

浙公网安备 33010602011771号