The 9th CCPC (Harbin) Onsite(The 2nd Universal Cup. Stage 10: Harbin)

Preface

VP一下据说今年最水的CCPC,发现事实上也确实如此

3h左右Rush完6个题后发现竟然已经打进Au区了,然后开始EH双开一个不会,只能等徐神solo字符串

结果经典后2h啥也没干坐到结束,但看结果而已已经赢了太多

唉看来明年要好好研究下选赛站了,像今年Harbin和Guilin的拿奖难度完全不是一个level的啊


A. Go go Baron Bunny!

原神题,弃疗!


B. Memory

一个naive的想法是每次把之前的结果\(pre\)乘上\(\frac{1}{2}\)后再加上当前的\(a_i\),再判断其正负,但直接做精度会爆炸

但灵机一动会发现由于\(a_i\)是整数,因此要比较大小其实我们只关心有没有小数部分,因此直接拿个变量记录一下即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
const int N=100005;
int n,a[N];
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	int ret=0,flag=0; for (i=1;i<=n;++i)
	{
		auto sgn=[&](CI x)
		{
			return x>0?1:-1;
		};
		if (ret&1) flag=sgn(ret);
		ret/=2; ret+=a[i];
		if (ret==0&&flag==0) putchar('0'); else
		if (ret>0||(ret==0&&flag>0)) putchar('+');
		else putchar('-');
	}
	return 0;
}

C. Karshilov's Matching Problem II

唉string problem,直接扔给徐神然后摆烂

徐神最后写了个Z函数+莫队的奇怪东西,而且跳左右端点的复杂度竟然还不均衡

坐等徐神Debug.jpg

Upt:徐神已经补完力,没想到\(O(n\sqrt {n\log n})\)跑的还飞快

      
#include <bits/stdc++.h>

using llsi = long long signed int;

std::vector<int> get_z(const std::string &s) {
	std::vector<int> z(s.size());
	size_t n = z[0] = s.size();
	for(int i = 1, l = -1, r = -1; i < n; ++i) {
		if(i >= r) r = i;
		else if(i + z[i - l] < r) {
			z[i] = z[i - l];
			continue;
		}
ext:	l = i;
		while(r < n && s[r] == s[r - l]) r += 1;
		z[i] = r - l;
	}
	return z;
}

std::vector<int> prefix_match(const std::string &s, const std::string &t) {
	std::vector<int> z = get_z(t), res(s.size());
	size_t n = s.size(), m = t.size();
	for(int i = 0, l = -1, r = -1; i < n; ++i) {
		if(i >= r) r = i;
		else if(i + z[i - l] < r) {
			res[i] = z[i - l];
			continue;
		}
ext:	l = i;
		while(r < n && r - l < m && s[r] == t[r - l]) r += 1;
		res[i] = r - l;
	}
	return res;
}

std::vector<int> get_fail(const std::string &s) {
	std::vector<int> fail(s.size());
	size_t n = s.size();
	fail[0] = -1;
	for(int i = 1, j = -1; i < n; ++i) {
		while(~j && s[j + 1] != s[i]) j = fail[j];
		if(s[j + 1] == s[i]) j += 1;
		fail[i] = j;
	}
	return fail;
}

std::vector<int> suffix_match(const std::string &s, const std::string &t) {
	std::vector<int> fail = get_fail(t), res(s.size());
	size_t n = s.size(), m = t.size();
	for(int i = 0, j = -1; i < n; ++i) {
		while(~j && t[j + 1] != s[i]) j = fail[j];
		if(t[j + 1] == s[i]) j += 1;
		res[i] = j + 1;
	}
	return res;
}

void work() {
	int n, q, B;
	std::cin >> n >> q;
	B = std::sqrt(n * std::__lg(n));
	std::string s, t;
	std::cin >> s >> t;
	std::vector<llsi> w(n), sw(n), fw(n), ans(q);
	std::vector<int> top(n);
	std::vector<int> pm = prefix_match(t, s);
	std::vector<int> sm = suffix_match(t, s);
	std::vector<int> f = get_fail(s);
	
	
	std::vector<std::array<int, 3>> query(q);
	std::vector<int> BB(n, 0);
	for(int i = B; i < n; ++i) BB[i] = BB[i - B] + 1;
	
	for(int i = 0; i < n; ++i) {
		std::cin >> w[i];
		sw[i] = fw[i] = w[i];
		if(i > 0) sw[i] += sw[i - 1];
		
		if(f[i] >= 0) fw[i] += fw[f[i]];
	}
	
	top[0] = -1;
	
	for(int i = 1; i < n; ++i) {
		if(f[i] < 0 || i - f[i] != f[i] - f[f[i]]) top[i] = f[i];
		else                                       top[i] = top[f[i]];
	}
	
	for(auto &[l, r, _]: query) std::cin >> l >> r, l -= 1, r -= 1;
	for(int i = 0; i < q; ++i) query[i][2] = i;
	std::sort(query.begin(), query.end(), [&](
		const std::array<int, 3> &x,
		const std::array<int, 3> &y
	) {
		auto [xl, xr, xi] = x;
		auto [yl, yr, yi] = y;
		return BB[xl] == BB[yl]
			? xr < yr
			: BB[xl] < BB[yl];
	});
	
	auto fetch_right = [&] (int l, int r) -> llsi {
		int s = sm[r] - 1;
		
		if(s < 0) return 0;
		while(top[s] + 1 > r - l + 1) s = top[s];
		
		if(s + 1 > r - l + 1) {
			int dlt = s - f[s];
			s -= (s - (r - l) + (dlt - 1)) / dlt * dlt;
		}
		
		return s < 0 ? 0 : fw[s];
	};
		
	int l = 0, r = -1;
	llsi cans = 0;
	for(auto [ql, qr, id]: query) {
		while(l > ql) {
			--l;
			auto t = std::min(pm[l], r - l + 1);
			cans += t > 0 ? sw[t - 1] : 0;
		}
		while(r < qr) {
			++r;
			cans += fetch_right(l, r);
		}
		while(l < ql) {
			auto t = std::min(pm[l], r - l + 1);
			cans -= t > 0 ? sw[t - 1] : 0;
			l++;
		}
		while(r > qr) {
			cans -= fetch_right(l, r);
			r--;
		}
		ans[id] = cans;
	}
	for(int i = 0; i < q; ++i) std::cout << ans[i] << char(10);
	
	return ;
}

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


D. A Simple MST Problem

奇思妙想题

首先考虑如果区间内存在某个质数\(P\),则对于两个数\(x,y\),除非\(w(x)=w(\operatorname{LCM}(x,y))\)(即\(x\)对应的质因子集合为\(y\)对应的质因子集合的子集),否则不如用\(w(x)+1\)的代价直接把\(x\)\(P\)连起来

因此现在的做法就很显然了,先把所有质因子集合有包含关系的点连起来,最后把每个连通块和\(P\)连起来即可

有一种比较好的处理方法是,对于某个数\(x\),我们令\(g(x)\)为它的质因数集合中所有数的乘积(由于有去重,因此\(g(12)=2\times 3=6;g(27)=3\)

此时\(x\)对应的质因子集合为\(y\)对应的质因子集合的子集等价于\(g(x)\)\(g(y)\)的约数,那么直接在上面跑一个调和级数的枚举即可

\(M=\sum r_i\),总复杂度\(O(M\log M)\)

但如果区间内没有质数怎么办呢,不难发现这样的区间长度一定不会很长,我们可以直接暴力跑生成树

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=1e6+5;
struct edge
{
	int x,y,w;
	inline edge(CI X=0,CI Y=0,CI W=0)
	{
		x=X; y=Y; w=W;
	}
	friend inline bool operator < (const edge& A,const edge& B)
	{
		return A.w<B.w;
	}
}; int t,l,r,w[N],g[N],vis[N],sz[N],is_prime[N],fa[N];
inline void init(CI n)
{
	RI i,j; for (i=1;i<=n;++i) g[i]=1;
	for (i=2;i<=n;++i) if (!w[i])
	{
		is_prime[i]=1; g[i]=i; w[i]=1;
		for (j=i*2;j<=n;j+=i) ++w[j],g[j]=g[j]*i;
	}
}
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
	for (scanf("%d",&t),init(1e6);t;--t)
	{
		RI i,j; scanf("%d%d",&l,&r); int ans=0;
		if (l==1)
		{
			for (i=2;i<=r;++i) ans+=w[i];
			printf("%d\n",ans); continue;
		}
		bool has_prime=0;
		for (i=l;i<=r;++i) if (is_prime[i]) has_prime=1;
		if (has_prime)
		{
			for (i=1;i<=r;++i) sz[i]=vis[i]=0;
			for (i=l;i<=r;++i) ++sz[g[i]];
			for (i=2;i<=r;++i) if (!vis[i]&&sz[i])
			{
				ans+=w[i]*(sz[i]-1)+(w[i]+1); vis[i]=1;
				for (j=i*2;j<=r;j+=i) if (!vis[j]&&sz[j])
				vis[j]=1,ans+=w[j]*sz[j];
			}
			printf("%d\n",ans-2);
		} else
		{
			vector <edge> E; for (i=l;i<=r;++i) fa[i]=i;
			for (i=l;i<=r;++i) for (j=l;j<=r;++j)
			E.push_back(edge(i,j,w[i]+w[j]-w[__gcd(i,j)]));
			sort(E.begin(),E.end());
			for (auto [x,y,w]:E)
			{
				if (getfa(x)==getfa(y)) continue;
				ans+=w; fa[getfa(x)]=getfa(y);
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}

E. Revenge on My Boss

看题解看的一头雾水的贪心题,祁神执意按自己理解Rush一个贪法然后寄了

Upt:祁神已经完全理解贪心思路并给出详细证明,ORZ

做法啥的由于官方题解中都讲了就不再赘述,这里补上官方题解省略的证明部分:

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

using pii = pair<int, int>;
#define ft first
#define sd second

const int N = 1e5+5;
const int INF = (int)1e18+5;
int t, n, A[N], B[N], C[N], D[N];
int totB=0;
vector<int> ans;

bool check(int x){
	ans.clear();
	vector<pii> vecn, vecp;
	for (int i=1; i<=n; ++i){
		int e=x/C[i]-totB-A[i];
		if (D[i]<=0) vecn.emplace_back(e, i);
		else vecp.emplace_back(e+D[i], i);
	}
	//	sort(vecn.begin(), vecn.end(), [&](const pii &a, const pii &b){return a>b;});
	sort(vecn.begin(), vecn.end(), greater<pii>());
	sort(vecp.begin(), vecp.end());
	int pre=0;
//	printf("check(%lld)\n", x);
//	for (auto [e, pos] : vecn) printf("(%lld %lld)", e, D[pos]);
//	for (auto [e, pos] : vecp) printf("(%lld %lld)", e-D[pos], D[pos]);
//	puts("");
	
	for (auto [e, pos] : vecn){
		if (pre > e) return false;
		ans.push_back(pos);
		pre += D[pos];
	}
	for (auto [e, pos] : vecp){
		if (pre > e-D[pos]) return false;
		ans.push_back(pos);
		pre += D[pos];
	}
	return true;
}

signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> t;
	while (t--){
		cin >> n;
		totB=0;
		for (int i=1; i<=n; ++i){
			cin >> A[i] >> B[i] >> C[i];
			D[i]=A[i]-B[i];
			totB+=B[i];
		}
		
		int L=0, R=INF;
		while (L<R){
			int M = L+(R-L)/2;
			if (check(M)) R=M;
			else L=M+1;
		}
//				printf("L=%lld\n", L);
		check(L);
		for (int x : ans) cout << x << ' ';
		cout << '\n';
	}
	return 0;	
}


F. Palindrome Path

做不来捏


G. The Only Way to the Destination

由于题目中的墙总是竖着放的,因此很容易想到一列一列地考虑问题

先把一行内连续的空格位置一起考虑,如果对于相邻的两列,存在一个\(2\times 2\)的空格位置,那么显然是无解的

即等价于对于相邻的两列的空格位置,如果它们横坐标对应的区间相交的长度\(>1\),则一定无解

区间不交显然不会影响,现在只要考虑区间交长度为\(1\)的情况,不难发现由于会出现形如下图的情况(其中红色部分为墙,白色部分为空地)

此时不难发现空地连通块形成了环,因此也是无解的

考虑判掉这种情况,我们只需要把每列的白色区间看作一个点,然后把相邻的区间相交的长度\(=1\)的两个区间对应的点连边,最后看图中有没有环即可

建图的时候相邻两列连边的时候拿个two pointers扫一下即可

#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
struct ifo
{
	int l,r,id;
	inline ifo(CI L=0,CI R=0,CI ID=0)
	{
		l=L; r=R; id=ID;
	}
}; int n,m,k,idx,fa[N]; vector <ifo> v[N]; vector <pi> w[N],E;
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
	RI i; scanf("%d%d%d",&n,&m,&k);
	if (n==1) return puts("YES"),0;
	if (m>2*k+1) return puts("NO"),0;
	for (i=1;i<=k;++i)
	{
		int x_1,x_2,y; scanf("%d%d%d",&x_1,&x_2,&y);
		w[y].push_back(pi(x_1,x_2));
	}
	for (i=1;i<=m;++i)
	{
		sort(w[i].begin(),w[i].end());
		int lst=0; for (auto [l,r]:w[i])
		{
			if (lst+1<=l-1) v[i].push_back(ifo(lst+1,l-1,++idx)); lst=r;
		}
		if (lst+1<=n) v[i].push_back(ifo(lst+1,n,++idx));
	}
	for (i=1;i+1<=m;++i)
	{
		auto interact=[&](const ifo& A,const ifo& B)
		{
			int L=max(A.l,B.l),R=min(A.r,B.r);
			return max(0,R-L+1);
		};
		for (RI p=0,q=0;p<v[i].size();++p)
		{
			if (q>0) --q;
			while (q>=0&&q<v[i+1].size()&&v[i+1][q].r<v[i][p].l) ++q;
			while (q>=0&&q<v[i+1].size()&&interact(v[i][p],v[i+1][q])!=0)
			{
				if (interact(v[i][p],v[i+1][q])>1) return puts("NO"),0;
				E.push_back(pi(v[i][p].id,v[i+1][q].id)); ++q;
			}
		}
	}
	for (i=1;i<=idx;++i) fa[i]=i;
	//for (auto [x,y]:E) printf("%d %d\n",x,y);
	for (auto [x,y]:E)
	{
		if (getfa(x)==getfa(y)) return puts("NO"),0;
		fa[getfa(x)]=getfa(y);
	}
	return puts("YES"),0;
}

H. Energy Distribution

看了题解感觉是个没啥意思的题,就感觉它说的很对但没有那种让人恍然大悟的感觉


I. Rolling For Days

woc还有云批题的吗,但是完全做不来捏


J. Game on a Forest

经典博弈全靠猜

刚开始祁神搞了个什么势能分析之类的东西,然后推出来单棵树都是先手必胜的

结果后面一看\(n=4\)的链会有问题,然后发现原来有奇数个点的树和偶数个点的树还不太一样

手玩了一波后大胆猜测对于奇数个点的树,其\(SG=1\);而偶数个点的树,其\(SG=2\)

这样我们只需要判断删掉每个点/边后剩下的森林中有多少个点数为奇数的树,有多少个点数为偶数的树

删边的情况是trivial的,而删点的情况可以用换根DP求出删掉每个点后其子树的大小,总复杂度\(O(n)\)

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

const int N = 1e5+5;
int n, m, sz[N], odd=0, even=0;
int ans=0;
vector<int> G[N];

void dfs1(int x, int f){
	sz[x]=1;
	for (int v : G[x]){
		if (v==f) continue;
		dfs1(v, x);
		sz[x]+=sz[v];
	}
}

void dfs2(int x, int f, int out){
	int o=0, e=0;
	int tsz = sz[x];
	for (int v : G[x]){
		if (v==f) continue;
		if (sz[v]%2==0) ++e; else ++o;
	}
	if (out>0){
		int te=0, to=0;
		if (sz[x]%2==0) ++te; else ++to;
		if (out%2==0) ++te; else ++to;
		if ((even+te)%2==0 && (odd+to)%2==0) ++ans;
		
		if (out%2==0) ++e; else ++o;
	}
	if ((even+e)%2==0 && (odd+o)%2==0) ++ans;
	
	for (int v : G[x]){
		if (v==f) continue;
		dfs2(v, x, out+sz[x]-sz[v]);
	}
}

signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> n >> m;
	for (int i=1; i<=m; ++i){
		int a, b; cin >> a >> b;
		G[a].push_back(b); G[b].push_back(a);
	}
	vector<int> rt;
	for (int i=1; i<=n; ++i) if (0==sz[i]){
		dfs1(i, -1);
		rt.push_back(i);
		if (sz[i]%2==0) ++even; else ++odd;
	}
	
	for (int x : rt){
		if (sz[x]%2==0) --even; else --odd;
		dfs2(x, -1, 0);
		if (sz[x]%2==0) ++even; else ++odd;
	}
	cout << ans << '\n';
	return 0;	
}

K. Omniscia Spares None

唉又是原神,还是启动不了捏


L. Palm Island

徐神开场写的签到,我题目都没看过

#include <bits/stdc++.h>

short n, a[1000], b[1000];

std::vector<int> ans;

void op1() {
	ans.push_back(1);
	int t = a[0];
	memcpy(a, a + 1, sizeof(short) * (n - 1));
	a[n - 1] = t;
}

void op2() {
	ans.push_back(2);
	int t = a[1];
	memcpy(a + 1, a + 2, sizeof(short) * (n - 2));
	a[n - 1] = t;
}

int main() {
	std::ios::sync_with_stdio(false);
	int T; std::cin >> T;
	while(T--) {
		std::cin >> n;
		ans.clear();
		for(int i = 0; i < n; ++i) std::cin >> a[i], a[i] -= 1;
		for(int i = 0; i < n; ++i) std::cin >> b[i], b[i] -= 1;
		for(int i = 1; i < n; ++i) {
			while(a[0]     != b[i    ]) op1();
			while(a[n - 1] != b[i - 1]) op2();
		}
		while(a[0] != b[0]) op1();
		for(int i = 0; i < ans.size(); ++i) std::cout << ans[i];
		std::cout << char(10);
	}
	return 0;
}

M. Painter

模拟题,直接枚举询问的每个位置然后\(O(n)\)判断它被赋上了什么字符即可

但要注意题目中对于行列的定义和正常的大不相同(题目中是直角坐标系下的,原点在左下角)

#include<cstdio>
#include<iostream>
#include<vector>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
struct ifo
{
	int tp,a,b,c,d; char w;
	inline ifo(void)
	{
		tp=a=b=c=d=w=0;
	}
}; vector <ifo> O; int n;
int main()
{
	ios::sync_with_stdio(false); cin.tie(0);
	for (cin>>n;n;--n)
	{
		string opt; cin>>opt;
		if (opt=="Circle")
		{
			O.push_back(ifo());
			cin>>O.back().b>>O.back().a>>O.back().c>>O.back().w;
		} else
		if (opt=="Rectangle")
		{
			O.push_back(ifo()); O.back().tp=1;
			cin>>O.back().b>>O.back().a>>O.back().d>>O.back().c>>O.back().w;
		} else
		{
			int x_1,y_1,x_2,y_2; cin>>x_1>>y_1>>x_2>>y_2;
			for (RI i=y_2;i>=y_1;--i) for (RI j=x_1;j<=x_2;++j)
			{
				bool flag=0;
				for (RI k=O.size()-1;k>=0;--k)
				{
					if (O[k].tp==0)
					{
						auto sqr=[&](CI x)
						{
							return 1LL*x*x;
						};
						if (sqr(i-O[k].a)+sqr(j-O[k].b)<=sqr(O[k].c))
						{
							putchar(O[k].w); flag=1; break;
						}
					} else
					{
						if (O[k].a<=i&&i<=O[k].c&&O[k].b<=j&&j<=O[k].d)
						{
							putchar(O[k].w); flag=1; break;
						}
					}
				}
				if (!flag) putchar('.');
				if (j==x_2) putchar('\n');
			}
		}
	}
	return 0;
}

Postscript

明天徐神好像有点事,而且晚上也有CF Div1,小小休假一天

posted @ 2024-03-16 16:42  空気力学の詩  阅读(68)  评论(0编辑  收藏  举报