Codeforces Round #885 数学专场

妙,我只能说妙。今天补DEF发现除了F诡秘的杨辉三角,我都能独立做出来。

但为什么我感觉DE难度不如C!!!!

A

题意:一个人站在 (x,y) 处,而其他人分别在 (x1,y1)(xn,yn),每一次这个人先移动一步到上下左右四个格子,然后其他 n 个人再移动一步,求是否永远这个人与其他人不会走到一个位置。

题解:

对于永远等敏感字眼,我们一般是找题目中的不变量亦或者变量之间的关系

在这个题中,注意到每移动一步之后,每个人的横纵坐标和的奇偶性都会改变,所以若两个人最初坐标奇偶性不同,则永远无法相遇,反过来,两个人最初坐标一致,则无论前者怎么走,后者都有走法使得曼哈顿距离不增大,而前者撞墙之后,后者就会缩小距离,最终相遇。

所以若存在 xi+yimod2=x+ymod2,则输出 “NO”,否则输出“YES”。

int n,x,y,k,m,a[105][105];
signed main(){
	int t;cin>>t;
	while(t--){	
		cin>>n>>m>>k>>x>>y;
		int tag=0,s=(x+y)&1;
		for(int i=1;i<=k;i++){
			int x,y;cin>>x>>y;
			if(((x+y)&1)==s)tag=1;
		}
		if(tag)cout<<"No\n";
		else cout<<"Yes\n";
	}
}

B

题意:有 n 块木板排成一排,第 i 块木板的颜色是 ci,你站在第一块木板前面,需要跳跃到第 n 块木板后面,每一次只能跳相同颜色的木板。现在你可以更改一块木板的颜色,使得你每一次跳跃的距离(指两块木板中间部分,不计两端点)的最大值最小。

“最大的最小”却不是二分答案,有意思。

在本题中,由于每次只能跳同一个颜色,必然是每一个颜色单独处理。

先将所有点按颜色为第一关键字,下标为第二关键字进行排序,将同色木板分成一组,考虑单独处理这一组。

设这一组木板下标分别为 a1,a2at(a1<a2<<at),则每两块木板间的距离为 aiai11,特别地,最后一块木板里边界的距离为 nat

di=aiai1,特别地,dt+1=nat

先考虑不重新涂色,答案为 maxi=1t+1{di},若重新涂色,则必定是在 dmax 所代表的两块木板中间,以消去这个最大值,则 dmax=dmax2,最后再取最大值即可。

那么对于每个颜色都这样处理,最后取最小就是答案。

#define N 505050
#define int long long
#define pr pair<int,int>
#define mk make_pair
int n,x,y,k,m,pre[N],cnt[N];
pr c[N];
int solve(int l,int r){
	int tot=0;
	for(int i=l;i<=r;i++)cnt[++tot]=c[i].second;cnt[++tot]=n+1;
	int ans=0,mx=0,cmx=0;
	for(int i=tot;i;i--)cnt[i]=cnt[i]-cnt[i-1];
	for(int i=1;i<=tot;i++){
		mx=max(mx,cnt[i]);
	}
	for(int i=1;i<=tot;i++){
		if(mx==cnt[i]){
			cnt[i]=(cnt[i]+1)/2;mx=-1;
		}
		ans=max(ans,cnt[i]);
	}
	return ans-1;
}
signed main(){
	int t;cin>>t;
	while(t--){	
		read(n);int ans=0x3f3f3f3f,s;read(s);
		for(int i=1;i<=n;i++)read(c[i].first),c[i].second=i;
		sort(c+1,c+n+1);int l=0,r=0;c[0]=c[n+1]=mk(0,0);
		for(int i=1;i<=n;i++){
			if(c[i].first!=c[i-1].first)l=i;
			if(c[i].first!=c[i+1].first){
				ans=min(ans,solve(l,i));
			}
		}
		cout<<ans<<"\n";
	}
}

C

题意:

给定数组 a,b,定义一次操作如下:

ci=|aibi|,得到 ai=bi,bi=ci

求是否可能通过 k 次操作,使得所有的 ai 都为零。如果可能,输出“YES”,否则输出“NO”。

首先,可以发现每一个 i 是独立的。(分离独立项原则

则我们可以考虑求出对于每一个 iansi 所需要满足的条件,最后判断是否存在通解即可。

现在我们来求解 ansi,将 ai,bi 简记为 a,b

f0=a,f1=b,fi=|fi2fi1|(i2),则在第 k 次操作后,a 的值为 fk

注意到若 b 远大于 a,不妨设 b=ax+m(0m<a),则操作序列呈:

a,ax+m,a(x1)+m,a,a(x2)+m,a(x3)+m,a

不难发现规律,若 xmod2=1,则操作到 m,a 状态时需要 32x 次操作,而 xmod2=0,则操作到状态 a,m 需要 32x ,此时问题化为了同样性质的子问题,启发我们递归求解答案。

这个过程类似于欧几里得算法,不难写出如下递归函数:

int gcd(int a,int b){
    if(a==0)return 0;
    if(b==0)return 1;
    if(a>=b){
        int r=a%b,k=a/b;
        if(k%2==1)return gcd(b,r)+k+k/2;
		else return gcd(r,b)+k+k/2;
    }
    return 1+gcd(b,abs(a-b));
}

注意到在最后 a=0 后,整个循环节长度变为 3,这就给了我们求通解的机会。

ansi=xi+3k(kN),其中 xi 是第一次 a=0 时候的操作数。

则有通解的充要条件显然是所有的 xi 模三同余。

注意 ai=bi=0需要特判。

D

找循环节,不变量是解决数学题的有力手段

题意:

给定数 a 和 操作数 k,每一次可以进行两个操作:

  1. a 加上其个位数字。
  2. a 累加到答案中。

求答案的最大值。

贪心策略:所有操作一必然是在最开始进行的,证明显然。

考虑特殊性质:

  1. amod10=5,此时最多进行一次操作1。
  2. amod10 为偶数,此时 a 的个位数字随着不断地操作1最后回归到 2,4,8,6 的循环中。
  3. amod10 为奇数且不等于 5,此时进行一次操作1即可化为情况2。

对于第一种情况,答案显然为 max(ak,(a+5)(k1))

对于第二种情况。设最优情况下操作1进行的次数 c=4x+r(0r<4),则可以枚举 r,最多枚举四个,顺带更改 a,k,则化为求 (a+20x)(k4x) 的最值。这显然是个二次函数,利用顶点坐标公式即可求解。

第三种情况可以转化为第二种情况,在此略去。

#define int long long
signed main(){
	int t,a,k;
	cin>>t;
	while(t--){
		cin>>a>>k;
		if(a%10==0){
			cout<<a*k<<"\n";continue;
		}
		if(a%10ll==5ll){
			cout<<max(a*k,(a+5ll)*(k-1ll))<<"\n";continue; 
		}
		int ans=a*k;
		if(a&1ll){
			a+=a%10ll;k--;ans=max(ans,a*k);
		}
		for(int i=0;i<4;i++){
			if(!k)break;
			int aa=-80ll,bb=20ll*k-4ll*a,cc=a*k,x=-1*bb/(2ll*aa);
			if(x<0)x=0;
			x=min(x,k/4);
			ans=max(ans,x*x*aa+bb*x+cc);
			if((x+1)*4<=k){
				x++;ans=max(ans,x*x*aa+bb*x+cc);
			}
			a+=a%10ll;k--;
		}
		cout<<ans<<"\n";
	} 
}

E

挺有意思的问题,但个人觉得赛场上开题顺序如果是 ABEDCF 可能就不会是现在这悲催的结局了。

注意到:

ansi=x=1y=x[i=xyi=Xi]=x=1y=x[(x+y)(yx+1)=2Xi]

这里长得有点像约数,但不完全是约数。

注意到 x+y,yx+1 的奇偶性不同,而若确定了 x+y,yx+1 则必定可以求出合法的 x,y2Xi 也是偶数,那么其一对奇偶不同的约数即为一组解。我们可以考虑将 2 强制乘在其中一个因子上,保证其为偶数。

ansi 实际等于 Xi 的奇因子个数。

考虑如何求出 Xi 的奇因子个数,这等价于 Xi 去掉质因子2后的约数个数。记 eixi 去掉质因子2后的数,则 Xi 去掉质因子2后的数为 ci=k=0ieiansi=d(ci)

直接求显然不现实,可以考虑维护每个质因子出现次数 fi,则 ci=(fk+1)

这是一个递推的过程,可以对 ei 进行分解质因数来完成 f 的更新和答案计算,注意 x0 的范围是 109。可以写出如下代码:

#include<iostream>
using namespace std;
#define N 1005050
#define int long long
int f[N],n,p,x,ans=1;
int power(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p;
		b>>=1; 
	} 
	return ans;
}
int solve(int d){
	while((d&1)==0)d>>=1;
	for(int i=2;i*i<=d;i++){
		int cnt=0;
		while(d%i==0){
//			cout<<"prime: "<<i<<"\n";
			d/=i;++cnt;
		}
		if(cnt){
			if(!f[i]){
				ans=ans*(cnt+1)%p;f[i]=cnt;
			}
			else ans=ans*power(f[i]+1,p-2)%p*(f[i]+cnt+1)%p,f[i]+=cnt;
		}
	}
	if(d>1){
		if(d<=1e6){
			int i=d,cnt=1;
			if(!f[i]){
				ans=ans*(cnt+1)%p;f[i]=cnt;
			}
			else ans=ans*power(f[i]+1,p-2)%p*(f[i]+cnt+1)%p,f[i]+=cnt;
		} 
		else ans=ans*2ll%p; 
	}
	return ans;
}
signed main(){
	cin>>x>>n>>p;
	solve(x);
	for(int i=1;i<=n;i++){
		int d;cin>>d;solve(d);
		cout<<ans<<"\n";
	}
}

但是交上去挂了,为什么?

模数不固定,虽然是质数但可能很小,极有可能不存在逆元。

怎么办?发现我们只需要维护 f 的连乘积以及对 f 的单点修改,这完全可以使用线段树。

注意这里的时间复杂度是不正确的(O(nnlogn)),但实际远远跑不满,再加上将 106 的值域范围先筛一遍质数变成 105 后卡卡可以过。(106 内有 78498 个质数)。

这里容易被大质数卡得分解过程爆炸,可以特判一下。

#pragma GCC opzitime(3)
#include<iostream>
using namespace std;
#define N 1005050
#define int long long
int f[N],n,p,x,ans=1,pri[N],v[N],tot,id[N];
struct node{
	int l,r,mul;
}t[N<<2];
#define lc x<<1
#define rc x<<1|1
void read(int &x){
	x=0;char ch=getchar();
	while(ch>'9'||ch<'0'){
		ch=getchar();
	}
	while('0'<=ch&&ch<='9'){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
}
void build(int x,int l,int r){
	t[x].l=l,t[x].r=r,t[x].mul=1;
	if(l==r)return ;
	int mid=l+r>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
}
void updata(int x,int pos,int k){
	if(t[x].l==t[x].r){
		t[x].mul+=k;
		t[x].mul%=p;
		return ;
	}
	if(t[lc].r>=pos)updata(lc,pos,k);
	else updata(rc,pos,k);
	t[x].mul=t[lc].mul*t[rc].mul%p;
}
void solve(int d){
	while((d&1)==0)d>>=1;
	if(d<=1e6&&v[d]==0){
		updata(1,id[d],1);return ;
	}
	for(int i=2;i<=tot;i++){
		int cnt=0;
		while(d%pri[i]==0){
			d/=pri[i];++cnt;
		}
		updata(1,i,cnt);
		if(d<pri[i])break;
		if(pri[i]*pri[i]>d){
			if(d>1e6)ans=2;
			else if(v[d]==0){
				updata(1,id[d],1);
			}
			break;
		}
	}
}
void init(){
	v[1]=1;
	for(int i=2;i<=1e6;i++){
		if(!v[i]){
//			cout<<"pri: "<<i<<"\n";
			pri[++tot]=i;id[i]=tot;
		}
		for(int j=i;j<=1e6;j+=i){
			if(i!=j)v[j]=1;
		}
	}
}
signed main(){
	read(x),read(n),read(p);
	init();
	build(1,1,tot);
	solve(x);
	for(int i=1;i<=n;i++){
		int d;read(d);solve(d);
		printf("%lld\n",ans*t[1].mul%p);
	}
}

F

现在以 a0 经过操作的变化为例,依次呈现出如下变化:

0:a0=a0

1:a0=a0a1

2:a0=a0a2

3:a0=a0a1a2a3

4:a0=a0a4

5:a0=a0a1a4a5

6:a0=a0a2a4a6

7:a0=a0a1a2a3a4a5a6a7

8:a0=a0a8
……
如果说,我们写一个系数矩阵 bn=8),有:

[1000000010100000111100001000100011001100101010101111111100000000]

观察到什么了吗?bi,j=(bi1,j1+bi1,j)mod2。事实上这是一个在模2意义下的杨辉三角。

而在最后一行,变成了自己异或自己,则必然有解。-1就是骗人的。。。

我们考虑求出这个最小的解。显然当全部为0之后,后面无论执行多少次操作都是全0。

那么这个值是可二分的,换句话说是可倍增的(题目更适用倍增一些)。

我们考虑从大到小枚举增量 p=2k,最初 p=n,后不断除2。

考虑求出上一次操作到 p 次操作之后的数组 b,显然根据表格有 ai=aiaip

然后判断是否全0即可解决这个问题。复杂度 O(nlog2n)

有没有什么优化空间呢?

注意到每次“有效”的倍增更改后,i,ip 这两个位置的数其实都是 aiaip,换言之这个数组是前后相等的,我们只需保留一半继续处理即可。

时间复杂度 O(n)

#include<iostream>
using namespace std;
#define N (1<<21)
int a[N],b[N],n;
int main(){
	ios::sync_with_stdio(false);
	cin>>n;for(int i=0;i<n;i++)cin>>a[i];
	int mx=0;for(int i=0;i<n;i++)mx=max(mx,a[i]);
	if(!mx){
		cout<<"0\n";return 0;
	}
	int ans=0;
	while(n>1){
		int m=n>>1;
		for(int i=0;i<m;i++)b[i]=a[i]^a[i+m];
		mx=0;for(int i=0;i<m;i++)mx=max(mx,b[i]);
		if(mx){
			ans|=m;for(int i=0;i<m;i++)a[i]=b[i];
		}
//		else break;
		n=m;
	} 
	cout<<ans+1<<"\n";
} 
posted @   spdarkle  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示