The 2022 ICPC Asia East Continent Final Contest
Preface
5.5 VP 的比赛拖到 5.9 才写博客,而且要不是因为飞机延误我估计都不会想到这事
这场经典的前期炸穿,写什么挂什么,到 2.5h 的时候才过了 2 题
中后期开始发力后勉强冲到了 7 题,可惜的是最后一个 L 差了 10min,少判了一种 Corner Case
C. Best Carry Player 2
这题好久之前被搬到过队内赛里,由于祁神声称他没有印象了就扔给他写了
考虑 DP,令 \(f_{i,j,0/1}\) 表示处理了低 \(i\) 位,共进了 \(j\) 位,上一位进/不进位所需的最小数
注意由于可能出现 \(x=10^{18},k=18\) 这种情况,DP 数组可能需要开到 \(2k\) 的长度
一种好的解决方案是预处理掉所有末尾的零,这样就可以避免这种情形了
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+5;
const int INF = (int)4e18 + 5;
int x, k;
int pw[20];
int dp[20][20][2];
int num[20];
int zero;
int solve1() {
for (int i=0; i<18; ++i) {
num[i] = x%10;
x /= 10;
}
for (int i=0; i<18; ++i) for (int j=0; j<=18; ++j) dp[i][j][0] = dp[i][j][1] = INF;
dp[0][0][0] = 0;
if (num[0] != 0) dp[0][1][1] = 10 - num[0];
for (int i=1; i<18; ++i) {
for (int j=0; j<=k; ++j) {
dp[i][j][0] = dp[i-1][j][0];
if (num[i] < 9) dp[i][j][0] = min(dp[i][j][0], dp[i-1][j][1]);
if (j>0) {
dp[i][j][1] = (9-num[i])*pw[i]+dp[i-1][j-1][1];
if (num[i] > 0) dp[i][j][1] = min((10-num[i])*pw[i]+dp[i-1][j-1][0], dp[i][j][1]);
}
}
}
// printf("111111\n");
// if (min(dp[17][k][0], dp[17][k][1]) == INF) return -1;
return min(dp[17][k][0], dp[17][k][1]);
}
void solve() {
cin >> x >> k;
int tmp = x;
if (0 == k) {
for (int i=0; i<=18; ++i) {
if (tmp % 10 < 9) {
cout << pw[i] << '\n';
return;
} else tmp /= 10;
}
}
zero = 0;
while (x%10 == 0) {
x /= 10;
++zero;
}
cout << solve1();
for (int i=0; i<zero; ++i) cout << '0';
cout << '\n';
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
pw[0] = 1;
for (int i=1; i<=18; ++i) pw[i] = pw[i-1]*10;
int T; cin >> T; while (T--) solve();
return 0;
}
E. Map
不难发现传送操作一定在开始和结束进行,因此我们不妨直接枚举前后传送的次数
这题的难点在于根据输入求出这个变换的转移矩阵,徐神的写法是直接大力解方程
#include <bits/stdc++.h>
using real = long double;
struct vivi {
real x, y;
friend std::istream& operator >> (std::istream &in, vivi &k) {
int _x, _y;
in >> _x >> _y;
k.x = _x, k.y = _y;
return in;
}
friend std::ostream& operator << (std::ostream &out, const vivi &k) {
return out << k.x << ' ' << k.y;
}
real len(const vivi &rhs) const {
return std::hypot(x - rhs.x, y - rhs.y);
}
};
real d3(
real a1, real a2, real a3,
real b1, real b2, real b3,
real c1, real c2, real c3
) {
return
a1 * (b2 * c3 - b3 * c2) -
a2 * (b1 * c3 - b3 * c1) +
a3 * (b1 * c2 - b2 * c1);
}
std::tuple<real, real, real, real, real, real> solve(
real x1, real x2, real x3,
real y1, real y2, real y3,
real z1, real z2, real z3,
real w1, real w2, real w3
) {
// a b c
// | x1 y1 1 | = z1
// | x2 y2 1 | = z2
// | x3 y3 1 | = z3
real q = d3(
x1, y1, 1,
x2, y2, 1,
x3, y3, 1
);
return {
d3(
z1, y1, 1,
z2, y2, 1,
z3, y3, 1
) / q,
d3(
x1, z1, 1,
x2, z2, 1,
x3, z3, 1
) / q,
d3(
x1, y1, z1,
x2, y2, z2,
x3, y3, z3
) / q,
d3(
w1, y1, 1,
w2, y2, 1,
w3, y3, 1
) / q,
d3(
x1, w1, 1,
x2, w2, 1,
x3, w3, 1
) / q,
d3(
x1, y1, w1,
x2, y2, w2,
x3, y3, w3
) / q,
};
}
void work() {
vivi a1, b1, c1, d1;
vivi a2, b2, c2, d2;
std::cin >> a1 >> b1 >> c1 >> d1;
std::cin >> a2 >> b2 >> c2 >> d2;
vivi s, t;
std::cin >> s >> t;
int n, k;
std::cin >> k >> n;
auto [a, b, c, d, e, f] = solve(
a1.x, b1.x, c1.x,
a1.y, b1.y, c1.y,
a2.x, b2.x, c2.x,
a2.y, b2.y, c2.y
);
auto jump = [&](const vivi &v) -> vivi {
return vivi {
a * v.x + b * v.y + c,
d * v.x + e * v.y + f
};
};
// std::cerr << std::format("debug: {} {} {} {} {} {}\n", a, b, c, d, e, f);
// std::cerr << "debug2: " << jump(a1) << ' ' << jump(b1) << ' ' << jump(c1) << ' ' << jump(d1) << char(10);
real ans = s.len(t);
vivi ss = s;
for(int i = 0; i <= n; ++i, ss = jump(ss)) {
vivi tt = t;
for(int j = 0; i + j <= n; ++j, tt = jump(tt)) {
// std::cerr << "debug3: " << ss << " " << tt << char(10);
ans = std::min(ans, ss.len(tt) + k * (i + j));
}
}
std::cout << ans << char(10);
return ;
}
int main() {
std::ios::sync_with_stdio(false);
std::cout << std::fixed << std::setprecision(15);
int T; std::cin >> T; while(T--) work();
return 0;
}
F. Inversion
很有意思的一个交互,从开局做到快结束的时候才会
首先观察到 \([a_x>a_y]=f(x,y)\oplus f(x+1,y)\oplus f(x,y-1)\oplus f(x+1,y-1)\)
把求原来排列改为排序,我们就得到了一个操作次数 \(4n\log n\) 的做法,实测是无法通过的
考虑用插入排序,每次确定一个数 \(i\) 的相对位置时可以二分它的插入位置,这样询问次数仍是 \(\log n\) 级别的
这样做的好处是观察上述式子,\(f(\ast,y-1)\) 相关的项其实可以直接手动计算逆序对得出,这样就把询问次数降到了 \(2n\log n\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
const int N=2005;
int n,f[N],p[N],q[N],rcd[N][N];
class Tree_Array
{
private:
int bit[N];
public:
#define lowbit(x) (x&-x)
inline void add(RI x,CI y)
{
for (;x<=n;x+=lowbit(x)) bit[x]+=y;
}
inline int get(RI x,int ret=0)
{
for (;x;x-=lowbit(x)) ret+=bit[x]; return ret;
}
#undef lowbit
}BIT;
int main()
{
memset(rcd,-1,sizeof(rcd));
scanf("%d",&n);
for (RI i=1;i<=n;++i) q[i]=i;
int cnt=0;
auto ask=[&](CI x,CI y)
{
if (x==y) return 0;
if (rcd[x][y]!=-1) return rcd[x][y];
int z; printf("? %d %d\n",x,y); fflush(stdout);
assert(++cnt<=40000);
scanf("%d",&z); return rcd[x][y]=z;
};
auto cmp=[&](CI x,CI y)
{
return ask(x,y)^ask(x+1,y)^f[x]^f[x+1];
};
for (RI i=2;i<=n;++i)
{
int l=1,r=i-1,ret=i;
for (RI j=1;j<i;++j) p[q[j]]=j;
f[i]=0;
for (RI j=i-1;j>=1;--j)
{
f[j]=f[j+1]+BIT.get(p[j]);
BIT.add(p[j],1);
}
for (RI j=1;j<i;++j) BIT.add(p[j],-1),f[j]&=1;
while (l<=r)
{
int mid=l+r>>1;
if (cmp(q[mid],q[i])) ret=mid,r=mid-1; else l=mid+1;
}
for (RI j=i;j>ret;--j) q[j]=q[j-1];
q[ret]=i;
}
for (RI i=1;i<=n;++i) p[q[i]]=i;
printf("! ");
for (RI i=1;i<=n;++i) printf("%d%c",p[i]," \n"[i==n]);
fflush(stdout);
return 0;
}
H. Chinese Checker
神秘爆搜题,不难发现确定起点后能跳的位置只有棋盘大小个,且不同的起点对应的终止态一定不同
因此可以直接枚举起点然后大力 DFS
实现时选择合适的坐标系能大大简化代码,我是选择了将两种跳的方向看作 \(x,y\) 轴,令一种方向对应的方向向量就是 \((\pm1,\pm1)\)
再手动打表求出每行对应的列的范围即可,代码十分精简
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=20;
const int dx[6]={1,-1,0,0,1,-1},dy[6]={0,0,1,-1,1,-1};
const int L[17]={4,4,4,4,0,1,2,3,4,4,4,4,4,9,10,11,12};
const int R[17]={4,5,6,7,12,12,12,12,12,13,14,15,16,12,12,12,12};
int t,n,x,y,ans,a[N][N],vis[N][N];
inline bool inside(CI x,CI y)
{
return 0<=x&&x<17&&L[x]<=y&&y<=R[x];
}
inline void DFS(CI x,CI y)
{
if (vis[x][y]) return;
vis[x][y]=1; ++ans;
for (RI i=0;i<6;++i)
{
int xx=x,yy=y;
while (inside(xx,yy)&&!a[xx][yy]) xx+=dx[i],yy+=dy[i];
if (!inside(xx,yy)) continue;
int nx=2*xx-x,ny=2*yy-y;
if (!inside(nx,ny)) continue;
int cnt=0; xx=x,yy=y;
while (xx!=nx||yy!=ny) cnt+=a[xx][yy],xx+=dx[i],yy+=dy[i];
cnt+=a[nx][ny];
if (cnt==1) DFS(nx,ny);
}
}
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d",&n); ans=0;
memset(a,0,sizeof(a));
for (RI i=1;i<=n;++i)
{
scanf("%d%d",&x,&y);
a[x-1][y-1+L[x-1]]=1;
}
for (RI i=0;i<17;++i)
for (RI j=L[i];j<=R[i];++j)
{
if (!a[i][j]) continue;
memset(vis,0,sizeof(vis));
a[i][j]=0;
--ans; DFS(i,j);
a[i][j]=1;
}
printf("%d\n",ans);
}
return 0;
}
I. Chase Game
不难发现当两个人位置重合后最优方案就是按照最短路直接走到 \(n\),这个可以简单推个式子算贡献
因此将起点和所有与 \(k\) 距离小于 \(d\) 的点单独拿出来跑个最短路,求出走到每个点最少要被炸多少血
然后枚举上述的点的所有邻居,若该邻居与 \(k\) 距离大于等于 \(d\) 则快速计算贡献即可
#include<cstdio>
#include<iostream>
#include<queue>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e18;
int n,m,k,d,vis[N],dis_k[N],dis_n[N],dis[N]; vector <int> v[N];
inline void BFS(int* dis,CI st)
{
static int vis[N];
for (RI i=1;i<=n;++i) dis[i]=INF;
queue <int> q;
dis[st]=0; q.push(st);
while (!q.empty())
{
int now=q.front(); q.pop();
for (auto to:v[now])
if (dis[to]>dis[now]+1)
{
dis[to]=dis[now]+1;
q.push(to);
}
}
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&m,&k,&d);
for (RI i=1;i<=m;++i)
{
int x,y; scanf("%lld%lld",&x,&y);
v[x].push_back(y); v[y].push_back(x);
}
BFS(dis_k,k); BFS(dis_n,n);
for (RI i=1;i<=n;++i) dis[i]=INF;
dis[1]=0;
priority_queue <pair <int,int>> hp;
hp.push({0,1});
vector <int> pnt;
while (!hp.empty())
{
int now=hp.top().second; hp.pop();
if (vis[now]) continue; vis[now]=1;
pnt.push_back(now);
for (auto to:v[now])
{
if (dis_k[to]>=d) continue;
if (dis[to]>dis[now]+(d-dis_k[to]))
{
dis[to]=dis[now]+(d-dis_k[to]);
hp.push({-dis[to],to});
}
}
}
auto calc=[&](int len)
{
int ret=(d+1)*d/2*(len/d);
len%=d;
return ret+(d+d-len+1)*len/2;
};
// for (auto x:pnt) printf("dis_k[%lld] = %lld; dis[%lld] = %lld\n",x,dis_k[x],x,dis[x]);
int ans=INF;
for (auto x:pnt)
{
if (x==n)
{
ans=min(ans,dis[n]);
continue;
}
for (auto y:v[x])
{
if (dis_k[y]<d) continue;
ans=min(ans,dis[x]+calc(dis_n[y]+1));
}
}
return printf("%lld",ans),0;
}
J. Chase Game 2
签到题,我题目都没看
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, deg[N], fa[N], bkt[N];
vector<int> G[N], leaf;
void dfs(int x) {
for (int v : G[x]) {
if (v == fa[x]) continue;
fa[v] = x;
dfs(v);
}
}
int solve() {
cin >> n;
leaf.clear();
for (int i=1; i<=n; ++i) {
deg[i] = 0; fa[i] = -1; bkt[i] = 0;
G[i].clear();
}
for (int i=1; i<n; ++i) {
int a, b; cin >> a >> b;
G[a].push_back(b); G[b].push_back(a);
++deg[a], ++deg[b];
}
int rt = -1;
for (int i=1; i<=n; ++i) {
if (deg[i]>1 && -1==rt) rt=i;
if (1==deg[i]) leaf.push_back(i);
}
if (leaf.size()==n || leaf.size()==n-1)
return -1;
fa[rt] = -1;
dfs(rt);
for (int v : leaf) ++bkt[fa[v]];
int mx = 0;
int sz = leaf.size();
for (int i=1; i<=n; ++i) mx = max(mx, bkt[i]);
if (mx*2 <= sz) return (sz+1)/2;
return mx;
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T; while (T--) cout << solve() << '\n';
return 0;
}
L. Aqre
这题我基本没参与,队友的写法大致是找一个 \(4\times 4\) 的密铺单元,将其扩展到边长恰好大于等于 \(n,m\),然后在里面找一个合法的且 \(1\) 数量最多的 \(n\times m\) 的子矩形
但这种方法需要特判一些 \(2\times k,3\times (3k+4)\) 的情况,具体实现看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1050;
int samp[4][4] = {
{0, 1, 1, 1},
{1, 1, 0, 1},
{1, 0, 1, 1},
{1, 1, 1, 0}
};
int n, m;
int ans[N][N];
int sum[N][N];
int f(int i, int j) {
if (i < 0 || j < 0) return 0;
else return sum[i][j];
}
int calc(int i, int j) {
return f(i+n-1, j+m-1) - f(i-1, j+m-1) - f(i+n-1, j-1) + f(i-1, j-1);
}
void solve() {
cin >> n >> m;
if (n < 4 && m < 4) {
cout << n*m << '\n';
for (int i=0; i < n; ++i) {
for (int j=0; j<m; ++j) {
cout << 1;
if (j==m-1) cout << '\n';
}
}
return;
}
for (int i=0; i<(n+3)/4*4 + 4; ++i) {
for (int j=0; j<(m+3)/4*4 + 4; ++j) {
ans[i][j] = samp[i%4][j%4];
sum[i][j] = ans[i][j] + f(i-1, j) + f(i, j-1) - f(i-1, j-1);
}
}
// for (int i=0; i<(n+3)/4; ++i) {
// for (int j=0; j<(m+3)/4; ++j) {
// cout << ans[i][j];
// }
// }
if (3 == n && m % 4 == 3) {
cout << 9 * (m+1) / 4 - 1 << '\n';
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (i%2 ==0 && j % 4 == 3) cout << 0;
else if (i % 2 == 1 && j % 4 == 1) cout << 0;
else cout << 1;
if (j == m-1) cout << '\n';
}
}
} else if (3 == m && n % 4 == 3) {
cout << 9 * (n+1) / 4 - 1 << '\n';
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (j%2 ==0 && i % 4 == 3) cout << 0;
else if (j % 2 == 1 && i % 4 == 1) cout << 0;
else cout << 1;
if (j == m-1) cout << '\n';
}
}
} else {
int cnt1 = 0, ax = 0, ay = 0;
for (int i=0; i<4; ++i) {
for (int j=0; j<4; ++j) {
if (i%4==1 && j%4==1) continue;
if ((i+n-1)%4==3 && j%4==0) continue;
if (i%4==0 && (j+m-1)%4==3) continue;
if ((i+n-1)%4==2 && (j+m-1)%4==2) continue;
if (n==2 && (i==1 || i==3)) continue;
if (m==2 && (j==1 || j==3)) continue;
int res = calc(i, j);
if (res > cnt1) {
cnt1 = res;
ax = i, ay = j;
}
}
}
cout << cnt1 << '\n';
for (int i=0; i<n; ++i) for (int j=0; j<m; ++j) {
cout << ans[ax+i][ay+j];
if (j==m-1) cout << '\n';
}
}
}
signed main() {
// ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T; while (T--) solve();
return 0;
}
M. Dining Professors
签到,简单贪心即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,m,a[N],b[N];
int main()
{
scanf("%d%d",&n,&m);
for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
a[0]=a[n]; a[n+1]=a[1];
for (RI i=1;i<=n;++i) b[i]=a[i-1]+a[i]+a[i+1];
sort(b+1,b+n+1,greater <int>());
int ans=3*(n-m);
for (RI i=1;i<=m;++i) ans+=b[i];
return printf("%d",ans),0;
}
Postscript
神人飞机不知道今天还能不能起飞,感觉要打不上比赛了啊

浙公网安备 33010602011771号