The 2021 ICPC Asia East Continent Final Contest (EC-Final 2021)
Preface
估计是最后一场 VP 的比赛了,这场选了四年前的 ECF 真题,然而因为中后期题里有做过的题感觉也没训到什么东西
这场后期让机给队友写几何,导致会了的 J 也没时间写,我也是直接三个半小时美美下班看云顶比赛去了
A. DFS Order
签到,下界是深度,上界是总点数减去该点的子树大小
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,dep[N],sz[N]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
sz[now]=1; dep[now]=dep[fa]+1;
for (auto to:v[now])
if (to!=fa) DFS(to,now),sz[now]+=sz[to];
}
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d",&n);
for (RI i=1;i<=n;++i) v[i].clear();
for (RI i=1;i<n;++i)
{
int x,y; scanf("%d%d",&x,&y);
v[x].push_back(y);
}
dep[0]=0; DFS();
for (RI i=1;i<=n;++i)
printf("%d %d\n",dep[i],n-sz[i]+1);
}
return 0;
}
B. Beautiful String
队友写的,好像是个字符串+计数的经典题,我题都没看
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr int $n = 5003;
constexpr uint32_t mod1 = 998244853, mod2 = 1000000009;
struct Hasher {
uint32_t h1, h2;
inline friend Hasher operator +(const Hasher &lhs, const Hasher &rhs) {
return { (lhs.h1 + rhs.h1) % mod1, (lhs.h2 + rhs.h2) % mod2 };
}
inline friend Hasher operator -(const Hasher &lhs, const Hasher &rhs) {
return { (lhs.h1 - rhs.h1 + mod1) % mod1, (lhs.h2 - rhs.h2 + mod2) % mod2 };
}
inline friend Hasher operator *(const Hasher &lhs, const Hasher &rhs) {
return {
(uint32_t)(((uint64_t)lhs.h1 * rhs.h1) % mod1),
(uint32_t)(((uint64_t)lhs.h2 * rhs.h2) % mod2)
};
}
inline friend bool operator ==(const Hasher &lhs, const Hasher &rhs) {
return lhs.h1 == rhs.h1 && lhs.h2 == rhs.h2;
}
} seed{42, 37}, seedExp[$n];
void prep(int n = 5000) {
seedExp[0] = Hasher{1, 1};
for(int i = 1; i <= n; i++) seedExp[i] = seedExp[i - 1] * seed;
}
Hasher hsh[$n];
void calc_hash(const char *s) {
hsh[0] = {0, 0};
for(int i = 1; s[i]; i++)
hsh[i] = hsh[i - 1] * seed + Hasher{ (uint32_t)s[i] - '0', (uint32_t)s[i] - '0' };
}
inline Hasher substr(int l, int r) {
return hsh[r] - hsh[l - 1] * seedExp[r - l + 1];
}
llsi ars[$n][$n];
int z[$n];
void get_z(const char *s, int n) {
z[1] = n;
for(int i = 2, l = 0, r = 0; i <= n; ++i) {
if(i > r) r = i; else
if(i + z[1 + i - l] < r) {
z[i] = z[1 + i - l];
continue;
}
l = i;
while(r <= n && s[r] == s[1 + r - l]) r += 1;
z[i] = r - l;
}
return ;
}
void work() {
int n;
std::string s; std::cin >> s;
n = s.size();
s = std::string("#") + s;
calc_hash(s.c_str());
for(int i = 1; i <= n; ++i) {
memset(ars[i], 0, sizeof(int) * (n + 1));
for(int j = 1; j <= std::min(i, n - i); ++j) {
ars[i][j] = ars[i][j - 1] +
int(substr(i - j + 1, i) == substr(i + 1, i + j));
}
for(int j = std::min(i, n - 1) + 1; j <= n; ++j) {
ars[i][j] += ars[i][j - 1];
}
for(int j = 1; j <= n; ++j) {
ars[i][j] += ars[i][j - 1];
}
}
// std::cerr << "ars:\n";
// for(int i = 1; i <= n; ++i) {
// for(int j = 1; j <= std::min(i, n - i); ++j) {
// std::cerr << ars[i][j] << char(j == std::min(i, n - i) ? 10 : 32);
// }
// }
llsi ans = 0;
for(int i = 2; i <= n; ++i) {
get_z(s.c_str() + i - 1, n - i + 1);
// std::cerr << "z[" << i << " .. n] = ";
// for(int j = 1; j <= n - i + 1; ++j) std::cerr << z[j] << char(j == n - i + 1 ? 10 : 32);
for(int j = 4; j <= n - i; ++j) {
llsi hkr = std::max(std::min(z[j], j - 2), 0);
llsi upd = ars[i - 1][hkr - 1];
// std::cerr << upd << char(j == n - i ? 10 : 32);
ans += upd;
}
}
std::cout << ans << char(10);
return ;
}
int main() {
std::ios::sync_with_stdio(false);
prep();
int T; std::cin >> T; while(T--) work();
return 0;
}
D. Two Walls
神秘几何,队友赛时最后一直在 Rush 这个题,但一直 WA 在二十多个点,只能说几何是这样的
E. Prof. Pang and Poker
思博题,想清楚就不难的一个题
设 \(P\) 的那张牌权值为 \(p\),首先按顺序判掉一些显然的 Case:
- \(n=1\),此时必败
- \(A,B\) 中任意一个人没有 \(<p\) 的牌,此时必败
- \(B\) 中 \(<p\) 的牌数量 \(>1\),此时必胜,因为此时 \(A\) 可以把出牌权丢给 \(B\) 然后让其一直出,最后总能获胜
令 \(B\) 那张 \(<p\) 的牌权值为 \(q\),对于剩下的局面,想要获胜需要满足以下四个条件:
- \(A\) 中 \(<p\) 的牌数量 \(>1\)
- \(A\) 所有牌的最大值大于 \(B\) 所有牌的最大值
- \(A\) 存在一张权值在 \([q,p)\) 中的牌
- \(n\ge 4\)
策略的话就是让 \(A\) 先出一张无关紧要的 \(<p\) 的牌,然后把出牌权过给 \(B\)
让 \(B\) 一直出牌,直到 \(B\) 手中只剩下 \(q\) 时将其管上,然后打出权值在 \([q,p)\) 中的牌的即可
其中 \(n\ge 4\) 的条件非常隐蔽,因为 \(n=3\) 的极端情况 \(A\) 出完权值在 \([q,p)\) 中的牌后会直接跑掉
最后还需要注意特判 \(m=1\) 的情况,具体实现看代码
#include<cstdio>
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int t,n,m;
int main()
{
ios::sync_with_stdio(false); cin.tie(0);
for (cin>>t;t;--t)
{
cin>>n>>m;
vector <int> A,B;
auto trs=[&](const char& ch)
{
if ('2'<=ch&&ch<='9') return ch-'0'; else
if (ch=='T') return 10; else
if (ch=='J') return 11; else
if (ch=='Q') return 12; else
if (ch=='K') return 13; else
return 14;
};
for (RI i=1;i<=n;++i)
{
string s; cin>>s;
A.push_back(trs(s[0]));
}
for (RI i=1;i<=m;++i)
{
string s; cin>>s;
B.push_back(trs(s[0]));
}
string s; cin>>s;
int P=trs(s[0]);
if (n==1) { puts("Shou"); continue; }
int less_A=0,less_B=0;
for (auto x:A) if (x<P) ++less_A;
for (auto x:B) if (x<P) ++less_B;
if (less_A==0||less_B==0) { puts("Shou"); continue; }
if (m==1)
{
int Q;
for (auto x:B) if (x<P) Q=x;
bool grt_Q_less_P=0;
for (auto x:A) if (x>=Q&&x<P) grt_Q_less_P=1;
puts(grt_Q_less_P?"Pang":"Shou");
continue;
}
if (less_B>1) { puts("Pang"); continue; }
int Q;
for (auto x:B) if (x<P) Q=x;
bool grt_Q_less_P=0;
for (auto x:A) if (x>=Q&&x<P) grt_Q_less_P=1;
int max_A=*max_element(A.begin(),A.end());
int max_B=*max_element(B.begin(),B.end());
if (n>=4&&grt_Q_less_P&&less_A>=2&&max_A>max_B) puts("Pang"); else puts("Shou");
}
return 0;
}
H. Check Pattern is Good
暑假前集训做过这题,做法可以看 这里
但是这题还需要输出方案,二分图的最大独立集方案的构造可以参考 这里
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
#include<array>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,m,tp[N][N],num[N][N][2]; char s[N][N]; array <int,3> rst[N*N<<1];
namespace Network_Flow
{
const int NN=20005,M=10*NN,INF=1e9;
struct edge
{
int to,nxt,v;
}e[M<<1]; int cnt,head[NN],cur[NN],dep[NN],S,T,in_match[NN],tag[NN]; queue <int> q;
inline void init(void)
{
cnt=1; memset(head,0,T+1<<2); memset(in_match,0,T+1<<2); memset(tag,0,T+1<<2);
}
inline void addedge(CI x,CI y,CI z)
{
e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
e[++cnt]=(edge){x,head[y],0}; head[y]=cnt;
}
#define to e[i].to
inline bool BFS(void)
{
memset(dep,0,T+1<<2); dep[S]=1; q=queue <int>(); q.push(S);
while (!q.empty())
{
int now=q.front(); q.pop();
for (RI i=head[now];i;i=e[i].nxt)
if (e[i].v&&!dep[to]) dep[to]=dep[now]+1,q.push(to);
}
return dep[T];
}
inline int DFS(CI now,CI tar,int dis)
{
if (now==tar) return dis; int ret=0;
for (RI& i=cur[now];i&&dis;i=e[i].nxt)
if (e[i].v&&dep[to]==dep[now]+1)
{
int dist=DFS(to,tar,min(dis,e[i].v));
dis-=dist; ret+=dist; e[i].v-=dist; e[i^1].v+=dist;
}
if (!ret) dep[now]=0; return ret;
}
inline void travel(CI now)
{
if (tag[now]) return; tag[now]=1;
for (RI i=head[now];i;i=e[i].nxt)
if (to!=S&&to!=T&&e[i].v) travel(to);
}
#undef to
inline int Dinic(int ret=0)
{
while (BFS()) memcpy(cur,head,T+1<<2),ret+=DFS(S,T,INF); return ret;
}
};
using namespace Network_Flow;
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i,j,p,q; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",s[i]+1);
if (n==1||m==1)
{
puts("0");
for (RI i=1;i<=n;++i,putchar('\n'))
for (RI j=1;j<=m;++j)
{
if (s[i][j]=='?') s[i][j]='B';
putchar(s[i][j]);
}
continue;
}
int idx=0; S=0; T=2*n*m+1; init();
for (i=1;i<=n;++i) for (j=1;j<=m;++j)
if ((i+j)&1) if (s[i][j]!='?') s[i][j]=(s[i][j]=='B'?'W':'B');
vector <int> L,R;
for (i=1;i<n;++i) for (j=1;j<m;++j)
{
int cnt_W=0,cnt_B=0;
for (p=0;p<2;++p) for (q=0;q<2;++q)
{
if (s[i+p][j+q]=='W') ++cnt_W;
if (s[i+p][j+q]=='B') ++cnt_B;
}
if (!cnt_W&&!cnt_B)
{
rst[num[i][j][0]=++idx]={i,j,0};
rst[num[i][j][1]=++idx]={i,j,1};
L.push_back(num[i][j][0]); R.push_back(num[i][j][1]);
tp[i][j]=2; addedge(num[i][j][0],num[i][j][1],1);
addedge(S,num[i][j][0],1); addedge(num[i][j][1],T,1);
} else if (!cnt_B)
{
rst[num[i][j][0]=++idx]={i,j,0};
L.push_back(num[i][j][0]);
tp[i][j]=0; addedge(S,num[i][j][0],1);
} else if (!cnt_W)
{
rst[num[i][j][1]=++idx]={i,j,1};
R.push_back(num[i][j][1]);
tp[i][j]=1; addedge(num[i][j][1],T,1);
} else tp[i][j]=3;
}
auto check=[&](CI x,CI y,CI c)
{
if (tp[x][y]==3) return false; if (tp[x][y]==2) return true; return tp[x][y]==c;
};
for (i=1;i<=n;++i) for (j=1;j<=m;++j) if (s[i][j]=='?')
{
if (i-1<1||i+1>n||j-1<1||j+1>m) continue;
if (check(i,j-1,0)&&check(i-1,j,1)) addedge(num[i][j-1][0],num[i-1][j][1],1);
if (check(i-1,j,0)&&check(i,j-1,1)) addedge(num[i-1][j][0],num[i][j-1][1],1);
if (check(i-1,j-1,0)&&check(i,j,1)) addedge(num[i-1][j-1][0],num[i][j][1],1);
if (check(i,j,0)&&check(i-1,j-1,1)) addedge(num[i][j][0],num[i-1][j-1][1],1);
}
for (i=1;i<=n;++i) for (j=1;j<m;++j) if (s[i][j]=='?'&&s[i][j+1]=='?')
{
if (i-1<1||i+1>n) continue;
if (check(i-1,j,0)&&check(i,j,1)) addedge(num[i-1][j][0],num[i][j][1],1);
if (check(i,j,0)&&check(i-1,j,1)) addedge(num[i][j][0],num[i-1][j][1],1);
}
for (i=1;i<n;++i) for (j=1;j<=m;++j) if (s[i][j]=='?'&&s[i+1][j]=='?')
{
if (j-1<1||j+1>m) continue;
if (check(i,j-1,0)&&check(i,j,1)) addedge(num[i][j-1][0],num[i][j][1],1);
if (check(i,j,0)&&check(i,j-1,1)) addedge(num[i][j][0],num[i][j-1][1],1);
}
int res=idx-Dinic();
printf("%d\n",res);
for (auto x:L)
for (RI i=head[x];i;i=e[i].nxt)
if (e[i].to!=S&&e[i].v==0) in_match[x]=in_match[e[i].to]=1;
for (auto x:L)
{
if (in_match[x]) continue;
travel(x);
}
vector <int> ans;
for (auto x:L)
if (tag[x]) ans.push_back(x);
for (auto x:R)
if (!tag[x]) ans.push_back(x);
assert((int)ans.size()==res);
for (auto id:ans)
{
auto [x,y,c]=rst[id];
for (p=0;p<2;++p) for (q=0;q<2;++q)
if (s[x+p][y+q]=='?') s[x+p][y+q]=(c?'B':'W');
}
for (RI i=1;i<=n;++i) for (RI j=1;j<=m;++j)
if (s[i][j]=='?') s[i][j]='B';
for (i=1;i<=n;++i) for (j=1;j<=m;++j)
if ((i+j)&1) s[i][j]=s[i][j]=='B'?'W':'B';
for (RI i=1;i<=n;++i,putchar('\n'))
for (RI j=1;j<=m;++j) putchar(s[i][j]);
}
return 0;
}
I. Future Coder
队友写的签,我题意都不知道
#include <bits/stdc++.h>
void work() {
int n; std::cin >> n;
int64_t ans = 0;
int n2 = 0, n1 = 0, ze = 0, p1 = 0, p2 = 0;
while(n--) {
int a; std::cin >> a;
if(a <= -2) {
ans += p1 + p2;
n2 += 1;
} else
if(a == -1) {
ans += p1 + p2;
n1 += 1;
} else
if(a == 0) {
ans += p1 + p2;
ze += 1;
} else
if(a == 1) {
ans += n2 + n1 + ze + p1 + p2;
p1 += 1;
} else
/* a >= 2 */ {
ans += n2 + n1 + ze + p1;
p2 += 1;
}
}
std::cout << ans << char(10);
}
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) work();
return 0;
}
J. Elden Ring
本质最短路题,赛后补了下发现非常好写
显然 \(A\le B\) 和 \(A>B\) 是两个问题,因为此时每个点能访问的时间是一段前缀/后缀
前者非常简单,因为沿着最短路走一定最优,只不过中途有些点可能会无法访问,直接 BFS 一遍即可
后者考虑求出每个点最早访问时间 \(t_i\) 后,其实就是把最短路的转移结果对 \(t_i\) 取 \(\max\)
但需要注意的是我们要判断在某个时刻能否真的走到一个点
考虑 dijkstra 的过程中,我们是按 \(dis\) 顺序枚举点的,因此可以很容易地维护出 \(dis\) 小于某个点的点数量,用这个值判断能否激活该点即可
具体实现看代码
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e9;
int t,n,m,A,B,l[N],dis[N]; vector <int> v[N];
namespace Case1 // A<=B
{
inline void solve(void)
{
queue <int> q;
dis[1]=0; q.push(1);
while (!q.empty())
{
int now=q.front(); q.pop();
for (auto to:v[now])
{
if (dis[to]!=INF) continue;
if (l[1]+1LL*(A-B)*dis[now]<=l[to]) continue;
dis[to]=dis[now]+1; q.push(to);
}
}
}
}
namespace Case2 // A>B
{
inline void solve(void)
{
static int vis[N];
for (RI i=1;i<=n;++i) vis[i]=0;
for (RI i=2;i<=n;++i)
if (l[i]<l[1]) l[i]=1; else l[i]=(l[i]-l[1])/(A-B)+2;
priority_queue <pair <int,int>> hp;
l[1]=dis[1]=0; hp.push({0,1}); int cnt=0;
while (!hp.empty())
{
auto [_,now]=hp.top(); hp.pop();
if (vis[now]) continue; vis[now]=1;
if (cnt<dis[now]) { dis[now]=INF; continue; }
++cnt;
for (auto to:v[now])
{
if (dis[to]>max(dis[now]+1,l[to]))
{
dis[to]=max(dis[now]+1,l[to]);
hp.push({-dis[to],to});
}
}
}
}
}
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d%d%d",&n,&m,&A,&B);
for (RI i=1;i<=n;++i) dis[i]=INF,v[i].clear();
for (RI i=1;i<=m;++i)
{
int x,y; scanf("%d%d",&x,&y);
v[x].push_back(y); v[y].push_back(x);
}
for (RI i=1;i<=n;++i)
{
scanf("%d",&l[i]);
if (i>=2) l[i]+=B;
}
if (A<=B) Case1::solve(); else Case2::solve();
if (dis[n]==INF) puts("-1"); else printf("%d\n",dis[n]);
}
return 0;
}
L. Fenwick Tree
队友写的,我题目都没看
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
set<int> cnt[N];
int lb(int x) {return x&(-x);}
int solve() {
int n; cin >> n;
int ans = 0;
string str; cin >> str; str = '0'+str;
for (int i=1; i<=n; ++i) cnt[i].clear();
for (int i=1; i<=n; ++i) {
if (str[i]=='1') {
if (cnt[i].empty()) {
++ans;
for (int j=i; j<=n; j+=lb(j)) cnt[j].insert(i);
} else {
for (int j=i+lb(i); j<=n; j+=lb(j)) {
for (int x : cnt[i]) cnt[j].erase(x);
}
cnt[i].clear();
for (int j=i; j<=n; j+=lb(j)) cnt[j].insert(i);
}
} else {
if (cnt[i].size() == 1) ++ans;
for (int j=i+lb(i); j<=n; j+=lb(j)) {
for (int x : cnt[i]) cnt[j].erase(x);
}
cnt[i].clear();
}
}
return ans;
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T; while (T--) cout << solve() << '\n';
return 0;
}
Postscript
感觉现在的自己非常缺少对于算竞的 passion 啊,再也没法在 VP 或是比赛中收获快乐了,感觉是真的到了要告别的时候了

浙公网安备 33010602011771号