The 2024 ICPC Kunming Invitational Contest

Preface

周五去昆明特意买了动车,就为了在车上也能 VP 一场

找了半天不知道打什么,最后决定去昆明不如把今年的昆明邀请赛补一下,遂开了这场

前期可以说还是十分顺遂的,2h15min 的时候就过了 7 个题,其中还早早地过了后期题 H

但后面被 J 和 L 小卡了一手,最后通过换题成功把两个题都过了,然后 Rush F 失败,后面发现 F 好像有点简单


A. Two-star Contest

签到模拟题,祁神写的我题目都没看

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

const int N = 4e5+5;
int n, m, k, s[N], rk[N];
vector<int> vec[N];

void solve() {
    cin >> n >> m >> k;
    for (int i=1; i<=n; ++i) {
        cin >> s[i], rk[i] = i;
        vec[i].clear(), vec[i].reserve(m);
        for (int j=0; j<m; ++j) {
            int a; cin >> a; vec[i].push_back(a);
        }
    }

    sort(rk+1, rk+n+1, [&](int a, int b) {return s[a] < s[b];});
    int pos = n+1;
    for (int i=2; i<=n; ++i) if (s[rk[i]] > s[rk[i-1]]) {pos = i; break;}

    int curval = 0, nxtval = 0;
    for (int x=1; x<pos; ++x) {
        auto &vecx = vec[rk[x]];
        int ret = 0;
        for (int i=0; i<m; ++i) {
            if (-1==vecx[i]) vecx[i]=0;
            else ret += vecx[i];
        }
        nxtval = max(ret, nxtval);
    }

    bool ok = true;
    for (int i=pos; i<=n; ++i) {
        if (s[rk[i]] > s[rk[i-1]]) curval = nxtval+1, ++nxtval;

        auto &veci = vec[rk[i]];
        int ret = 0;
        for (int j=0; j<m; ++j) if (veci[j]>=0) ret += veci[j];

        // printf("i=%lld ret=%lld\n", i, ret);
        if (ret >= curval) {
            nxtval = max(ret, nxtval);
            for (int j=0; j<m; ++j) if (veci[j]<0) veci[j]=0;
        } else {
            int tmp = curval-ret;
            for (int j=0; j<m; ++j) if (veci[j]<0) {
                veci[j] = min(k, tmp), 
                tmp -= min(tmp, veci[j]);
            }
            if (tmp > 0) {
                ok=false;
                break;
            }
        }
    }

    if (!ok) cout << "No\n";
    else {
        cout << "Yes\n";
        for (int i=1; i<=n; ++i) {
            for (int x : vec[i]) cout << x << ' ';
            cout << endl;
        }
    }
}

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

B. Gold Medal

签到,先贪心地把每个数补成 \(k\) 的倍数,多余部分直接计算即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
const int N=105;
int t,n,k,rem[N],m; pair <int,int> a[N];
signed main()
{
	for (scanf("%lld",&t);t;--t)
	{
		scanf("%lld%lld",&n,&k);
		for (RI i=1;i<=n;++i)
		{
			scanf("%lld",&a[i].se);
			a[i].fi=(a[i].se+k-1)/k*k-a[i].se;
		}
		sort(a+1,a+n+1);
		scanf("%lld",&m);
		for (RI i=1;i<=n&&m>0;++i)
		if (a[i].fi<=m) m-=a[i].fi,a[i].se+=a[i].fi; else a[i].se+=m,m=0;
		int ans=0; if (m>0) ans+=m/k;
		for (RI i=1;i<=n;++i) ans+=a[i].se/k;
		printf("%lld\n",ans);
	}
	return 0;
}

E. Relearn through Review

其实很早的时候就猜出做法了,不过由于不确定正确性就一直没写

首先不难发现如果我们修改 \([l,r]\) 区间,则答案由三部分构成,即不变的前缀/后缀,以及中间集体加 \(k\) 的一段

注意到当前后缀的贡献不变时,中间部分的长度肯定是越短越好,而我们知道前后缀 \(\gcd\) 的值只有 \(\log a_i\)

因此把所有的前后缀变化的分界点拉出来暴力枚举下 \([l,r]\) 即可,中间部分的贡献可以用 ST 表预先维护

复杂度 \(O(n\log n\log a_i+\log^3 a_i)\),注意可以一次都不操作

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,k,a[N],pre[N],suf[N],lg[N],f[N][20];
inline int query(CI l,CI r)
{
	int k=lg[r-l+1]; return __gcd(f[l][k],f[r-(1<<k)+1][k]);
}
signed main()
{
	for (scanf("%lld",&t);t;--t)
	{
		scanf("%lld%lld",&n,&k);
		for (RI i=1;i<=n;++i) scanf("%lld",&a[i]);
		pre[0]=suf[n+1]=0;
		for (RI i=1;i<=n;++i) pre[i]=__gcd(pre[i-1],a[i]);
		for (RI i=n;i>=1;--i) suf[i]=__gcd(suf[i+1],a[i]);
		for (RI i=1;i<=n;++i) f[i][0]=a[i]+k;
		lg[0]=-1; for (RI i=1;i<=n;++i) lg[i]=lg[i>>1]+1;
		for (RI j=1;j<20;++j)
		for (RI i=1;i+(1<<j)-1<=n;++i)
		f[i][j]=__gcd(f[i][j-1],f[i+(1<<j-1)][j-1]);
		vector <int> vec;
		for (RI i=1;i<=n;++i)
		{
			if (pre[i]!=pre[i-1])
			{
				if (i-1>=1) vec.push_back(i-1);
				vec.push_back(i);
			}
			if (suf[i]!=suf[i+1])
			{
				if (i+1<=n) vec.push_back(i+1);
				vec.push_back(i);
			}
		}
		sort(vec.begin(),vec.end());
		vec.erase(unique(vec.begin(),vec.end()),vec.end());
		int ans=pre[n];
		for (RI i=0;i<vec.size();++i)
		for (RI j=i;j<vec.size();++j)
		{
			int l=vec[i],r=vec[j];
			int cur=__gcd(__gcd(pre[l-1],suf[r+1]),query(l,r));
			ans=max(ans,cur);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

F. Collect the Coins

感觉想到了就很简单的一个题啊,但我们队有人在想图论几何建模,有人在贪心,怎么回事呢

首先一眼二分答案 \(v\),考虑递推检验,令 \([l_i,r_i]\) 表示在处理完第 \(i\) 枚硬币后,一个机器人在 \(c_i\),另一个机器人可以的取值在 \([l_i,r_i]\) 内,初始时 \([l_1,r_1]=(-\infty,\infty)\)

考虑新加入一枚硬币 \(i+1\) 的影响,令 \(d=(t_{i+1}-t_i)\times v\)

  • \(c_{i+1}\in[c_i-d,c_i+d]\),则之前在 \(c_i\) 的机器人可以收集到硬币,此时另一个机器人可能的取值变为 \([l_i-d,r_i+d]\)
  • \(c_{i+1}\in[l_i-d,r_i+d]\),则之前不在 \(c_i\) 的机器人可以收集到硬币,此时另一个机器人可能的取值变为 \([c_i-d,c_i+d]\)

注意到如果以上两种 Case 都不成立则一定无解,如果成立其一就直接转移即可

当同时成立时由于两个区间都包含 \(c_{i+1}\),因此这两个区间一定相交,可以合并成一个大区间

复杂度 \(O(n\log c_i)\)

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5,INF=1e18;
struct itv
{
	int l,r;
	inline itv(CI L=0,CI R=0)
	{
		l=L; r=R;
	}
	inline bool is_in(CI x)
	{
		return l<=x&&x<=r;
	}
	friend inline itv operator + (const itv& A,const itv& B)
	{
		return itv(min(A.l,B.l),max(A.r,B.r));
	}
};
int T,n,t[N],c[N];
inline bool check(CI v)
{
	itv cur(-INF,INF);
	for (RI i=2;i<=n;++i)
	{
		int dlt=(t[i]-t[i-1])*v;
		itv np=itv(cur.l-dlt,cur.r+dlt),nq=itv(c[i-1]-dlt,c[i-1]+dlt);
		bool p=nq.is_in(c[i]),q=np.is_in(c[i]);
		if (p&&q) { cur=np+nq; continue; }
		if (p) { cur=np; continue; }
		if (q) { cur=nq; continue; }
		return 0;
	}
	return 1;
}
signed main()
{
	for (scanf("%lld",&T);T;--T)
	{
		scanf("%lld",&n);
		for (RI i=1;i<=n;++i) scanf("%lld%lld",&t[i],&c[i]);
		int l=0,r=1e9,mid,ret=-1; while (l<=r)
		if (check(mid=l+r>>1)) ret=mid,r=mid-1; else l=mid+1;
		if (ret==-1) puts("-1"); else printf("%lld\n",ret);
	}
	return 0;
}

G. Be Positive

签到,根据经典结论当 \(n=1\or 4\mid n\) 时一定无解,因为此时所有数异或起来都等于 \(0\)

否则按照四个数一组,交换相邻两组相邻的元素即可构造出字典序最小的解

#include<cstdio>
#include<iostream>
#include<cassert>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int t,n,a[N];
int main()
{
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d",&n);
		if (n==1||n%4==0) { puts("impossible"); continue; }
		for (RI i=0;i<n;++i) a[i]=i; swap(a[0],a[1]);
		for (RI i=3;i+1<n;i+=4) swap(a[i],a[i+1]);
		int xsum=0; for (RI i=0;i<n;++i)
		xsum^=a[i],assert(xsum!=0);
		for (RI i=0;i<n;++i) printf("%d%c",a[i]," \n"[i==n-1]);
	}
	return 0;
}

H. Subarray

感觉很典的一个题啊,尤其是处理贡献的卷积部分好像和南京的 I 一模一样

考虑从大到小枚举每个数作为最大值的贡献,此时很容易用单调栈算出此时每个数左边/右边有多少个合法的端点选择,不妨记为数组 \(\{a_i\},\{b_i\}\),但要注意被隔断的情况

此时不难发现要计算贡献的话就是枚举所有的 \(i\in[1,k],j\in[i,k]\),然后 \(a_i\times b_j\rightarrow c_{j-i+1}\)

这个式子直接把 \(\{a_i\}\) reverse 一下然后和 \(\{b_i\}\) 做卷积,然后平移一下就能快速计算贡献了

然后这题就做完了,复杂度 \(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=400005,mod=998244353;
int t,n,a[N],rst[N],l[N],r[N],stk[N],top,ans[N]; vector <int> pos[N];
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
inline void dec(int& x,CI y)
{
	if ((x-=y)<0) x+=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
namespace Poly
{
	int lim,p,rev[N<<2];
	inline void init(CI n)
	{
		for (lim=1,p=0;lim<=n;lim<<=1,++p);
		for (RI i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<p-1);
	}
	inline void NTT(int *f,CI opt)
	{
		RI i; for (i=0;i<lim;++i) if (i<rev[i]) swap(f[i],f[rev[i]]);
		for (i=1;i<lim;i<<=1)
		{
			int m=i<<1,D=quick_pow(3,opt==1?(mod-1)/m:mod-1-(mod-1)/m);
			for (RI j=0;j<lim;j+=m)
			{
				int W=1; for (RI k=0;k<i;++k,W=1LL*W*D%mod)
				{
					int x=f[j+k],y=1LL*f[i+j+k]*W%mod;
					f[j+k]=f[i+j+k]=x; inc(f[j+k],y); dec(f[i+j+k],y);
				}
			}
		}
		if (!~opt)
		{
			int Inv=quick_pow(lim); for (i=0;i<lim;++i) f[i]=1LL*f[i]*Inv%mod;
		}
	}
};
int main()
{
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d",&n);
		for (RI i=1;i<=n;++i) scanf("%d",&a[i]),rst[i]=a[i];
		sort(rst+1,rst+n+1); int m=unique(rst+1,rst+n+1)-rst-1;
		for (RI i=1;i<=n;++i) a[i]=lower_bound(rst+1,rst+m+1,a[i])-rst;
		for (RI i=1;i<=m;++i) pos[i].clear();
		for (RI i=1;i<=n;++i) pos[a[i]].push_back(i);
		stk[top=0]=0;
		for (RI i=1;i<=n;++i)
		{
			while (top&&a[stk[top]]<a[i]) --top;
			l[i]=stk[top]; stk[++top]=i;
		}
		stk[top=0]=n+1;
		for (RI i=n;i>=1;--i)
		{
			while (top&&a[stk[top]]<a[i]) --top;
			r[i]=stk[top]; stk[++top]=i;
		}
		for (RI i=1;i<=m;++i)
		{
			auto solve=[&](vector <int>& vec)
			{
				static int A[N<<2],B[N<<2];
				int sz=(int)vec.size();
				for (RI i=0;i<sz;++i)
				{
					A[sz-i]=vec[i]-l[vec[i]];
					B[i]=r[vec[i]]-vec[i];
				}
				Poly::init(2*sz); Poly::NTT(A,1); Poly::NTT(B,1);
				for (RI i=0;i<Poly::lim;++i) A[i]=1LL*A[i]*B[i]%mod;
				Poly::NTT(A,-1);
				for (RI i=1;i<=sz;++i) inc(ans[i],A[i+sz-1]);
				for (RI i=0;i<Poly::lim;++i) A[i]=B[i]=0;
			};
			vector <int> vec; vec.push_back(pos[i][0]);
			for (RI j=1;j<pos[i].size();++j)
			{
				int p=vec.back(),q=pos[i][j];
				if (r[p]<q) solve(vec),vec.clear();
				vec.push_back(q);
			}
			solve(vec);
		}
//		for (RI i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
		int ret=0;
		for (RI i=1;i<=n;++i) inc(ret,1LL*i*ans[i]%mod*ans[i]%mod),ans[i]=0;
		printf("%d\n",ret);
	}
	return 0;
}

I. Left Shifting 2

徐神开场写的,应该也是个签

#include <iostream>
#include <format>

int main() {
    int T; std::cin >> T; while(T--) {
        std::string s; std::cin >> s;
        int ans = 0, t = 0, n = s.size();
        bool flag = true;
        for(int l = 0, r; l < n; l = r) {
            r = l + 1;

            while(r < n && s[r] == s[l]) r++;
            if(r - l == n) {
                flag = false;
                ans = n / 2;
                break;
            }
            if((l == 0 || r == n) && s[0] == s[n - 1])
                continue;

            ans += (r - l) / 2;
            if((r - l) % 2 == 0) t = 1;
        }
        if(flag && s[0] == s[n - 1]) {
            int l, r;
            for(l = 0; l < n && s[l] == s[0]; l++);
            for(r = n - 1; r >= 0 && s[r] == s[0]; r--);
            ans += (l + (n - 1) - r) / 2;
            if((l + n - r - 1) % 2 == 0) t = 1;
        }
        std::cout << std::max(0, ans - t) << char(10);
    }
    return 0;
}

J. The Quest for El Dorado

我和祁神讨论了半天,徐神在 L 坐牢;最后一波交换后我去写 L,徐神来写 J,成功双开双过

徐神的做法好像是基于欠着贡献先走到某个点,然后对某个颜色开堆单独维护最优的转移方案

虽然我基本没咋听懂,但你就说过没过题吧

#include <iostream>
#include <queue>

using llsi = long long signed int;
using pii = std::pair<int, int>;
using tuple = std::tuple<int, int, int>;

template<typename T>
using less_pq = std::priority_queue<T, std::vector<T>, std::greater<T>>;

void work() {
    int n, m, k;
    std::cin >> n >> m >> k;
    std::vector<std::vector<tuple>> out(n);
    
    for(int i = 0, u, v, c, l; i < m; ++i) {
        std::cin >> u >> v >> c >> l;
        u--, v--, c--;
        out[u].emplace_back(v, c, l);
        out[v].emplace_back(u, c, l);
    }

    std::vector<pii> dis(n, {1e9, 0});
    std::vector<bool> vis(n, false);
    std::vector<less_pq<std::pair<int, int>>> hang(m);
    less_pq<std::pair<pii, int>> cur;
    for(int i = 0; i < m; ++i) hang[i].push({0, 0});
    for(int i = 1, a, b; i <= k; ++i) {
        std::cin >> a >> b; a--;
        // if(hang[a].size()) std::cerr << "hang[" << a << "].top = [" << hang[a].top().first << ", " << hang[a].top().second << "]\n";
        while(hang[a].size() && hang[a].top().first <= b) {
            cur.push({{i, hang[a].top().first}, hang[a].top().second});
            hang[a].pop();
        }
        while(cur.size()) {
            auto [cur_dis, current] = cur.top(); cur.pop();
            if(vis[current]) continue;
            // std::cerr << "cur, i, cur_dis = " << current + 1 << ", " << i << ", " << cur_dis.second << char(10);
            vis[current] = 1;
            for(auto [out, color, length]: out[current]) {
                if(vis[out]) continue;
                // std::cerr << "out, color, a, i, cur_dis.second + length = " << out + 1 << ", " << color << ", " << a << ", " << i << ", " << cur_dis.second + length << char(10);
                if(color != a) {
                    hang[color].push({length, out});
                    continue;
                }
                if(cur_dis.second + length <= b && pii(i, cur_dis.second + length) < dis[out]) {
                    dis[out] = pii(i, cur_dis.second + length);
                    cur.push({dis[out], out});
                } else {
                    hang[a].push({length, out});
                    continue;
                }
            }
        }
    }
    // std::cerr << n << " " << m << " " << k << char(10);
    for(int i = 0; i < n; ++i) std::cout << int(vis[i]);
    std::cout << char(10);
}

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T; while(T--) work();
    return 0;
}

L. Trails

刚开始徐神写了个基于差分的做法,然后我画了几个图找了个反例把做法叉了,随后才意识到这题和 LIS 很像,遂用经典的二分+单调 DP 数组解决

考虑用扫描线的思路,维护一条平行于 \(x\) 轴的线,可以先假设全局一条斜线都没有算一个贡献,最后再把斜线带来的影响减去

不妨设 \(f_i\) 表示经过 \(i\) 条斜线后最小的 \(y\) 坐标是多少,不难发现 \(f\) 单调上升,且最后需要减去的影响就是 \(\sum(q-f_i)\)

要维护 \(f_i\),只需要将所有斜线按照 \(x\) 从小到大,\(y\) 从大到小排序,每次转移的时候找到最大的 \(r\) 使得 \(f_r\le y\),并将 \(f_{r+1}\) 修改为 \(y+1\) 即可,代码十分好写

#include <iostream>
#include <vector>
#include <set>
#include <algorithm>

using llsi = long long signed int;

void work() {
    int n;
    llsi p, q;
    std::cin >> n >> p >> q;
    std::vector<std::pair<int, int>> hkr;
    for(int i = 0, x, y; i < n; ++i) {
        std::cin >> x >> y;
        if(x >= p || y >= q) continue;
        hkr.emplace_back(x, y);
    }
    auto cmp=[&](const std::pair<int, int>& A,const std::pair<int, int>& B)
    {
    	return A.first!=B.first?A.first<B.first:A.second>B.second;
    };
    std::sort(hkr.begin(), hkr.end(), cmp);
    llsi ue = 0, ans = (q + 1) * q / 2 * (p + 1) + (p + 1) * p / 2 * (q + 1);
    std::vector <int> f;
    f.push_back(0);
    int l = 0;
    for(auto [x, y]: hkr) {
        ans -= (x - l) * ue;
        l = x;
        if (y>=f.back())
        {
        	f.push_back(y+1);
        	ue+=q-y;
        } else
        {
        	int p=upper_bound(f.begin(),f.end(),y)-f.begin();
        	if (p>=0&&p<f.size())
        	{
        		ue+=f[p]-(y+1);
        		f[p]=y+1;
        	}
        }
    }
    ans -= (p - l) * ue;
    std::cout << ans << char(10);
}

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T; while(T--) work();
    return 0;
}

M. Italian Cuisine

挺 trivial 的一个几何,开场 1h 左右就被祁神切了,我题意都不知道

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

using i128 = __int128;
i128 sqr(i128 x) {return x*x;}

struct Pt {
    int x, y;
    int crs(const Pt &b) const {return x*b.y - y*b.x;}
    Pt operator-(const Pt &b) const {return Pt{x-b.x, y-b.y};}
    int len2() const {return x*x + y*y;}
};
int cross(const Pt &p, const Pt &a, const Pt &b) {return (a-p).crs(b-p);}

const int N = 2e5+5;
i128 sum[N];

int solve() {
    int n; cin >> n;
    vector<Pt> pt; pt.resize(2*n+1);
    int x0, y0, r0; cin >> x0 >> y0 >> r0;
    Pt c = Pt{x0, y0};
    for (int i=0; i<n; ++i) {
        int x, y; cin >> x >> y;
        pt[i] = pt[n+i] = Pt{x, y};
    }
    pt[2*n] = pt[0];
    for (int i=1; i<=2*n; ++i) sum[i] = sum[i-1] + pt[i-1].crs(pt[i]);

    auto check = [&](int x, int y) {
        int res = cross(pt[x], pt[y], c);
        return (res > 0) && (sqr(res) >= sqr(r0) * (pt[x]-pt[y]).len2());
    };

    int ans = 0;
    for (int i=0, j=1; i<n; ++i, j=max(j, i+1)) {
        while (j<=2*n && check(i, j)) {
            // printf("i=%lld j=%lld\n", i, j);
            ans = max((i128)ans, sum[j]-sum[i]+pt[j].crs(pt[i]));
            ++j;
        }
    }
    return ans;
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T; while (T--) cout << solve() << '\n';
    return 0;
}

Postscript

感觉真得打 SUA 出题的赛站,各种知识点的题目都有,不然遇到不对胃口的场直接等死就完了

posted @ 2024-11-30 11:11  空気力学の詩  阅读(1537)  评论(3)    收藏  举报