A. Zhily and Array Operating
贪心解决,当 ai+1<0a_{i+1} < 0ai+1<0 一定不操作,当 ai+1>0a_{i+1} > 0ai+1>0 一定操作,而且下标大的位置开始操作更优。
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];
}
for(int i = n - 1; i >= 1; i --){
if(a[i+1] > 0){
a[i] +=a [i+1];
}
}
int ans = 0;
for(int i = 1; i <= n; i ++){
if(a[i] > 0) ans ++;
}
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout << fixed << setprecision(12);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
B. Zhily and Mex and Max
贪心解决,第一个位置放最大值,一定最优。
后面的位置按mex递增放置即可,如果不能做到让mex递增,就随便放了。
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];
}
map<int,int> mp;
for(int i = 1; i <= n; i ++){
mp[a[i]] ++;
}
int mx = (--mp.end())->first;
mp.rbegin()->second --;
if(mp.rbegin()->second == 0) mp.erase((--mp.end()));
int ans = 0,p = 0;
set<int> se;
se.insert(mx);
while(se.find(p) != se.end()) p ++;
ans += mx + p;
for(int i = 2; i <= n; i ++){
if(mp.find(p) != mp.end()){
mp[p] --;
se.insert(p);
if(mp[p] == 0) mp.erase(mp.find(p));
}
else{
mp.begin()->second --;
se.insert(mp.begin()->first);
if(mp.begin()->second == 0) mp.erase(mp.begin());
}
while(se.find(p) != se.end()) p ++;
ans += mx + p;
}
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
//cout << fixed << setprecision(12);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
C. Zhily and Bracket Swapping
左括号和右括号总数相等才可能有解。
对于任意括号序列,如果某段前缀的右括号数量大于左括号,则不平衡。反之,每一个前缀的右括号数量小于左括号数量,且最后一个位置右括号数量等于左括号,则平衡。
对于 ai=bia_i = b_iai=bi 的位置,交换无意义,直接加减。
对于 ai≠bia_i \neq b_iai=bi 的位置,可以立刻贪心地把右括号换给当前左括号数多的一方(官解的做法),也可以把这种位置记录下来,存进 cntcntcnt,后续如果遇到一个序列不平衡,再决定把cntcntcnt里的左括号给不平衡的序列(本题解代码的做法)。
cpp
复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
int n;
cin >> n;
string s1,s2;
cin >> s1 >> s2;
s1 = "#" + s1;
s2 = "#" + s2;
int cntl = 0,cntr = 0;
for(int i = 1; i <= n; i ++){
if(s1[i] == '(') cntl ++;
else cntr ++;
if(s2[i] == '(') cntl ++;
else cntr ++;
}
if(cntl != cntr){
cout << "NO\n";
return;
}
int sum1 = 0,sum2 = 0;
int cnt = 0;
for(int i = 1; i <= n; i ++){
//cout << i << endl;
if(s1[i] == s2[i]){
if(s1[i] == '('){
sum1 ++;
}else{
sum1 --;
}
if(s2[i] == '('){
sum2 ++;
}else{
sum2 --;
}
if(sum1 < 0){
if(cnt > 0){
cnt -= 2;
sum1 += 2;
}
else{
cout << "NO\n";
return;
}
}
if(sum2 < 0){
if(cnt > 0){
cnt -= 2;
sum2 += 2;
}
else{
cout << "NO\n";
return;
}
}
}else{
if(sum1 > 0 && sum2 > 0){
cnt += 2;
sum1 --;
sum2 --;
}else if(sum1 > 0){
sum1 --;
sum2 ++;
}else if(sum2 > 0){
sum2 --;
sum1 ++;
}else{
if(cnt > 0){
cnt -= 2;
sum1 += 2;
sum1 --;
sum2 ++;
}
else{
cout << "NO\n";
return;
}
}
}
}
cout << "YES\n";
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
//cout << fixed << setprecision(12);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
D. Zhily and Barknights
独立计算任意两个位置是否引发争议。
任选一对下标 (i,j)(i,j)(i,j) ,这对下标能引发争议当且仅当 biai>bjajb_ia_i >b_ja_jbiai>bjaj。
如果我们枚举 bi,bjb_i,b_jbi,bj的值,将bi,bjb_i,b_jbi,bj的值固定,bibj\frac{b_i}{b_j}bjbi也就固定了,那么所有满足aiaj>bibj\frac{a_i}{a_j} >\frac{b_i}{b_j}ajai>bjbi 的 ai,aja_i,a_jai,aj都和这对 bi,bjb_i,b_jbi,bj 配对成功,引发一次争议。
找到满足条件的 ai,aja_i,a_jai,aj 的方法是预处理所有 aiaj\frac{a_i}{a_j}ajai 后排序,再二分查找。
本题涉及的模板------分数类
cpp
复制代码
struct frac {
int a, b;
frac(int a, int b) : a(a), b(b) {}
bool operator < (const frac& r) const {
return (__int128)a * r.b < (__int128)r.a * b;
}
bool operator > (const frac& r) const {
return (__int128)a * r.b > (__int128)r.a * b;
}
bool operator == (const frac& r) const {
return (__int128)a * r.b == (__int128)r.a * b;
}
frac operator + (const frac& r) const {
return {a * r.b + r.a * b, b * r.b};
}
frac operator - (const frac& r) const {
return {a * r.b - r.a * b, b * r.b};
}
frac operator * (const frac& r) const {
return {a * r.a, b * r.b};
}
frac operator / (const frac& r) const {
return {a * r.b, b * r.a};
}
};
完整代码
cpp
复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int mod = 998244353;
struct frac {
int a, b;
frac(int a, int b) : a(a), b(b) {}
bool operator < (const frac& r) const {
return (__int128)a * r.b < (__int128)r.a * b;
}
bool operator > (const frac& r) const {
return (__int128)a * r.b > (__int128)r.a * b;
}
bool operator == (const frac& r) const {
return (__int128)a * r.b == (__int128)r.a * b;
}
frac operator + (const frac& r) const {
return {a * r.b + r.a * b, b * r.b};
}
frac operator - (const frac& r) const {
return {a * r.b - r.a * b, b * r.b};
}
frac operator * (const frac& r) const {
return {a * r.a, b * r.b};
}
frac operator / (const frac& r) const {
return {a * r.b, b * r.a};
}
};
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];
}
if(n == 1){
cout << 0 << '\n';
return;
}
auto cmp = [&](PII l,PII r) -> bool{
int mul1 = l.first * r.second;
int mul2 = r.first * l.second;
return mul1 > mul2;
};
vector<frac> pa;
for(int i = 1; i <= n; i ++){
for(int j = i + 1; j <= n; j ++){
pa.push_back({a[i],a[j]});
}
}
pa.push_back({2000000000,1});
sort(pa.begin(),pa.end(),greater<frac>());
int cnt = 0;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
if(i != j){
int l = 0,r = pa.size() - 1;
frac tmp = {b[j],b[i]};
while(l != r){
int mid = l + r + 1 >> 1;
if(pa[mid] > tmp) l = mid;
else r = mid - 1;
}
cnt += l ;
}
}
}
auto qmi = [&](int a,int b) -> int{
int ret = 1;
while(b){
if(b & 1) ret = 1ll * a * ret % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return ret;
};
int pow1 = 1,pow2;
for(int i = 1; i <= n-2; i ++){
pow1 = pow1 * i % mod;
pow1 %= mod;
}
pow2 = pow1 * (n-1) % mod * n % mod;
pow2 %= mod;
cnt %= mod;
int ans = pow1 * cnt % mod * qmi(pow2,mod-2) % mod;
cout << ans%mod << '\n';
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout << fixed << setprecision(12);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
E. Zhily and Signpost
考虑用暴力做法解决本题。
询问可以去重,多个相同时刻只计算一次。
对于最简单的情况,所有模 dud_udu(节点儿子数)同余的询问,走的儿子是相同的,考虑聚合它们为询问组。
定义一个询问组,为一系列 模 xxx 同余的数。对于一个询问组,如果 xxx 能被 dud_udu(节点儿子数)整除,或者组内只有一个数,那么整个询问组可以直接平移到同一个儿子,这个平移是 o(1)o(1)o(1) 的,使用 movemovemove 函数或指针。如果 xxx 不能被 dud_udu(节点儿子数)整除,那么进行一次暴力地拆分,枚举询问组内每一个询问,对其取模,重新归类到另一个模数更大的新询问组。
看似暴力,实则已经可以过了,证明如下:
对于所有平移操作,复杂度为 o(n)o(n)o(n)。
得到的新询问组的模数 xxx,一定是老询问的两倍以上。而模数到达 1e181e181e18 之后,询问组内只有一个询问,不用再拆分。故拆分操作不超过 log1e18\log{1e18}log1e18 次,每次拆分的时间复杂度为 o(n)o(n)o(n)。
cpp
复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
void solve(){
int n,q;
cin >> n >> q;
vector<vector<int>> v(n+1);
for(int i = 2; i <= n; i ++){
int fa;
cin >> fa;
v[fa].push_back(i);
}
for(int i = 1; i <= n; i ++){
sort(v[i].begin(),v[i].end());
}
vector<int> len(n+1);
for(int i = 2; i <= n; i ++){
cin >> len[i];
}
vector<int> ans(q+1);
vector<PII> query(q+1);
for(int i = 1; i <= q; i ++){
cin >> query[i].first;
query[i].second = i;
}
sort(query.begin()+1,query.end());
vector<int> val;
vector<vector<int>> idx;
for(int i = 1; i <= q; i ++){
val.push_back(query[i].first);
idx.push_back({});
idx.back().push_back(query[i].second);
while(i + 1 <= q && query[i+1].first == val.back()){
idx.back().push_back(query[i+1].second);
i ++;
}
}
vector<int> ask(idx.size());
iota(ask.begin(),ask.end(),0ll);
auto lcm = [&](__int128 a,__int128 b) -> __int128{
__int128 g = a * b / __gcd(a,b);
return g;
};
auto dfs = [&](auto &&self,int u,int p,int t,int mod,vector<int> a) -> void{
//cout << u << endl;
if(v[u].size() == 0){
for(int i : a){
for(int j : idx[i]){
ans[j] = u;
}
}
return;
}
if(a.size() <= 1 || mod == -1 || mod % v[u].size() == 0){
int ne = (val[a[0]] + t) % v[u].size();
self(self,v[u][ne],u,t + len[v[u][ne]],mod,move(a));
}else{
vector<vector<int>> b(v[u].size());
for(int i : a){
b[(val[i] + t)%(v[u].size())].push_back(i);
}
int nmod = lcm(mod,v[u].size());
if(nmod > 1e18) nmod = -1;
for(int i = 0; i < v[u].size(); i ++){
if(b[i].size()){
self(self,v[u][i],u,t + len[v[u][i]],nmod,b[i]);
}
}
}
};
dfs(dfs,1,-1,0,1,ask);
for(int i = 1; i <= q; i ++){
cout << ans[i] << " \n"[i == q];
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while(t --){
solve();
}
}
F. Zhily and Cycle
肯定要转成欧拉回路求解,不然炸复杂度了,我们把每个点的出边(i,au)(i,a_u)(i,au)转化成(i,ai),(ai,au)(i,a_i),(a_i,a_u)(i,ai),(ai,au)两条边,找到新图的一个欧拉回路,再删掉第二条边就得到哈密顿回路。
先把每个 iii 连上第一部分的边 (i,ai)(i , a_i)(i,ai)。
如果对于每一个当前入度出度不平衡的点,恰好能找到n条边使得整个图入度出度平衡就有解了。
观察所有入度大于出度的点,假设差值为degdegdeg,它们恰好能连degdegdeg条边,每条边能且只能连向序号大于它的点。因为他们入度大于出度本身是由(i,a_i)导致的,后面必定要跟一条(a_i,a_u),(a_i,a_u)这条边的要求有且仅有下一个点的序号更大。
我们把(a_i,a_u)称作前向边,贪心解决这个问题。
定义 deg[i]deg[i]deg[i] 为点 iii 的入度和出度的差值。 对 degdegdeg 求前缀和 perperper,一旦出现 pre[i]<0pre[i] < 0pre[i]<0 则无解。每一个 pre[i]==0pre[i] == 0pre[i]==0的位置成为切分点,因为不能有前向边跨过 pre[i]==0pre[i] == 0pre[i]==0 的位置,一旦跨过 pre[i]pre[i]pre[i] 减为负。
对于两个 pre[i]==0pre[i] == 0pre[i]==0 所夹的区间,先贪心地连 (i,i+1),最后一个位置不加边,保证块内联通,然后把 deg[i]>0deg[i]>0deg[i]>0 的点记下来,作为提供者,遇到deg[i]<0deg[i]<0deg[i]<0 就连一条提供者到该点的前向边。
如果新图不连通输出no。
用模板求新图的欧拉回路。只保留欧拉回路中(i,a_i)这类边的左端点,就得到了答案。
本题涉及的模板------欧拉回路(保留边+保留点)
cpp
复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
void Hierholzer_node(){
int n;
vector<vector<int>> g(n + 1);
vector<int> p(n + 1); // 当前点扫到第几条边
vector<int> path;
auto dfs = [&](auto &&self, int u) -> void {
while (p[u] < g[u].size()) {
int v = g[u][p[u]++];
self(self, v);
}
path.push_back(u);
};
int st;
dfs(dfs, st);
reverse(path.begin(), path.end());
}
void Hierholzer(){
int n;
cin >> n;
vector<vector<PII>> v(n + 1); // v[l] = {r, eid}
vector<int> from, to,used;
int eid = 0;
auto addedge = [&](int l, int r) -> int {
v[l].push_back({r, eid});
// 有向图删掉下面一行
v[r].push_back({l, eid}); // 有向图删该行
from.push_back(l);
to.push_back(r);
used.push_back(0);
return eid++;
};
vector<int> p(n + 1),edge,node;
auto dfs = [&](auto &&self, int u, int peid) -> void {
while (p[u] < (int)v[u].size()) {
auto [r, eid] = v[u][p[u]++];
if (used[eid]) continue;
used[eid] = 1;
self(self, r, eid);
}
edge.push_back(peid);
node.push_back(u);
};
int st;
dfs(dfs, st, -1);
// node 即点序列
// edge 即边序列(edge[0] 为 -1)
reverse(edge.begin(), edge.end());
reverse(node.begin(), node.end());
}
完整代码
cpp
复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
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);
struct edge{
int l,r,fix;
};
vector<edge> e;
int np = 0;
vector<int> deg(n+1);
auto addedge = [&](int l,int r,int fix)->void{
deg[l] --;
deg[r] ++;
e.push_back({l,r,fix});
v[l].push_back(np);
np ++;
};
for(int i = 1; i <= n; i ++){
addedge(i,a[i],1);
}
vector<int> pre(n+1);
int last = 1;
auto work = [&](int l,int r)->void{
for(int i = l; i <= r - 1; i ++){
addedge(i,i+1,0);
}
vector<PII> stk;
for(int i = l; i <= r; i ++){
if(deg[i] > 0){
stk.push_back({i,deg[i]});
}
else if(deg[i] < 0){
while(deg[i] < 0){
addedge(stk.back().first,i,0);
stk.back().second --;
if(!stk.back().second) stk.pop_back();
}
}
}
};
for(int i = 1; i <= n ; i ++){
pre[i] = pre[i-1] + deg[i];
if(pre[i] < 0){
cout << "No\n";
return;
}
if(pre[i] == 0){
work(last,i);
last = i + 1;
}
}
vector<int> done(n+1);
int cnt = 0;
auto dfs = [&](auto &&self,int u)->void{
done[u] = 1;
cnt ++;
for(int i : v[u]){
if(!done[e[i].r]){
self(self,e[i].r);
}
}
};
dfs(dfs,1);
if(cnt != n){
cout << "No\n";
return;
}
vector<int> p(n+1);
vector<int> euler;
auto dfs2 = [&](auto &&self,int u,int id)->void{
while(p[u] < v[u].size()){
int eid = v[u][p[u]++];
self(self,e[eid].r,eid);
}
euler.push_back(id);
};
dfs2(dfs2,1,-1);
reverse(euler.begin(),euler.end());
vector<int> ans;
for(auto i : euler){
if(i != -1 && e[i].fix){
ans.push_back(e[i].l);
}
}
cout << "Yes\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();
}
}