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

神人飞机不知道今天还能不能起飞,感觉要打不上比赛了啊

posted @ 2025-05-09 17:21  空気力学の詩  阅读(141)  评论(0)    收藏  举报