The 2024 ICPC Asia East Continent Online Contest (II)

Preface

被徐神带飞咯,全程睡觉看队友卡卡过题,最变态的是 K 我上去乱写了个假做法就下机睡觉了,后面徐神反手就改了个正解出来

这场主要是周五晚上无来由地发烧了,第二天比赛的时候头痛的一批,几乎没法集中精力想代码和写题

但没想到这场最后打的还挺好,开局 1h 不到就把 6 个签过了,然后跟徐神讲了下 C 题意徐神表示秒会

徐神上机写 C 的时候我和祁神把 E 的做法讨论出来了,并得到一个较为简单的实现,此时徐神 C 很快写完交上去 WA 了,在写了对拍后发现欠考虑了些就换祁神上去写 E

本来说好是祁神写我在边上看的,结果看着看着给我看红温了直接头也不痛了,抢键盘冲上去乱写一通交上去就过了

此时发现罚时优势挺大,并且看榜发现这题后面题都不简单,遂决定再 all-in 一个过的人比较多的 K

徐神上机改了下就把 C 调出来了,结果交上去竟然 T 了,在本机搞了一堆强数据测试后感觉做法复杂度没问题后,我直接上去抄了个快读然后发现 54ms 过了

在下面的时候和祁神把 K 题分治+卷积+完全二分图组合计数的思路大致搞了出来,但因为没有想清楚就冲上去写了个会计算重复贡献的做法,最后经典没过样例下机反思

此时我的头痛突然加剧,遂只能趴在桌子上开睡,让祁神把做法跟徐神交流下,后面就在我迷迷糊糊中听队友讨论出一个不重不漏计数的方法,徐神上去也是很快敲出来过了

最后 9 题校排 16 终于打了个像样的排名了,那么根据控制变量法之前究竟是谁在演呢,我不好说


A. Gambling on Choosing Regionals

读懂题意后不难发现所谓的最坏情况就是和强队全撞了,因此最优的决策一定是去队伍数最小的赛站

按能力值从大到小排序后对每个学校开一个桶统计下即可,注意当前队伍所在学校对应的值要减去 \(1\)

#include<cstdio>
#include<iostream>
#include<string>
#include<array>
#include<algorithm>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,c[N],C,idx,bkt[N],ans[N]; array <int,3> a[N]; map <string,int> rst;
int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    cin>>n>>k; C=1e9;
    for (RI i=1;i<=k;++i) cin>>c[i],C=min(C,c[i]);
    for (RI i=1;i<=n;++i)
    {
        string tmp;
        cin>>a[i][0]>>tmp; a[i][2]=i;
        if (rst.count(tmp)) a[i][1]=rst[tmp];
        else a[i][1]=rst[tmp]=++idx;
    }
    sort(a+1,a+n+1,greater <array <int,3>>()); int sum=0;
    for (RI i=1;i<=n;++i)
    {
        auto [w,sch,id]=a[i];
        if (bkt[sch]<C) ++bkt[sch],++sum;
        ans[id]=(sum-bkt[sch])+(bkt[sch]-1)+1;
    }
    for (RI i=1;i<=n;++i) printf("%d\n",ans[i]);
    return 0;
}

B. Mountain Booking

看过题人数是个防 AK,不过这场没过的题好像都是 DS 相关的,看来我的挂机导致我们队没开错题,赢


C. Prefix of Suffixes

string master 专业对口,这么多场网络赛终于有个字符串了

徐神的做法大致就是用 KMP 的 fail 数组等差数列 \(\log\) 级别的性质来做,因为我一点不懂字符串科技具体的也不懂了

#include <bits/stdc++.h>

using llsi = long long signed int;

int n;
int s[300005], a[300005], b[300005];
int fail[300005], top[300005];
llsi ans = 0, bsum = 0;

class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        char Fin[S],*A,*B;
    public:
        template <typename T> inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        #undef tc
}F;

int main() {
    F.read(n);
    for(int i = 1; i <= n; ++i) {
        F.read(s[i]); F.read(a[i]); F.read(b[i]);
        s[i] = (s[i] + ans) % n;
        int j = fail[i - 1];
        //int count = 0;
        if(i == 1) fail[1] = top[1] = 0;
        else {
            //count += 1;
            while(j && s[j + 1] != s[i]) {
                //count += 1;
                bsum -= b[i - 1 - j + 1];
                j = fail[j];
            }
            if(s[j + 1] == s[i]) fail[i] = j + 1;
            else                 fail[i] = 0;
            if(i - fail[i] == fail[i] - fail[fail[i]]) top[i] = top[fail[i]];
            else                                       top[i] = fail[i];
        }
        for(; j > 0; ) {
            //count += 1;
            if(s[j + 1] == s[i]) {
                if(s[fail[j] + 1] == s[i]) j = top[j];
                else j = fail[j];
            } else {
                bsum -= b[i - 1 - j + 1];
                j = fail[j];
            }
        }
        // std::cerr << "top[" << i << "] = " << top[i] << char(10);
        // std::cerr << "fail[" << i << "] = " << fail[i] << char(10);
        if(s[i] == s[1]) bsum += b[i];
        ans += a[i] * bsum;
        std::cout << ans << char(10);
    }
    return 0;
}

D. Query on Tree

不可做的 DS 题,鉴定为弃疗


E. Escape

很经典的一个题,想到了就很简单

考虑玩家最后选择的路径上能不能包含点 \(x\),这就要求当人走到这个位置时不能被机器人抓到

首先若所有机器人到该点的最小距离 \(>d\) 则该点一定可以走;还有一种情况就是人到的比机器人早

由于有走回头路的情况因此套路地发现这和奇偶性有关,考虑将每个点拆成奇偶两个

对奇偶两种点分别判断人是否能先于机器人到达即可,以此可以确定每个点是否可以被走到,最后对所有合法的点求一个最短路即可

#include<bits/stdc++.h>
using namespace std;

const int INF = 1e9+5;
const int N = 5e5+5;
int n, m, d, k, dis1[N], dis2[N], dis3[N], pre[N];
bool valid[N];
vector<int> G[N];

void BFS(queue<int> &Q, int dis[]) {
    while (!Q.empty()) {
        int x = Q.front(); Q.pop();
        for (int v : G[x]) {
            if (!valid[v]) continue;
            if (dis[v] > dis[x]+1) {
                dis[v] = dis[x]+1;
                pre[v] = x;
                Q.push(v);
            }
        }
    }
}

void solve() {
    cin >> n >> m >> d;
    for (int i=1; i<=2*n; ++i) G[i].clear(), dis1[i]=dis2[i]=dis3[i]=INF,valid[i]=1;
    for (int i=1; i<=m; ++i) {
        int x, y; cin >> x >> y;
        int x1=2*x-1, y1=2*y-1;
        int x2=2*x, y2=2*y;
        G[x1].push_back(y2); G[y2].push_back(x1);
        G[x2].push_back(y1); G[y1].push_back(x2);
    }
    cin >> k;
    queue<int> Q;
    for (int i=1; i<=k; ++i) {
        int s; cin >> s;
        dis1[2*s-1] = 0;
        pre[2*s-1] = -1;
        Q.push(2*s-1);
    }
    BFS(Q, dis1);
    queue<int> Q2; Q2.push(1); dis2[1] = 0; pre[1] = -1;
    BFS(Q2, dis2);
    
    for (int i=1; i<=2*n; ++i) {
        if (dis1[i]>d||dis2[i]<dis1[i]) valid[i]=1; else valid[i]=0;
    }

    if (!valid[1]) {
        puts("-1"); return;
    }

    queue<int> Q3; Q3.push(1); dis3[1] = 0; pre[1] = -1;
    BFS(Q3, dis3);
    if (min(dis3[2*n-1],dis3[2*n])==INF) {
        puts("-1"); return;
    }

    printf("%d\n",min(dis3[2*n-1],dis3[2*n]));

    int x;
    if (dis3[2*n-1]<=dis3[2*n]) x=2*n-1; else x=2*n;
    vector <int> path;
    while (x!=-1) path.push_back(x),x=pre[x];
    reverse(path.begin(),path.end());
    for (auto x:path) printf("%d ",(x+1)/2);
    putchar('\n');
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int t; cin >> t; while (t--) solve();
    return 0;
}

F. Tourist

纯签到,我题都没看

#include <bits/stdc++.h>

int main() {
    std::ios::sync_with_stdio(false);
    int n; std::cin >> n;
    for(int64_t i = 1, rating = 1500, c; i <= n; ++i) {
        std::cin >> c;
        rating += c;
        if(rating >= 4000) {
            std::cout << i << char(10);
            return 0;
        }
    }
    std::cout << "-1\n";
    return 0;
}

G. Game

首先发现平局并没啥用,同时由于赢得的钱不会给胜方因此其实就是个辗转相除的过程,简单模拟一下即可

#include <bits/stdc++.h>

using llsi = long long signed int;
constexpr llsi mod = 998244353;

llsi ksm(llsi a, llsi b) {
    llsi c = 1;
    while(b) {
        if(b & 1) c = c * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return c;
}

llsi f(llsi x, llsi y, llsi p1, llsi p2) {
    if(x == 0) return 0;
    if(y == 0) return 1;
    return ksm(p1, y / x) * (mod + 1 - f(y % x, x, p2, p1)) % mod;
}

int main() {
    std::ios::sync_with_stdio(false);
    int t; std::cin >> t; while(t--) {
        llsi x, y, a0, a1, b;
        std::cin >> x >> y >> a0 >> a1 >> b;
        llsi t = ksm(a0 + a1, mod - 2);
        a0 = a0 * t % mod; a1 = a1 * t % mod;
        std::cout << f(x, y, a0, a1) << char(10);
    }
    return 0;
}

H. Points Selection

刚开始没仔细看题一直在想“选子集和模 \(n\)\(c\)”这个限制怎么做,想来想去最优的也就是 bitset 了,遂感觉这是个不可做题,结果最后 20min 才发现题目保证数据随机

由于若 \(query(a,b,c)\) 为真,则 \(query(\ge a,\ge b,c)\) 也一定为真

因此考虑从小到大枚举 \(a\) 的值,并令 \(f_c\) 表示最小的满足 \(query(a,b,c)\) 为真的值 \(b\),有了这个后可以很容易计算答案

每次加入一个点后需要 \(O(n)\) 的时间暴力更新 \(f\) 数组,但我们可以用随机的性质来做一些分析

考虑加入 \(k\) 个点后,随机的性质会使得其 \(2^k\) 个子集和模 \(n\) 的值在 \([0,n)\) 内均匀分布,因此 \(k=O(\log n)\) 时期望就可以将所有 \(f\) 的值填满

因此 \(f\) 数组的最大值的期望值等于所有已经加入的点的纵坐标的第 \(O(\log n)\) 小值,即 \(O(\frac{n\log n}{k})\)

注意到每次加入一个点时,若它的 \(y\) 坐标 \(\ge \max(f)\) 时可以直接跳过它,因此它更新答案(即其纵坐标 \(< \max(f)\) )的概率为 \(O(\frac{\log n}{k})\)

即期望更新次数为 \(O(\sum_{k=1}^n \frac{\log n}{k})=O(\log^2 n)\),总复杂度 \(O(n\log^2n)\),常数很小可以通过

#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=500005;
int n,f[N]; vector <pi> vec[N];
unsigned long long sum,ans;
int main()
{
	scanf("%d",&n);
	for (RI i=1;i<=n;++i)
	{
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		vec[x].push_back({y,w});
	}
	for (RI i=0;i<n;++i) f[i]=n+1;
	int maxy=n+1;
	for (RI x=1;x<=n;++x)
	{
		for (auto [y,w]:vec[x])
		{
			if (y>=maxy) continue;
			static int g[N]; maxy=-1; sum=0;
			for (RI i=0;i<n;++i) g[i]=f[i];
			for (RI i=0;i<n;++i)
			g[(i+w)%n]=min(g[(i+w)%n],max(f[i],y));
			g[w]=min(g[w],y);
			for (RI i=0;i<n;++i)
			{
				f[i]=g[i]; maxy=max(maxy,f[i]);
				sum+=1ull*i*(1ull*(f[i]+n)*(n-f[i]+1)/2);
			}
		}
		ans+=1ull*x*sum;
	}
	return printf("%llu",ans),0;
}

I. Strange Binary

祁神开场写的神秘构造,感觉还是挺小清新的

#include<bits/stdc++.h>
using namespace std;

void solve() {
    int n; cin >> n;
    if (n%4==0) {
        cout << "NO\n";
        return ;
    }

    vector<int> A(32), ans(32);
    for (int i=0; i<=30; ++i) {
        if ((n>>i)&1) A[i] = 1;
        else A[i] = 0;
    }

    auto find1 = [&](int pos) {
        while (pos>=0) {
            if (A[pos]==1) return pos;
            else --pos;
        }
        return -1;
    };
    for (int i=31; i>=0; --i) {
        if (1==A[i]) ans[i]=1;
        else {
            int pos = find1(i);
            if (-1==pos) break;
            ans[i] = 1;
            for (int j=i-1; j>=pos; --j) ans[j]=-1;
            i = pos;
        }
    }

    cout << "YES\n";
    for (int i=0; i<32; ++i) {
        cout << ans[i] << (i%8==7 ? '\n' : ' ');
    }
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int t; cin >> t; while (t--) solve();
    return 0;
}

J. Stacking of Goods

很套路的题,用交换法可以证明物品 \(i\)\(j\) 之前当且仅当 \(c_i\times w_j>c_j\times w_i\),改下排序的比较函数即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct ifo
{
    int w,v,c;
    friend inline bool operator < (const ifo& A,const ifo& B)
    {
        return A.c*B.w>B.c*A.w;
    }
}a[N]; int n;
signed main()
{
    scanf("%lld",&n);
    for (RI i=1;i<=n;++i)
    scanf("%lld%lld%lld",&a[i].w,&a[i].v,&a[i].c);
    sort(a+1,a+n+1); int ans=0,W=0;
    for (RI i=n;i>=1;--i) ans+=a[i].v-a[i].c*W,W+=a[i].w;
    return printf("%lld",ans),0;
}

K. Match

按位异或的题很容易想到从高位往低位枚举,并令 \(solve(A,B,p)\) 表示 \(A,B\) 两个集合在高 \(p\) 位匹配的答案,返回值为一个多项式

\(A,B\) 按当前位的权值为 \(0/1\) 分为 \(A_0,A_1,B_0,B_1\),考虑以下两种情况:

  • \(k\) 的第 \(p\) 位为 \(1\),此时只能异或值为 \(1\) 的两种方案进行匹配,递归算 \(solve(A_0,B_1,p-1)\)\(solve(A_1,B_0,p-1)\) 即可;
  • \(k\) 的第 \(p\) 位为 \(0\),异或值为 \(1\) 的两种方案可以任意匹配,先求出 \(solve(A_0,B_0,p-1)\)\(solve(A_1,B_1,p-1)\) 后剩下的就是个完全二分图匹配,组合 DP 转移即可;

最后总复杂度 \(O(n^4)\),实际常数极小跑的飞快

#include <bits/stdc++.h>

using llsi = long long signed int;
constexpr llsi mod = 998244353;

using poly = std::vector<llsi>;

llsi ksm(llsi a, llsi b) {
    llsi c = 1;
    while(b) {
        if(b & 1) c = c * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return c;
}

llsi fac[201], facinv[201];
void prep(int n = 200) {
    fac[0] = 1;
    for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
    facinv[n] = ksm(fac[n], mod - 2);
    for(int i = n; i >= 1; --i) facinv[i - 1] = facinv[i] * i % mod;
    return ;
}

llsi C(llsi a, llsi b) {
    if(a < b || b < 0) return 0ll;
    return fac[a] * facinv[b] % mod * facinv[a - b] % mod;
}

poly mult(const poly &a, const poly &b) {
    poly c(a.size() + b.size() - 1, 0);
    for(int i = 0; i < a.size(); ++i) for(int j = 0; j < b.size(); ++j)
        c[i + j] = (c[i + j] + a[i] * b[j]) % mod;
    return c;
}

poly full(int a, int b) {
    poly c(std::min(a, b) + 1);
    for(int i = 0; i < c.size(); ++i) c[i] = C(a, i) * C(b, i) % mod * fac[i] % mod;
    return c;
}

int n;
llsi k;

poly solve(const poly &a, const poly &b, int t) {
    if(a.empty() || b.empty()) return poly {1};
    if(t == -1) return full(a.size(), b.size());
    poly A[2], B[2];
    for(auto a: a) A[a >> t & 1].emplace_back(a);
    for(auto b: b) B[b >> t & 1].emplace_back(b); 
    if(k >> t & 1) return mult(solve(A[0], B[1], t - 1), solve(A[1], B[0], t - 1));
    poly C[2];
    
    C[0] = solve(A[0], B[0], t - 1);
    C[1] = solve(A[1], B[1], t - 1);

    poly res(std::min(a.size(), b.size()) + 1, 0);
    for(int i = 0; i < C[0].size(); ++i) for(int j = 0; j < C[1].size(); ++j) {
        llsi base = C[0][i] * C[1][j] % mod;
        llsi a0res = A[0].size() - i, a1res = A[1].size() - j;
        llsi b0res = B[0].size() - i, b1res = B[1].size() - j;
        poly aster = mult(full(a0res, b1res), full(a1res, b0res));
        for(int k = 0; k < aster.size(); ++k) res[i + j + k] = (res[i + j + k] + base * aster[k]) % mod;
    }

    return res;
}

int main() {
    std::ios::sync_with_stdio(false);
    prep(200);
    std::cin >> n >> k;
    poly a(n), b(n);
    for(auto &a: a) std::cin >> a; for(auto &b: b) std::cin >> b;
    poly ans = solve(a, b, 60);
    while(ans.size() < n + 1) ans.emplace_back(0);
    for(int i = 1; i <= n; ++i) std::cout << ans[i] << char(10);
    return 0;
}


L. 502 Bad Gateway

徐神开场写的,做法不难想到就是均值不等式,但需要手写分数类避免精度误差

#include <bits/stdc++.h>

using llsi = long long signed int;

struct frac {
    llsi a, b;
    friend frac operator +(const frac &x, const frac &y) {
        llsi g = std::__gcd(x.b, y.b);
        return frac{ x.a * (y.b / g) + y.a * (x.b / g), x.b / g * y.b };
    }
    friend frac operator -(const frac &x, const frac &y) {
        llsi g = std::__gcd(x.b, y.b);
        return frac{ x.a * (y.b / g) - y.a * (x.b / g), x.b / g * y.b };
    }
    friend bool operator <(const frac &x, const frac &y) {
        return x.a * y.b < x.b * y.a;
    }
};

int main() {
    std::ios::sync_with_stdio(false);
    int t; std::cin >> t; while(t--) {
        frac ans { 0x7FFFFFFF, 1 };
        llsi T; std::cin >> T;
        llsi cbase = static_cast<llsi>(std::sqrt(2 * T));
        for(llsi c = cbase - 3; c <= cbase + 3; ++c) {
            if(c <= 0 || c > T) continue;
            ans = std::min(ans, frac{c * (c + 1) + 2 * T - 2 * c, 2 * c});
        }
        llsi g = std::__gcd(ans.a, ans.b);
        std::cout << ans.a / g << " " << ans.b / g << char(10);
    }
    
}

Postscript

网络赛也终于告一段落了,今年区域赛拿了 2+2 的名额,希望能不负众望打出点好成绩吧

posted @ 2024-09-23 18:04  空気力学の詩  阅读(275)  评论(0编辑  收藏  举报