
Problem A. String Rotation Gam
- "旋转"操作最多使得答案比旋转前多 1 1 1。
- 如果原序列中有一个长度大于 1 1 1 的 "block"(相同类型字符组成的连续子字符串),并且首尾字母不同,就可以使得答案 + 1 + 1 +1。
- 具体操作方法是,把长度大于 1 1 1 的 "block" 换到首尾。
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
int n;
cin >> n;
string s;
cin >> s;
s = "#" + s;
bool flag = false;
int ans = 0;
for(int i = 1; i <= n; i ++){
int j = i;
while(j + 1 <= n && s[j + 1] == s[i]) j ++;
if(j - i > 0) flag = 1;
ans ++;
i = j;
}
if(s[1] != s[n] && flag) ans ++;
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
Problem B. Flipping Binary String
- 同一个位置被翻转偶数次等价于不被翻转,被反转奇数次等价于翻转 1 1 1 次。题目中 "翻转除了 i 的所有位置" 这一操作,可以拆成两步独立的操作,先翻转一次所有位置(操作1),再翻转一次第 i 个位置(操作2)。
- 如果原序列中 '1' 的个数是偶数,那么通过在所有 s [ i ] = 1 s[i] = 1 s[i]=1 的位置做翻转,可以使得序列变为全 '0'。此时,操作 1 1 1(翻转全部)的次数为偶,等于不做;而操作 2 2 2(翻转第 i i i 个)用在了每一个 s [ i ] = 1 s[i] = 1 s[i]=1的位置,使其变为 0。
- 如果原序列中 '0' 的个数是奇数,那么通过在所有 s [ i ] = 0 s[i] = 0 s[i]=0 的位置做翻转,可以使得序列变为全 '0'。我们类比上一种情况理解,此时,操作 1 1 1(翻转全部)的次数为偶,等于全部做一次翻转,那么原来为 '0' 的位置变为 '1',需要对这些位置进行操作 2 2 2 使之变回 '0'。
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
int n;
cin >> n;
string s;
cin >> s;
s = "#" + s;
array<int,2> cnt = {0,0};
for(int i = 1; i <= n; i ++){
cnt[s[i] - '0'] ++;
}
vector<int> ans;
if(cnt[1] % 2 == 0){
for(int i = 1; i <= n; i ++){
if(s[i] == '1') ans.push_back(i);
}
}else if(cnt[0] % 2 == 1){
for(int i = 1; i <= n; i ++){
if(s[i] == '0') ans.push_back(i);
}
}else{
cout << -1 << '\n';
return;
}
cout << ans.size() << '\n';
for(int i : ans){
cout << i << ' ';
}
cout << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
Problem C. All-in-one Gun
- 设 a a a 数组的和 ( ∑ i = 1 n a i \sum_{i = 1}^{n} a_i ∑i=1nai) 为 s u m sum sum,当 h ≥ s u m h \ge sum h≥sum 时,肯定能打完全部子弹,是否交换子弹不影响结果,只需要关注 h m o d s u m h \bmod sum hmodsum 的部分,该部分记为 l l l,即 l = h m o d s u m l = h \bmod sum l=hmodsum 。
- 从前往后枚举每一颗子弹,找到第一个能消耗完 l l l 的位置。设当前位置的前缀和为 p r e s u m presum presum,如果不进行交换,那么要满足 p r e s u m ≥ l presum \ge l presum≥l;如果进行交换,选择前缀最小值和后缀最大值交换,要满足 p r e s u m − p r e m i + s u f f [ i + 1 ] ≥ l presum - premi + suff[i+1] \ge l presum−premi+suff[i+1]≥l, p r e m i premi premi 为前缀最小值, s u f f [ i + 1 ] suff[i+1] suff[i+1] 从第 i i i 位起的后缀最大值。
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
int n,h,k;
cin >> n >> h >> k;
vector<int> a(n+1);
for(int i = 1; i <= n; i ++) cin >> a[i];
int sum = accumulate(a.begin()+1,a.end(),0ll);
int l = h % sum;
if(l == 0){
cout << (k + n) * (h / sum) - k << '\n';
return;
}else{
vector<int> suff(n+2);
for(int i = n; i >= 1; i --){
suff[i] = max(suff[i+1],a[i]);
}
int premi = 1e18,presum = 0;
for(int i = 1; i <= n; i ++){
premi = min(premi,a[i]);
presum += a[i];
if(presum >= l || presum - premi + suff[i+1] >= l){
cout << (k + n) * (h / sum) + i << '\n';
return;
}
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
Problem D. Cost of Tree
设点 i i i 的子树中各点的权重和为 s u m a [ i ] suma[i] suma[i]。
- 首先考虑不进行交换操作时,如何计算每个根节点 u u u 的答案:
- 枚举与 u u u 相连的点 i i i。
- 通过 d f s dfs dfs 得到点 i i i 的答案,将来自点 i i i 的答案记为 a n s [ i ] ans[i] ans[i],是叶子节点则答案为 0 0 0;
- 来自点 i i i 子树的所有点到 u u u 的距离比到 i i i 的距离多 1 1 1,所以他们对 u u u 的贡献比对 i i i 多一个 s u m a [ i ] suma[i] suma[i]。代码上体现为 a n s [ u ] + = a n s [ i ] + s u m a [ i ] ans[u] += ans[i] + suma[i] ans[u]+=ans[i]+suma[i]
- 然后考虑在点 u u u 进行交换操作能产生的答案增量 a d d i t i o n [ u ] addition[u] addition[u]:
- 原本点 i i i 子树可以做的操作,新的根也可做。 a d d i t i o n [ u ] = max ( a d d i t i o n [ u ] , a d d i t i o n [ i ] ) addition[u] = \max{(addition[u],addition[i])} addition[u]=max(addition[u],addition[i])。
- 贪心地把 s u m a suma suma 最大的子树整体割下,接到深度最深的节点上。如果深度最深的节点恰好在 s u m a suma suma 最大的子树中,则考虑第二深节点和第二大子树。
点 i i i 的最终答案为 a n s [ i ] + a d d i t i o n [ i ] ans[i] + addition[i] ans[i]+addition[i]。
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
int n;
cin >> n;
vector<int> a(n+1);
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
vector<vector<int>> v(n+1);
for(int i = 1; i <= n - 1; i ++){
int x,y;
cin >> x >> y;
v[x].push_back(y),v[y].push_back(x);
}
vector<int> ans(n+1),addition(n+1);
vector<int> suma(n+1),deep(n+1);
auto dfs = [&](auto &&self,int u,int p) -> void{
vector<pair<int,int>> tmpsum,tmpdeep;
tmpsum.clear();
tmpdeep.clear();
for(int i : v[u]){
if(i == p) continue;
self(self,i,u);
tmpsum.push_back({suma[i],i});
tmpdeep.push_back({deep[i],i});
suma[u] += suma[i];
deep[u] = max(deep[u],deep[i] + 1);
ans[u] += ans[i] + suma[i];
addition[u] = max(addition[u],addition[i]);
}
suma[u] += a[u];
sort(tmpsum.begin(),tmpsum.end());
sort(tmpdeep.begin(),tmpdeep.end());
if(tmpsum.size() <= 1){
addition[u] = max(addition[u],0ll);
}
else if(tmpsum.back().second != tmpdeep.back().second){
addition[u] = max(addition[u],tmpsum.back().first * (tmpdeep.back().first + 1));
}else{
addition[u] = max(addition[u],tmpsum[tmpsum.size()-2].first * (tmpdeep.back().first + 1));
addition[u] = max(addition[u],tmpsum.back().first * (tmpdeep[tmpdeep.size()-2].first + 1));
}
};
dfs(dfs,1,-1);
for(int i = 1; i <= n; i ++){
cout << ans[i] + addition[i] << " \n"[i == n];
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
Problem E. Swap to Rearrange
- 对于 1 ~ n 的每个数字,其分给 a a a 数组的数量和不分给 a a a 数组必须相同。如果把每个自然数看作一个点,这个结论可翻译为每个点的入度和出度相同。
- 欧拉回路中,每个点的入度和出度相同。
- 考虑在每一对 a [ i ] , b [ i ] a[i] , b[i] a[i],b[i] 之间建立一条无向边,找到一条合法的欧拉回路即可。
- 需要前置知识------欧拉回路,P7771 【模板】欧拉路径。
- 无向图找欧拉回路的方法:将一条无向边拆为两条共享同一个 i d id id 的有向边,称这两条有向边又称为无向边的"半边"。找欧拉回路时,经过一条有向边就标记其 i d id id 为已使用,确保每条无向边只走过一次。
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
struct edge{
int x,id,dir;
edge(int _x,int _id,int _dir):x(_x),id(_id),dir(_dir){}
};
void solve(){
int n;
cin >> n;
vector<int> a(n+1),b(n+1);
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
for(int i = 1; i <= n; i ++){
cin >> b[i];
}
vector<int> p(n+1),deg(n+1);
vector<int> done(n+1);
vector<vector<edge>> v(n+1);
//0 a->b
for(int i = 1; i <= n; i ++){
v[a[i]].emplace_back(b[i],i,1);
v[b[i]].emplace_back(a[i],i,0);
deg[a[i]] ++,deg[b[i]] ++;
}
for(int i = 1; i <= n; i ++){
if(deg[i] % 2 != 0){
cout << -1 << '\n';
return;
}
}
vector<int> ans,vis(n+1);
auto dfs = [&](auto &&self,int u)->void{
vis[u] = 1;
while(p[u] < v[u].size()){
auto [i,id,dir] = v[u][p[u] ++];
if(done[id]) continue;
done[id] = 1;
if(dir) ans.push_back(id);
self(self,i);
}
};
for(int i = 1; i <= n; i ++){
if(!vis[i]){
dfs(dfs,i);
}
}
cout << ans.size() << '\n';
for(int i : ans){
cout << i << ' ';
}
cout << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while(t --){
solve();
}
}