The 2021 CCPC Guangzhou Onsite

发布时间 2023-10-21 18:41:13作者: 空気力学の詩

Preface

逆天PKU,出的什么寄吧东西,做题全靠打表找规律,一场比赛全是\(\bmod 998244353\)

4题成功从金到铜完美区分,我们队VP的时候两个半小时就下班了,看祁神写了3h的大几何题L

虽然徐神开场一眼看出了F是个诈骗题,但推了1h无果还是要上机打表找规律才发现式子

最后和徐神一起推K推出来一个复杂度看着就很假的容斥反演式子,后面一看题解妈的居然是能过的

这场因为好多题都不会就只写过了的题吧


C. Necklace

唯一一个看起来正常点的题目,YY了好多种贪心思路才过

首先最大值最小一眼二分答案,考虑怎么检验答案\(x\)是否合法

不妨假设覆盖的第一个关键点在覆盖其区间的右端点处,即我们尽可能给它留出更多向前的余量

对于接下来的每个区间,都贪心地让它们的右端点尽可能偏右,当然还要保证每个区间内只有一个关键点

考虑如果在某次操作中无法覆盖到某个关键点了,那么说明我们需要将前面所选的区间整体向右偏移一些,如果不能移动使得其能覆盖下个关键点,则直接返回无解

否则最后看看是否能把环覆盖完全即可,前面整体可以移动的偏移量可以一边扫一边算

最后不放心还换了个方向又算了一遍,今天又交了一遍发现只做一个方向也是对的

#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;
#define gc()(is==it?it=(is=in)+fread(in,1,Q,stdin),(is==it?EOF:*is++):*is++)
const int Q=(1<<24)+1;
char in[Q],*is=in,*it=in,c;
void read(int &n){
  for(n=0;(c=gc())<'0'||c>'9';);
  for(;c<='9'&&c>='0';c=gc())n=n*10+c-48;
}
#undef gc
int n,m,a[N],b[N];
inline bool check(int *a,CI x)
{
	int left=x-1,mi=left,pos=a[1]; for (RI i=2;i<=m;++i)
	{
		if (pos+x<a[i])
		{
			int shift=a[i]-pos-x; if (shift>mi) return 0;
			left-=shift; mi-=shift; pos=a[i];
		} else
		{
			mi=min(mi,a[i]-pos-1);
			pos=min(pos+x,i!=m?a[i+1]-1:INF);
		}
	}
	return pos-a[m]+left>=a[1]-1+n-a[m];
}
signed main()
{
	//freopen("C.in","r",stdin); freopen("C.out","w",stdout);
	RI i; for (read(n),read(m),i=1;i<=m;++i) read(a[i]),b[m-i+1]=n-a[i]+1;
	int l=(n+m-1)/m,r=n,mid,ret; while (l<=r)
	{
		mid=l+(r-l)/2LL;
		if (check(a,mid)||check(b,mid)) ret=mid,r=mid-1; else l=mid+1;
	}
	return printf("%lld",ret),0;
}

F. Cactus

本场最诈骗的一题,要勇敢跳出仙人掌计数的桎梏,发现只要\(f_i\)是两两不同的最后得到的结果都是相同的

VP时我们对由徐神上去写了个\(f_i=i,f_i=i^2,f_i=i^3\)后发现输出的都是斐波那契数列,然后交了一发就过了

证明的话可以去看Tutorial只能说是有点炸裂

#include <bits/stdc++.h>

using llsi = long long signed int;

constexpr llsi mod = 998244353;

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

inline llsi F(llsi n, llsi (*f)(llsi)) {
	llsi res = 0;
	for(int i = 1; i <= n; ++i) {
		llsi pro = 1;
		for(int j = 1; j <= n; ++j) if(j != i) 
			pro *= (1 + mod + f(i) - f(i) * f(j) % mod) % mod * ksm(f(i) - f(j) + mod, mod - 2) % mod,
			pro %= mod;
		res = (res + pro) % mod;
	}
	return res;
}

llsi f[3000005];

int main() {
	int n; std::cin >> n;
	f[1] = f[2] = 1;
	for(int i = 3; i <= n; ++i) f[i] = (f[i - 1] + f[i - 2]) % mod;
	std::cout << f[n] << char(10);
	return 0;
}

H. Three Integers

略微阳间的构造题,但就是这个签到难度的题搞了我快1h

考虑当\(a>c\)时,我们有如下构造法,令\(x=a\),并选择一个最小的\(i\)使得\(ia+c>b\),令\(z=ia+c\)

最后找一个最小的\(j\)使得\(jz+b>a\),并令\(y=jz+b\),手玩一下不难发现此时的\(x,y,z\)就是一组合法解

同理我们可以找出\(b>a\)以及\(c>b\)时的构造方法,此时可以覆盖所有三个数不完全相同的情况

考虑当\(a=b=c\)时,当且仅当\(a=b=c=0\)时有解,否则一定无解

#include<cstdio>
#include<iostream>
#include<assert.h>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,a,b,c;
signed main()
{
	//freopen("H.in","r",stdin); freopen("H.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		scanf("%lld%lld%lld",&a,&b,&c); int x,y,z,i,j;
		if (a==b&&a==c&&b==c)
		{
			if (a==0) puts("YES\n1 1 1"); else puts("NO"); continue;
		}
		if (a>c) x=a,i=c>b?0:(b-c)/a+1,z=i*a+c,j=b>a?0:(a-b)/z+1,y=j*z+b;
		else if (b>a) y=b,i=a>c?0:(c-a)/b+1,x=i*b+a,j=c>b?0:(b-c)/x+1,z=j*x+c;
		else if (c>b) z=c,i=b>a?0:(a-b)/c+1,y=i*c+b,j=a>c?0:(c-a)/y+1,x=j*y+a;
		printf("YES\n%lld %lld %lld\n",x,y,z);
		//assert(x>0); assert(y>0); assert(y>0);
		//assert(x%y==a); assert(y%z==b); assert(z%x==c);
	}
	return 0;
}

I. Pudding Store

妈的遇到计数题不会做怎么办,像这种输入一个数输出一个数的直接先打个表看看

然后这题规律也很简单,当\(n\le 3\)时答案就是\(n!\);当\(n>3\)时答案就是\(2^{n-3}\times 6\)

当然具体的结论证明还是去看Tutorial吧,这题讲的还是挺详细的

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int mod=998244353;
int t,n;
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;
}
int main()
{
	//freopen("I.in","r",stdin); freopen("I.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		if (scanf("%d",&n),n==1) puts("1");
		else if (n==2) puts("2"); else if (n==3) puts("6");
		else printf("%d\n",6LL*quick_pow(2,n-3)%mod);
	}
	return 0;
}

K. Magus Night

好神的容斥反演题,感觉徐神昨天晚上推的东西和题解差不太多的说,但没想到复杂度这么炸裂的东西也能过

首先考虑对答案来个容斥,设\(T=\{\gcd(S)\le q\and \operatorname{lcm}(S)\ge p\}\),即为我们所求的,同时我们定义:

\[A=\{\gcd(S)>q\}\\ B=\{\gcd(S)\le q\and \operatorname{lcm}(S)< p\} \]

则我们用不加任何限制的答案\((\sum_{i=1}^{m} i)^n\),减去\(A+B\)就是最后要求的答案了

首先考虑\(A\)怎么算,这个是很经典的莫反,就是用\(\sum_{d|\gcd(i,j)} \mu(d)=[\gcd(i,j)=1]\)来替换\(\gcd\)的式子,得到:

\[\begin{aligned} & A=\sum_{k=q+1}^m \sum _{i=1} ^{\lfloor \frac{m}{k} \rfloor} \mu(i) \times (i+2i+3i+ \dots + {\lfloor \frac{m}{i\cdot k} \rfloor}\times i)^n \\ =& \sum_{k=q+1}^m \sum _{i=1} ^{\lfloor \frac{m}{k} \rfloor} \mu(i) \times \big(\frac{1}{2}\times ik \times {\lfloor \frac{m}{i\times k} \rfloor} ({\lfloor \frac{m}{i\times k} \rfloor}+1)\big) ^ n \end{aligned} \]

然后就是计算\(B\)了,记\(f(j)\)表示\(\operatorname{lcm}(S)=j\)的贡献,但直接求不好求我们考虑用反演

\(F(j)\)表示\(\operatorname{lcm}(S)|j\)的贡献,则\(F(j)=[\sigma(j)]^n\),其中\(\sigma(j)\)表示\(j\)的约数和函数

同时根据定义,我们有\(F(j)=\sum_{d|j} f(d)\),那么用一下反演的式子就得到\(f(j)=\sum_{d|j}\mu(d)\times [\sigma(\frac{j}{d})]^n\)

有了这个我们就可以计算\(B\)的值了,有:

\[\begin{aligned} & B=\sum _{k=1}^{m} \sum _{i=1}^{\lfloor\frac{m}{k} \rfloor} \mu(i) \times \sum _{j=1}^{\lfloor \frac{p-1}{i\times k}\rfloor} f(j) \times (i\times k)^n \\ =& \sum _{k=1}^{m} \sum _{i=1}^{\lfloor\frac{m}{k} \rfloor} \mu(i) \times (i\times k)^n \times \sum _{j=1}^{\lfloor \frac{p-1}{i\times k}\rfloor} \sum_{d|j}\mu(d)\times [\sigma(\frac{j}{d})]^n \end{aligned} \]

然后直接对着最后那个式子算即可,复杂度上界是\(O(m\log m\log^2 p)\)的,但实际跑不满可以通过

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=998244353;
int n,m,p,q,pri[N],cnt,mu[N],sigma[N],vis[N]; vector <int> frac[N];
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) 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;
}
inline void init(CI n)
{
	RI i,j; for (mu[1]=1,i=2;i<=n;++i)
	{
		if (!vis[i]) pri[++cnt]=i,mu[i]=-1;
		for (j=1;j<=cnt&&i*pri[j]<=n;++j)
		if (vis[i*pri[j]]=1,i%pri[j]) mu[i*pri[j]]=-mu[i]; else break;
	}
	for (i=1;i<=n;++i) for (j=i;j<=n;j+=i)
	frac[j].push_back(i),inc(sigma[j],i);
}
int main()
{
	//freopen("K.in","r",stdin); freopen("K.out","w",stdout);
	RI i,j,k; scanf("%d%d%d%d",&n,&m,&p,&q); init(m);
	for (i=1;i<=m;++i) sigma[i]=quick_pow(sigma[i],n);
	int ans1=quick_pow(1LL*m*(m+1)/2LL%mod,n);
	int ans2=0; for (i=q+1;i<=m;++i)
	{
		int t=m/i; for (j=1;j<=t;++j)
		inc(ans2,1LL*(mu[j]+mod)*quick_pow(1LL*(t/j)*(t/j+1)/2LL%mod*i%mod*j%mod,n)%mod);
	}
	int ans3=0; for (i=1;i<=q;++i)
	{
		int t=m/i; for (j=1;j<=t;++j)
		{
			int ret=0,lim=(p-1)/i/j;
			for (k=1;k<=lim;++k) for (auto d:frac[k])
			inc(ret,1LL*(mu[d]+mod)*sigma[k/d]%mod);
			inc(ans3,1LL*(mu[j]+mod)*quick_pow(1LL*i*j%mod,n)%mod*ret%mod);
		}
	}
	return printf("%d",(1LL*ans1-ans2-ans3+2LL*mod)%mod),0;
}

Postscript

珍爱生命,远离PKU.jpg