The 2023 CCPC (Qinhuangdao) Onsite (The 2nd Universal Cup. Stage 9: Qinhuangdao)

Preface

完全披萨,最害怕的一集,2h过了5题后开始大坐牢环节

徐神开D感觉是个巨复杂的字符串讨论题,一不注意就码了200+行

然后我和祁神在下面讨论得出了I的做法,虽然是个DS题但上去写的时候一点自信没有

最后摸了半天到比赛结束后1min才调出样例,赛后又调了半小时左右才过了这题

唉这就是CCPC的强度吗,Au感觉遥不可及,这场还好前面手速够快不然Ag都保不住


A. Make SYSU Great Again I

签到构造题,按照蛇形向右下填入\(1\sim 2n\),保证每行每列都有两个数并且它们的\(\gcd\)\(1\)即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int n,k;
int main()
{
	RI i; for (scanf("%d%d",&n,&k),i=1;i<2*n;++i)
	printf("%d %d\n",(i+1)/2,(i+1)/2+(i%2?0:1));
	printf("%d %d\n",n,1); int left=k-2*n,x=1,y=1;
	while (left>0)
	{
		auto expand=[&](void)
		{
			if (y==n) ++x,y=1; else ++y;
		};
		while (x==y||x%n+1==y) expand();
		--left; printf("%d %d\n",x,y); expand();
	}
	return 0;
}

B. Yet Another Subsequence Problem

啥玩意,看题解好像是个类欧,做不了一点


C. Palindrome

究竟是什么样的字符串题能让string master徐神也折戟沉沙,原来是Hash+分类讨论+DS那没事了


D. Yet Another Coffee

这题我们队VP的时候集体犯病,搞了个相比于正解做法复杂很多的东西,但好在是给他莽过去了

考虑对于一种购买咖啡的方案,能用哪些优惠券其实只和购买的咖啡中最小的下标有关

可以定义一个双变量函数\(f(x,y)\),表示钦定构造的编号最小的咖啡为\(x\),一共买了\(y\)杯咖啡时的最小花费,其计算方法就是用\(a_x\)减去所有能用的优惠劵(记为\(g_i\)),再加上\(i\in[x+1,n]\)中最小的\(k\)\(a_i\)之和

显然当\(x\)固定时\(f(x,y)\)可以用主席树快速求值,现在考虑\(x\)变化带来的影响

注意到对于两个方案\(f(x_1,\cdot)\)\(f(x_2,\cdot)\),若\(x_1<x_2\)\(g_{x_1}<g_{x_2}\),那么显然对于任意的\(y\),均有\(f(x_1,y)<f(x_2,y)\),此时我们可以直接不考虑\(x_2\)

因此可以从后往前用一个单调栈找出所有可能成为答案的\(x_i\),用一个指针一路单调地扫过去即可

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

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cctype>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m,a[N],r,w,g[N],stk[N],rt[N],idx; vector <int> rst;
#define Tp template <typename T>
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++)
        #define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
        char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[30];
    public:
        inline FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        Tp inline void write(T x,const char ch='\n')
        {
        	if (x<0) x=-x,pc('-');
            RI ptop=0; while (pt[++ptop]=x%10,x/=10);
            while (ptop) pc(pt[ptop--]+48); pc(ch);
        }
        inline void flush(void)
        {
            fwrite(Fout,1,Ftop-Fout,stdout);
        }
        #undef tc
        #undef pc
}F;
class Segment_Tree
{
	private:
		struct segment
		{
			int ch[2],sz,sum;
		}O[N*20];
	public:
		inline void insert(int& x,CI y,CI pos,CI l=0,CI r=rst.size()-1)
		{
			O[x=++idx]=O[y]; ++O[x].sz; O[x].sum+=rst[pos]; if (l==r) return; int mid=l+r>>1; 
			if (pos<=mid) insert(O[x].ch[0],O[y].ch[0],pos,l,mid);
			else insert(O[x].ch[1],O[y].ch[1],pos,mid+1,r);
		}
		inline int query(CI now,CI rk,CI l=0,CI r=rst.size()-1)
		{
			if (l==r) return rk*rst[l]; int mid=l+r>>1;
			if (rk<=O[O[now].ch[0]].sz) return query(O[now].ch[0],rk,l,mid);
			else return O[O[now].ch[0]].sum+query(O[now].ch[1],rk-O[O[now].ch[0]].sz,mid+1,r);
		}
}SEG;
signed main()
{
	for (F.read(t);t;--t)
	{
		RI i,k; for (F.read(n),F.read(m),i=1;i<=n;++i) F.read(a[i]),g[i]=0;
		for (g[n+1]=0,i=1;i<=m;++i) F.read(r),F.read(w),g[1]+=w,g[r+1]-=w;
		for (i=1;i<=n;++i) g[i]+=g[i-1]; for (i=1;i<=n;++i) g[i]=a[i]-g[i];
		int top=0; for (i=n;i>=1;--i)
		{
			while (top&&g[stk[top]]>=g[i]) --top; stk[++top]=i;
		}
		for (rst.clear(),i=1;i<=n;++i) rst.push_back(a[i]); sort(rst.begin(),rst.end());
		for (i=1;i<=n;++i) a[i]=lower_bound(rst.begin(),rst.end(),a[i])-rst.begin();
		for (idx=0,rt[n+1]=0,i=n;i>=1;--i) SEG.insert(rt[i],rt[i+1],a[i]);
		for (k=i=1;k<=n;++k)
		{
			while (n-stk[i]+1<k) ++i;
			auto calc=[&](CI x,CI y)
			{
				if (x==n) return 0LL; return SEG.query(rt[x+1],y);
			};
			while (i+1<=top&&g[stk[i+1]]+calc(stk[i+1],k-1)<g[stk[i]]+calc(stk[i],k-1)) ++i;
			F.write(g[stk[i]]+calc(stk[i],k-1)," \n"[k==n]);
		}
	}
	return F.flush(),0;
}

PS:本题正解极其傻逼,分别考虑每张优惠券,显然将其使用在其可用区间中价格最小的那杯咖啡上一定最优,因此直接排个序求和输出即可


E. Coloring Tape

疑似是个防AK题,弃疗


F. Mystery of Prime

没有证明,全是猜测,这就是ACM的本质!

考虑如果现在有相邻的三个数\(x,y,z\),我们如果要修改\(y\)的值使得\(x+y,y+z\)都为质数,此时需要满足什么条件

有个naive的想法,因为绝大多数的偶数都不是质数,因此如果\(x,z\)的奇偶性不同,则最后无论\(y\)如何取值\(x+y,y+z\)中必然会有一个为偶数,此时显然不可能都为质数

因此我们猜测,当\(x,z\)奇偶性相同时,是否一定存在某个\(y\)使得\(x+y,y+z\)为质数,直觉告诉我们这个很对

事实上有个什么“波利尼亚克猜想”讲的就是这玩意,或者其实我们大可以写个暴力验证一下这个结论的正确性

在这个猜想成立的前提下,要处理这个题就很简单了,注意我们之前忽略了\(2\)这一特殊存在,但好在由于序列中所有数均为正数,因此有且仅有\(1+1=2\)这一种情况

因此我们大力DP,设\(f_{i,0/1/2/3}\)表示已经操作了前\(i\)个数,其中第\(i\)个数不变/变成\(1\)/变成某个偶数/变成某个不为\(1\)的奇数时的最少修改次数,转移的话大力分类讨论即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N],f[N][4]; //0: unchanged; 1: to 1; 2: to even; 3: to odd (exclude 1)
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
	for (scanf("%d",&a[i]),j=0;j<4;++j) f[i][j]=1e9;
	for (f[1][0]=0,f[1][1]=f[1][2]=f[1][3]=i=1;i<n;++i)
	{
		auto is_prime=[&](CI x)
		{
			for (RI i=2;i*i<=x;++i) if (x%i==0) return false; return true;
		};
		if (is_prime(a[i]+a[i+1])) f[i+1][0]=min(f[i+1][0],f[i][0]);
		if (a[i]%2==1) f[i+1][2]=min(f[i+1][2],f[i][0]+1);
		else f[i+1][3]=min(f[i+1][3],f[i][0]+1);
		if (is_prime(a[i]+1)) f[i+1][1]=min(f[i+1][1],f[i][0]+1);
		
		if (is_prime(1+a[i+1])) f[i+1][0]=min(f[i+1][0],f[i][1]);
		f[i+1][2]=min(f[i+1][2],f[i][1]+1); f[i+1][1]=min(f[i+1][1],f[i][1]+1);
		
		if (a[i+1]%2==1) f[i+1][0]=min(f[i+1][0],f[i][2]);
		f[i+1][1]=min(f[i+1][1],f[i][2]+1); f[i+1][3]=min(f[i+1][3],f[i][2]+1);
		
		if (a[i+1]%2==0) f[i+1][0]=min(f[i+1][0],f[i][3]);
		f[i+1][2]=min(f[i+1][2],f[i][3]+1);
	}
	int ans=f[n][0]; for (i=1;i<=3;++i) ans=min(ans,f[n][i]);
	return printf("%d",ans),0;
}

G. Path

好像是个签,我题目都没看

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

const int N = 1e5+5;
int n, m, A[N], B[N];
signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> n >> m;
	for (int i=1; i<=n; ++i) cin >> A[i];
	for (int i=1; i<=m; ++i) cin >> B[i];
	int ans=0;
	for (int i=1; i<n; ++i) ans += abs(A[i+1]-A[i]);
	for (int i=1; i<m; ++i) ans += abs(B[i+1]-B[i]);
	cout << ans << '\n';
	return 0;	
}

H. Quake and Rebuild

看着像个DS题,但我一点做不来的说


I. Phony

唉比赛的时候没想到可以线段树分裂,去写了个沟槽的两边同时二分+诡异无比的线段树上二分,细节有点多直接没写出来

这题的思路可以说是一眼,不难想到把每个数\(x\)看成二元组\((\lfloor\frac{x}{k}\rfloor,x\bmod k)\),则一次操作等价于找到最大的二元组并将其第一个元素减\(1\)

很容易想到拿个什么数据结构把所有\(\lfloor\frac{x}{k}\rfloor\)相同的数存到一起,同时按照第一维从大到小处理

我们可以动态的维护一个指针表示最大的那个数据结构中前多少大的元素已经被操作过一次了(这些数实际的第一维的值应该是存储的减去\(1\)),然后若当前的最大的结构的第一维和次大的相等了,那么就合并对于的两个数据结构

本来想着用启发式合并multiset的,但后面询问的时候要查第\(k\)大,咱也不会pb_ds,这下直接歇逼了

但好在后面发现可以写线段树合并,而且这玩意复杂度还只有一个\(\log\),遂马上滚去写

然后发现查询的时候按我的做法会有点问题,就是当前第一维最大的那个线段树中可能有若干个元素是需要实际减去\(1\)的,如果次大的线段树的第一维恰好与其相同就需要将它们合并

当时没想到其实权值线段树也是可以按排名分裂的,因此写了个外面二分答案+里面线段树上限定在某个排名区间内二分的几把玩意,最后还tmd跑过去了

秉着能过就是好代码的原则我也懒得写别的做法了,直接把屎山端上来吧

#include<cstdio>
#include<iostream>
#include<cctype>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
typedef long long LL;
const int N=500005;
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++)
        #define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
        char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[30];
    public:
        inline FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        inline void get_char(char& ch)
        {
        	while (!isalpha(ch=tc()));
        }
        Tp inline void write(T x,const char ch='\n')
        {
        	if (x<0) x=-x,pc('-');
            RI ptop=0; while (pt[++ptop]=x%10,x/=10);
            while (ptop) pc(pt[ptop--]+48); pc(ch);
        }
        inline void flush(void)
        {
            fwrite(Fout,1,Ftop-Fout,stdout);
        }
        #undef tc
        #undef pc
}F;
int n,m,rt[N],sz[N],idx; LL k,a[N],x; vector <LL> rst,module;
class Segment_Tree
{
	private:
		struct segment
		{
			int ch[2],sz;
		}O[N*20];
	public:
		inline void insert(int& x,CI pos,CI l=0,CI r=module.size()-1)
		{
			if (!x) x=++idx; ++O[x].sz; if (l==r) return; int mid=l+r>>1;
			if (pos<=mid) insert(O[x].ch[0],pos,l,mid); else insert(O[x].ch[1],pos,mid+1,r);
		}
		inline int merge(int x,int y,CI l=0,CI r=module.size()-1)
		{
			if (!x||!y) return x|y; if (l==r) return O[x].sz+=O[y].sz,x; int mid=l+r>>1;
			O[x].ch[0]=merge(O[x].ch[0],O[y].ch[0],l,mid);
			O[x].ch[1]=merge(O[x].ch[1],O[y].ch[1],mid+1,r);
			O[x].sz=O[O[x].ch[0]].sz+O[O[x].ch[1]].sz; return x;
		}
		inline LL find_kth(CI x,CI rk,CI l=0,CI r=module.size()-1)
		{
			if (l==r) return module[l]; int mid=l+r>>1;
			if (rk<=O[O[x].ch[1]].sz) return find_kth(O[x].ch[1],rk,mid+1,r);
			return find_kth(O[x].ch[0],rk-O[O[x].ch[1]].sz,l,mid);
		}
		inline int query(CI x,CI lim,const LL& grt,CI l=0,CI r=module.size()-1)
		{
			if (!x||!lim||module[r]<=grt) return 0;
			if (grt<module[l]) return min(lim,O[x].sz); int mid=l+r>>1;
			if (lim<=O[O[x].ch[1]].sz) return query(O[x].ch[1],lim,grt,mid+1,r);
			return query(O[x].ch[1],O[O[x].ch[1]].sz,grt,mid+1,r)+query(O[x].ch[0],lim-O[O[x].ch[1]].sz,grt,l,mid);
		}
}SEG;
signed main()
{
	//freopen("I.in","r",stdin); freopen("I.out","w",stdout);
	RI i; for (F.read(n),F.read(m),F.read(k),i=1;i<=n;++i)
	F.read(a[i]),rst.push_back(a[i]/k),module.push_back(a[i]%k);
	sort(rst.begin(),rst.end()); rst.erase(unique(rst.begin(),rst.end()),rst.end());
	sort(module.begin(),module.end()); module.erase(unique(module.begin(),module.end()),module.end());
	for (i=1;i<=n;++i)
	{
		int id=lower_bound(rst.begin(),rst.end(),a[i]/k)-rst.begin(); ++sz[id];
		SEG.insert(rt[id],lower_bound(module.begin(),module.end(),a[i]%k)-module.begin());
	}
	vector <LL> pfx(rst.size()); pfx[0]=sz[0];
	for (i=1;i<rst.size();++i) pfx[i]=pfx[i-1]+sz[i];
	int pos=0,id=rst.size()-1; for (i=1;i<=m;++i)
	{
		char opt; F.get_char(opt); F.read(x);
		if (opt=='C')
		{
			while (x>0)
			{
				if (pos+x<sz[id]) { pos+=x; x=0; break; }
				x-=sz[id]-pos; --rst[id]; pos=0;
				if (id>0)
				{
					LL steps=(rst[id]-rst[id-1])*sz[id];
					if (steps<=x)
					{
						x-=steps; pfx[id-1]+=sz[id]; sz[id-1]+=sz[id];
						rt[id-1]=SEG.merge(rt[id-1],rt[id]); --id;
					} else
					{
						rst[id]-=x/sz[id]; pos=x%sz[id]; x=0;
					}
				} else
				{
					rst[0]-=x/n; pos=x%n; x=0;
				}
			}
		} else
		{
			if (x<=sz[id]-pos)
			{
				F.write(rst[id]*k+SEG.find_kth(rt[id],pos+x)); continue;
			} else x-=(sz[id]-pos);
			if (id>0&&rst[id]-1==rst[id-1]&&x<=pos+sz[id-1])
			{
				LL l=0,r=k-1,mid,ret;
				while (l<=r)
				{
					mid=l+r>>1;
					if (SEG.query(rt[id],pos,mid)+SEG.query(rt[id-1],sz[id-1],mid)<x) ret=mid,r=mid-1; else l=mid+1;
				}
				F.write(rst[id-1]*k+ret); continue;
			}
			if (x<=pos)
			{
				F.write((rst[id]-1)*k+SEG.find_kth(rt[id],x)); continue;
			} else x-=pos;
			int l=0,r=id-1,mid,ret;
			while (l<=r)
			{
				mid=l+r>>1;
				if (pfx[id-1]-pfx[mid]<x) ret=mid,r=mid-1; else l=mid+1;
			}
			F.write(rst[ret]*k+SEG.find_kth(rt[ret],x-(pfx[id-1]-pfx[ret])));
		}
	}
	return F.flush(),0;
}

J. Keyi LIkes Reading

这场题的问题就是Easy太简单,Medium太难,导致前面的题极其傻逼而中期题都开不动

这题的话由于数据范围极小,我们直接大力状压DP,\(f_{mask}\)表示把长度状态为\(mask\)的单词都学了的最少天数

转移的话可以枚举子集,并预处理每个状态是否可以在一天内看完,总复杂度\(O(3^{|a_i|})\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=(1<<13)+5;
int n,W,g[N],f[N],x,c[N];
int main()
{
	RI i,j; for (scanf("%d%d",&n,&W),i=1;i<=n;++i) scanf("%d",&x),++c[x-1];
	int m=0; for (i=0;i<13;++i) if (c[i]>0) m|=(1<<i);
	vector <int> states; for (i=m;i;i=(i-1)&m) states.push_back(i);
	sort(states.begin(),states.end());
	for (auto x:states)
	{
		int sum=0; for (i=0;i<13;++i) if ((x>>i)&1) sum+=c[i];
		if (sum<=W) g[x]=1;
	}
	for (auto x:states)
	{
		f[x]=1e9; for  (i=x;i;i=(i-1)&x)
		if (g[i]) f[x]=min(f[x],f[x^i]+1);
	}
	return printf("%d",f[m]),0;
}

K. Make SYSU Great Again II

题解中认为这是本场最难的一题,但实际看来感觉还行,学校的学长队在VP的时候也是直接赛时过了这题

之后有时间遥控祁神去把这题补了(乐


L. Yet Another Maximize Permutation Subarrays

题目都没看,一眼寄


M. Inverted

没啥好说的,智力不足导致和祁神两个人画了半天也找不到关键的转化

后面补题的时候对着官方题解看了半天才看懂,由于里面有很多例图讲的也很详细所以就不细写了

总结就是Counting是一生之敌

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005,mod=998244353;
int n,x,y,vis[N],f[N],g[N]; bool act[N]; vector <int> v[N];
inline void DP(CI now,CI fa=0)
{
	if (!act[now]) return (void)(f[now]=1,g[now]=0);
	f[now]=0; g[now]=1; vis[now]=1;
	for (auto to:v[now]) if (to!=fa)
	{
		DP(to,now);
		f[now]=(1LL*f[now]*g[to]+1LL*g[now]*f[to]+2LL*f[now]*f[to])%mod;
		g[now]=(1LL*g[now]*g[to]+2LL*g[now]*f[to])%mod;
	}
}
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<n;++i)
	scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (i=1;i<n;++i)
	{
		scanf("%d",&x); act[x]=1; memset(vis,0,sizeof(vis));
		int ans=1; for (j=1;j<=n;++j) if (act[j]&&!vis[j]) DP(j),ans=1LL*ans*f[j]%mod;
		printf("%d\n",ans);
	}
	return 0;
}

Postscript

唉感觉从去年区域赛的时候到现在一个季度过去了水平也没啥变化的说,好多题该做不来还是做不来,怎么回事呢

posted @ 2024-03-12 15:34  空気力学の詩  阅读(147)  评论(0编辑  收藏  举报