The 2024 ICPC Asia East Continent Final Contest

互动娱乐活动

Preface

好久没训练了,一想到现在的水平下周要去打 CCPC Final,感觉冲击 Ag 都成问题啊

这场直接头等战犯,先是开局签到 A 连 WA 四发搞崩罚时,后期模拟 E 怒写半天最后还因为 Corner Case 没过

最后把理论 6 题希望冲 Au 的局打崩了,直接俯冲 Cu 区

A. Hitoshizuku

考虑从小到大考虑每个 \(b_i\) 最小的人,显然让他去匹配两个 \(a_j\le b_i\) 且 \(b_j\) 最小的两个人一定最优

实现的时候最好直接搞几个 set 来维护,我赛时写的双指针有一些奇怪的细节要注意,WA 了好多发

#include

#include

#include

#include

#include

#include

#define RI register int

#define CI const int&

using namespace std;

const int N=300005;

int t,n,vis[N]; array a[N],b[N];

int main()

{

for (scanf("%d",&t);t;--t)

{

scanf("%d",&n); n*=3;

for (RI i=1;i<=n;++i)

{

int x,y; scanf("%d%d",&x,&y);

a[i]={x,y,i}; b[i]={y,x,i}; vis[i]=0;

}

sort(a+1,a+n+1); sort(b+1,b+n+1);

bool flag=1; RI pa=1,pb=1,cnt=0;

set > st;

vector > ans;

while (cnt

{

while (pb<=n&&vis[b[pb][2]]) ++pb;

if (pb>n) { flag=0; break; }

vis[b[pb][2]]=1;

while (pa<=n&&a[pa][0]<=b[pb][0])

{

if (vis[a[pa][2]]) { ++pa; continue; }

st.insert({a[pa][1],a[pa][0],a[pa][2]});

++pa;

}

int num=0,x,y;

while (!st.empty())

{

int id=(*st.begin())[2];

st.erase(st.begin());

if (vis[id]) continue;

++num;

if (num==1) x=id; else

if (num==2) { y=id; break; }

}

if (num!=2) { flag=0; break; }

vis[x]=vis[y]=1;

ans.push_back({b[pb][2],x,y});

++cnt;

}

if (!flag) { puts("No"); continue; }

puts("Yes");

for (auto [x,y,z]:ans) printf("%d %d %d\n",x,y,z);

}

return 0;

}

E. Corrupted Scoreboard Log

只能说太久不写代码是这样的,曾经手拿把掐的模拟题现在写的磕磕绊绊,最后还没判 00 的 Corner Case

这题本身没啥思维难度,把整个串分解后,考虑枚举第一题和前面的分界符

剩下每题直接分类讨论一下各种情况即可,注意到每个题的提交次数只有 \(1/2/3\) 位,三位的情况又很特殊可以直接判,因此这部分复杂度是 \(O(2^m)\) 的

#include

#include

#include

#include

#define RI register int

#define CI const int&

using namespace std;

int n,m; string ans; vector vec; vector tp; bool flag;

inline void DFS(CI tot_pro,CI tot_pnt,string ans,CI pro,CI pnt,CI now)

{

// if (tot_pnt==1322&&now

// if (tot_pnt==1322) printf("now = %d,tot_pro = %d, tot_pnt = %d , pro = %d, pnt = %d\n",now,tot_pro,tot_pnt,pro,pnt);

if (now==vec.size())

{

if (pro==tot_pro&&pnt==tot_pnt)

{

cout<

}

return;

}

string tmp_ans=ans;

if (tp[now]==1) // try case

{

if (vec[now]=="1")

{

ans+=" 1 try";

DFS(tot_pro,tot_pnt,ans,pro,pnt,now+1);

ans=tmp_ans;

return;

}

int cur_pnt=0;

for (RI j=0;j

cur_pnt=cur_pnt*10+vec[now][j]-'0';

if (vec[now][0]=='0')

{

if (cur_pnt==0&&vec[now].size()==2)

{

ans+=' ';

for (RI j=0;j

ans+=' '; ans+=vec[now].back(); ans+=" try";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt,now+1);

ans=tmp_ans;

}

return;

}

if (cur_pnt>299) return;

ans+=' ';

for (RI j=0;j

ans+=' '; ans+=vec[now].back(); ans+=" try";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt,now+1);

ans=tmp_ans;

return;

}

if (vec[now][0]!='0') //do not solve this problem

{

int try_times=0;

for (RI j=0;j

try_times=try_times*10+vec[now][j]-'0';

if (try_times<=100)

{

ans+=' ';

for (RI j=0;j

ans+=" tries";

DFS(tot_pro,tot_pnt,ans,pro,pnt,now+1);

ans=tmp_ans;

}

}

if (flag) return;

if (vec[now].size()>=2&&vec[now].back()!='0'&&vec[now].back()!='1') //1 digit

{

int cur_pnt=0;

for (RI j=0;j

cur_pnt=cur_pnt*10+vec[now][j]-'0';

if (vec[now][0]=='0')

{

if (cur_pnt==0&&vec[now].size()==2)

{

ans+=' ';

for (RI j=0;j

ans+=' '; ans+=vec[now].back(); ans+=" tries";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt+(vec[now].back()-'0'-1)*20,now+1);

ans=tmp_ans;

}

} else

if (cur_pnt<=299)

{

ans+=' ';

for (RI j=0;j

ans+=' '; ans+=vec[now].back(); ans+=" tries";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt+(vec[now].back()-'0'-1)*20,now+1);

ans=tmp_ans;

}

}

if (flag) return;

if (vec[now].size()>=3&&vec[now][vec[now].size()-2]!='0') //2 digits

{

int cur_pnt=0;

for (RI j=0;j

cur_pnt=cur_pnt*10+vec[now][j]-'0';

int try_times=0;

for (RI j=vec[now].size()-2;j

try_times=try_times*10+vec[now][j]-'0';

if (vec[now][0]=='0')

{

if (cur_pnt==0&&vec[now].size()==3)

{

ans+=' ';

for (RI j=0;j

ans+=' ';

for (RI j=vec[now].size()-2;j

ans+=" tries";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt+(try_times-1)*20,now+1);

ans=tmp_ans;

}

} else

if (cur_pnt<=299)

{

ans+=' ';

for (RI j=0;j

ans+=' ';

for (RI j=vec[now].size()-2;j

ans+=" tries";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt+(try_times-1)*20,now+1);

ans=tmp_ans;

}

}

if (flag) return;

if (vec[now].size()>=4&&vec[now][vec[now].size()-3]=='1'&&vec[now][vec[now].size()-2]=='0'&&vec[now].back()=='0') // 3 digits (only 100)

{

int cur_pnt=0;

for (RI j=0;j

cur_pnt=cur_pnt*10+vec[now][j]-'0';

if (vec[now][0]=='0')

{

if (cur_pnt==0&&vec[now].size()==4)

{

ans+=' ';

for (RI j=0;j

ans+=' ';

ans+="100 tries";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt+(100-1)*20,now+1);

ans=tmp_ans;

}

} else

if (cur_pnt<=299)

{

ans+=' ';

for (RI j=0;j

ans+=' ';

ans+="100 tries";

DFS(tot_pro,tot_pnt,ans,pro+1,pnt+cur_pnt+(100-1)*20,now+1);

ans=tmp_ans;

}

}

if (flag) return;

}

inline void solve(CI tot_pro)

{

// printf("tot_pro = %d\n",tot_pro);

for (RI i=1;i<=6;++i)

{

string tmp_ans=ans;

if (i>=vec[0].size()) break;

string tmp=vec[0];

// cout<<"tmp = "<

int tot_pnt=0;

for (RI j=0;j

tot_pnt=tot_pnt*10+tmp[j]-'0';

if (tmp[0]=='0')

{

if (tmp.size()-i!=1) continue;

}

vec[0]="";

for (RI j=tmp.size()-i;j

vec[0]+=tmp[j];

for (RI j=0;j

// cout<<"i = "<

DFS(tot_pro,tot_pnt,ans,0,0,0);

if (flag) return;

vec[0]=tmp;

ans=tmp_ans;

}

}

int main()

{

ios::sync_with_stdio(0); cin.tie(0);

cin>>n>>m;

for (RI id=1;id<=n;++id)

{

vec.clear(); tp.clear(); flag=0;

string s; cin>>s; string tmp="";

if (s=="00") { cout<<"0 0"<

for (RI i=0;i

{

if (s[i]=='t')

{

vec.push_back(tmp); tmp="";

if (s[i+2]=='y') tp.push_back(1),i+=2;

else tp.push_back(0),i+=4;

continue;

}

tmp+=s[i];

}

int d=vec[0][0]-'0';

vec[0].erase(vec[0].begin());

ans=""; ans+=char(d+'0'); ans+=' '; solve(d);

if (flag) continue;

int dd=vec[0][0]-'0';

vec[0].erase(vec[0].begin());

ans=""; ans+=char(d+'0'); ans+=char(dd+'0'); ans+=' '; solve(d*10+dd);

}

return 0;

}

F. Boolean Function Reconstruction

被徐神单切力

考虑一种很 trivial 的构造方式,对于所有 \(f(i)=1\) 的状态 \(i\),直接把它对应的与表达式或到最后的答案中即可

更进一步的观察,我们可以发现由于序列中没有取反,因此如果状态 \(f(x)=1\),那么 \(x\) 的所有超集 \(y\) 都满足 \(f(y)=1\),因此可以贪心地构造最小的那些表示状态

但如果直接这么做还是会超过符号限制,因此我们不妨合并一些子项,即按顺序提取出每一个字符,再将剩下的子项按照含有/不含有这个字符分类,递归构造即可

#include

namespace check {

using bitset = std::bitset<1 << 15>;

bitset val[15];

bitset eval(const char *&s) {

if(isalpha(*s)) return val[*s++ - 'a'];

assert(*s == '(');

auto val1 = eval(++s);

const char *p1 = s;

auto val2 = eval(++s);

assert(*s++ == ')');

if(*p1 == '&') return val1 & val2;

else return val1 | val2;

}

std::string check(int n, std::string s) {

for(int i = 0; i < n; ++i) for(int j = 0; j < (1 << n); ++j)

val[i][j] = (j >> i & 1);

const char *p = s.c_str();

auto result = eval(p);

std::stringstream res;

for(int i = 0; i < (1 << n); ++i) res << result[i];

return res.str();

}

}

void generate(int i, const std::vector &s, std::stringstream &ss) {

std::vector a, b;

int aa = 0;

for(auto s: s) {

if(s >> i & 1) {

if(s == (1 << i)) aa = 1;

else a.push_back(s ^ (1 << i));

} else b.push_back(s);

}

if((a.size() || aa) && b.size()) ss << '(';

if(a.size()) ss << '(';

if(aa || a.size()) ss << char(i + 'a');

if(a.size()) ss << '&', generate(i + 1, a, ss), ss << ')';

if((a.size() || aa) && b.size()) ss << '|';

if(b.size()) generate(i + 1, b, ss);

if((a.size() || aa) && b.size()) ss << ')';

return ;

}

void work() {

int n; std::cin >> n;

std::string input;

std::vector c(1 << n); {

std::cin >> input;

for(int i = 0; i < (1 << n); ++i) c[i] = (input[i] == '1');

}

int op_count = 0;

int count0 = 0;

for(int i = 0; i < (1 << n); ++i) count0 += (c[i] == 0);

if(count0 == 0 ) return std::cout << "Yes\nT\n", void(0);

if(count0 == (1 << n)) return std::cout << "Yes\nF\n", void(0);

if(c[0] == 1) return std::cout << "No\n", void(0);

std::vector d(1 << n, 0);

std::vector ans;

for(int i = 1; i < (1 << n); ++i) {

if(d[i] && !c[i]) return std::cout << "No\n", void(0);

if(d[i] == c[i]) continue;

ans.push_back(i);

for(int S = ((1 << n) - 1) ^ i; ; S = (((1 << n) - 1) ^ i) & (S - 1)) {

d[S | i] = 1;

if(S == 0) break;

}

}

std::cout << "Yes\n";

std::stringstream anss;

generate(0, ans, anss);

std::cout << anss.str() << char(10);

return ;

}

int main() {

// freopen("data.in", "r", stdin);

// freopen("data.out", "w", stdout);

std::ios::sync_with_stdio(false);

int T; std::cin >> T; while(T--) work();

fclose(stdin);

return 0;

}

G. Collatz Conjecture

首先注意到 \([1,B]\) 中的所有数都一定在某个环上

这是因为对于某个 \(x\in [1,B]\),它在加上了若干次 \(B\) 并成为 \(y\times A\) 后,除去 \(A\) 变为的 \(y\) 一定也 \(\in[1,B]\)

不妨假设初始的 \(n\) 在环上,则它一定由某个 \(r\in[1,B]\) 不断加 \(B\) 得来,很容易发现这个 \(r\) 和 \(n\) 模 \(B\) 意义下同余

定下 \(r\) 后判断 \(n\) 是否合法只要找出环上最大的值即可,设进行了 \(x\) 次加 \(B\) 操作,则有 \(r+x\times B=y\times A\),用 exgcd 解出最小的 \(x\) 即可

#include

using namespace std;

#define int long long

int ex_gcd(int a, int b, int &x, int &y) {

if (0 == b) {x = 1; y = 0; return a;}

int ret = ex_gcd(b, a%b, y, x);

y -= a / b * x;

return ret;

}

int calc(int A, int B, int R) { //xA + yB = R 的最小正整数 x

int x0, y0;

int g = ex_gcd(A, B, x0, y0);

int x00 = x0 * (R / g), y00 = y0 * (R / g);

int x1 = B / g, y1 = A / g;

return ((x00 % x1) + x1) % x1;

}

bool solve() {

int A, B, n; cin >> A >> B >> n;

int r = n % B;

if (0 == r) r = B;

int x = calc(B, A, -r);

// printf("x = %d\n", x);

int n0 = r + x * B;

return n <= n0;

}

signed main() {

ios::sync_with_stdio(0); cin.tie(0);

int T; cin >> T; while (T--) cout << (solve() ? "Yes\n" : "No\n");

return 0;

}

H. Staircase Museum

我写 E 的时候队友把这题讨论出来了,但后面因为时间原因也没写完

赛后看了下队友的写法好像和官方题解不太一样,感觉很神奇

#include

using namespace std;

const int INF = (int)1e9+5;

#define ft first

#define sd second

const int N = 2e6+50;

int l[N], r[N], yyy[4][N], dp[N], deg[N];

vector ul, dr;

vector G[N];

map, int> id; int cntid=0;

struct Node {

int x, y, typ;

};

vector que; int ed=-1, fr=0;

// int allocate(pair pr) {

// if (!id.count(pr)) id[pr] = ++cntid;

// return id[pr];

// }

int getdist() {

vector que2; int ed2=-1, fr2=0;

for (int i=0; i<=cntid; ++i) {

if (deg[i]==0) que2.push_back(i), ++ed2;

}

while (fr2 <= ed2) {

int x = que2[fr2++];

// printf("x=%d fr2=%d ed2=%d\n", x, fr2, ed2);

for (int v : G[x]) if (deg[v]>0){

// printf("v=%d\n", v);

dp[v] = max(dp[v], dp[x]+1);

--deg[v];

if (0 == deg[v]) que2.push_back(v), ++ed2;

}

}

return dp[0];

}

int solve() {

int n; cin >> n;

for (int i=1; i<=n; ++i) cin >> l[i] >> r[i];

for (int i=0; i<=n; ++i) {

if (i+1 <= n) yyy[0][i] = r[i+1]; else yyy[0][i] = INF;

if (i > 0) yyy[1][i] = r[i]; else yyy[1][i] = r[1];

if (i+1 <= n) yyy[2][i] = l[i+1]-1; else yyy[0][i] = INF;

if (i > 0) yyy[3][i] = l[i]-1; else yyy[3][i] = l[1];

}

ul.push_back(-1);

dr.push_back(-1);

ul.push_back(0);

for (int i=1; i

if (yyy[0][i] != yyy[0][i-1]) ul.push_back(i);

}

for (int i=1; i

if (yyy[3][i] != yyy[3][i+1]) dr.push_back(i);

}

dr.push_back(n);

auto pr1 = make_pair(ul.back(), yyy[0][ul.back()]);

// printf("ul:(%d %d)\n", ul.back(), yyy[0][ul.back()]);

que.push_back(Node{pr1.ft, pr1.sd, 0}); ++ed;

id[pr1] = ++cntid;

dp[cntid] = 0;

auto pr2 = make_pair(dr.back(), yyy[3][dr.back()]);

// printf("dr:(%d %d)\n", dr.back(), yyy[3][dr.back()]);

que.push_back(Node{pr2.ft, pr2.sd, 1}); ++ed;

id[pr2] = ++cntid;

dp[cntid] = 0;

while (fr <= ed) {

auto [x, y, typ] = que[fr++];

int cur = id[make_pair(x, y)];

// printf("x=%d y=%d typ=%d\n", x, y, typ);

if (0 == typ) {

if (x > 0) {

int x1 = x;

int y1 = yyy[3][x];

int tmpid;

if (!id.count({x1, y1})) {

que.push_back(Node{x1, y1, 1}); ++ed;

id[{x1, y1}] = ++cntid;

tmpid = cntid;

} else {

tmpid = id[{x1, y1}];

}

G[cur].push_back(tmpid);

++deg[tmpid];

} else {

G[cur].push_back(0);

++deg[0];

}

int x0 = *prev(lower_bound(ul.begin(), ul.end(), x));

// printf("x0=%d\n", x0);

if (x0 > -1) {

int y0 = yyy[0][x0];

int tmpid;

if (!id.count({x0, y0})) {

que.push_back(Node{x0, y0, 0}); ++ed;

id[{x0, y0}] = ++cntid;

tmpid = cntid;

} else {

tmpid = id[{x0, y0}];

}

G[cur].push_back(tmpid);

++deg[tmpid];

}

} else {

if (y > l[1]-1) {

int x0 = lower_bound(yyy[0], yyy[0]+n+1, y) - yyy[0];

int y0 = y;

int tmpid;

if (!id.count({x0, y0})) {

que.push_back(Node{x0, y0, 0}); ++ed;

id[{x0, y0}] = ++cntid;

tmpid = cntid;

} else {

tmpid = id[{x0, y0}];

}

G[cur].push_back(tmpid);

++deg[tmpid];

} else {

G[cur].push_back(0);

++deg[0];

}

int x1 = *prev(lower_bound(dr.begin(), dr.end(), x));

if (x1 > -1) {

int y1 = yyy[3][x1];

int tmpid;

if (!id.count({x1, y1})) {

que.push_back(Node{x1, y1, 1}); ++ed;

id[{x1, y1}] = ++cntid;

tmpid = cntid;

} else {

tmpid = id[{x1, y1}];

}

G[cur].push_back(tmpid);

++deg[tmpid];

}

}

}

// for (int i=1; i<=cntid; ++i) {

// for (int v : G[i]) {

// dp[v] = max(dp[v], dp[i]+1);

// }

// }

int ans = getdist();

// printf("y1:"); for (int i=0; i<=n; ++i) printf("%d ", yyy[1][i]); puts("");

// for (auto [pri, idd] : id) {

// printf("(%d %d) %d\n", pri.ft, pri.sd, idd);

// }

// for (int i=1; i<=cntid; ++i) {

// printf("%d:", i);

// for (int v : G[i]) {

// printf("%d ", v);

// }puts("");

// }

// printf("dp:"); for (int i=0; i<=cntid; ++i) printf("%d ", dp[i]); puts("");

for (int i=0; i<=cntid; ++i) {

dp[i] = -1;

deg[i] = 0;

G[i].clear();

}

ul.clear(); dr.clear();

id.clear(); cntid = 0;

ed = -1, fr = 0; que.clear();

return ans;

}

signed main() {

ios::sync_with_stdio(0); cin.tie(0);

int T; cin >> T; while (T--) cout << solve() << '\n';

return 0;

}

I. Color-Balanced Tree

应该也是个签到构造,我题目都没看

#include

using namespace std;

const int N = 2e5+5;

int n, col[N], cnt[2];

int ed=-1, fr=0, que[N];

vector G[N];

void solve() {

cin >> n;

ed = -1, fr = 0;

cnt[0] = cnt[1] = 0;

for (int i=1; i<=2*n; ++i) {

col[i] = -1;

G[i].clear();

}

for (int i=1; i<2*n; ++i) {

int u, v; cin >> u >> v;

G[u].push_back(v);

G[v].push_back(u);

}

col[1] = 1;

++cnt[1];

que[++ed] = 1;

while (fr<=ed) {

int x = que[fr++];

bool isleaf = true;

for (int v : G[x]) if (-1 == col[v]) {

col[v] = col[x]^1;

++cnt[col[v]];

que[++ed] = v;

isleaf = false;

}

if (isleaf) {

--cnt[col[x]];

col[x] = -1;

}

}

// printf("n=%d\n", n);

for (int i=1; i<=2*n; ++i) {

if (-1==col[i]) {

if (cnt[0] <= cnt[1]) {

++cnt[0];

col[i] = 0;

} else {

++cnt[1];

col[i] = 1;

}

}

cout << col[i] << (i==2*n ? '\n' : ' ');

}

}

Postscript

五一训练保三争四吧,不然现在的水平和状态包去被薄纱的

2020勇士球员名单 库里与拉塞尔组成双枪
亚美尼亚国家足球队