2023牛客暑期多校训练营1(D/H/J/K/M)

目录

D.Chocolate

H.Matches

J.Roulette

K.Subdivision

M.Water


D.Chocolate

思路:当n=1且m=1时候先手必输,然后1*k(k>=2)的情况下后手必输,因为先手可以选到只剩下一个格子。而在其它情况里先手第一步可以先选(1,1)的格子,然后后手无论怎么选,先手都能使得在他选完之后,使剩下来的格子形成不了矩形,直到后手将剩下1*k的矩形,此时先手必胜。

void solve() {
	int n,m;
	cin>>n>>m;
	if(n==1&&m==1) cout<<"Walk Alone"<<endl;
	else cout<<"Kelin"<<endl;
}

H.Matches

思路:我们将a[i]>=b[i]的序对称为序1,a[i]<b[i]的序对称为序2,所有的序列两两配对总共可以分为六种情况

1.序1与序1(等价于序2与序2)不交

可以看出对答案的贡献为2*(C-B)。

2.序1与序1(等价于序2与序2)相交

 

 可以看出对答案的贡献为0。

3.序1与序1(等价于序2与序2)包容

 可以看出对答案的贡献为0。

4.序1与序2(等价于序2与序1)不交

 可以看出对答案的贡献为2*(D-B)。

5.序1与序2(等价于序2与序1)相交

  可以看出对答案的贡献为-2*(B-D)。

6.序1与序2(等价于序2与序1)包容

   可以看出对答案的贡献为-2*(B-D)。

综上可得,只有两个序列对类型不同,且他们有交集时,才会对答案产生负贡献,贡献的大小为-2*相交线段长度,所以我们可以将两种序对标记一下存入容器,左端点排序后遍历寻找不同类型序对的相交线段的最大长度,具体实现见代码。

代码:

struct st {
	int l,r,id;
};
bool cmp(st a,st b) {
	return a.l<b.l;//根据左端点来从小到大排序
}
vector<st>v;
void solve() {
	int n,k,sum=0,ans=0;
	//ans储存最长相交线段
	maxx[0]=maxx[1]=-inf;
	//分别记录两种线段的前缀右端点的最大值
	cin>>n;
	for(int i=1; i<=n; i++)cin>>a[i];
	for(int i=1; i<=n; i++) {
		cin>>k;
		sum+=abs(a[i]-k);//记录原本的答案
		if(k<=a[i])v.push_back({k,a[i],0});//分为两种序对,标记存储
		else v.push_back({a[i],k,1});
	}
	sort(v.begin(),v.end(),cmp);
	for(int i=0; i<v.size(); i++) {
		int now=v[i].id;
		if(maxx[!now]>v[i].l) { //如果前缀右端点的最大值比当前的左端点大,则说明产生了交集
			if(maxx[!now]<v[i].r)ans=max(ans,maxx[!now]-v[i].l);//若小于当前右端点,交集长度则为前缀右端点的最大值-当前左端点
			else ans=max(ans,v[i].r-v[i].l);//否则,则为当前的线段长度(相当于当前线段整个都被包含)
		}
		maxx[now]=max(maxx[now],v[i].r);//更新前缀右端点的最大值
	}
	cout<<sum-2*ans<<endl;//答案减去最大的负贡献
}

J.Roulette

思路:接下来的描述中1代表赢,0代表输。我们先对它们每个1进行分治,可以看出每个1对于答案的贡献一定是1,因为连续的x-1位0对于的答案的贡献为-(2^x-1),而连续的x-1位0后的第x位1的贡献为2^x次,它们的和即为-(2^x-1)+2^x=1。

比如0001,前面三场0的负贡献分别为-1,-2,-4,总共为-7,而最后一场1的贡献为4*2=8,所以总贡献为8-7=1。

因此,Walk Alone赢的次数固定为m次。而对于每个1前面的最多有几个0我们是可以计算的,只要负贡献不大于当前的本钱就行,

每次分治Walk Alone赢的基础概率为1/2,而后面的第x个的0会产生(1/2)^(x+1)的贡献,这表示形成之前x-1情况的概率*1/2,所以每位1的总贡献为1/2+(1/2)^2...(1/2)^(零的个数+1),最后分块求和就完事了。

代码:

int qkp(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1)ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
void solve() {
	int n,m,l,r,ans=1,base=1;
	cin>>n>>m;
	for(int i=0; i<=34; i++) {
		l=max(n+1,base),r=min(n+m,base*2-1),base*=2;
		//l表示2^i,r表示2^(i+1)-1 
		if(r<l)continue;
		int sum=(qkp(2,i)-1)*qkp(qkp(2,i),mod-2)%mod;
		//(qkp(2,i)-1)*qkp(qkp(2,i),mod-2)表示的是(2^i-1)/2^i,为等比数列求和公式 
		ans=ans*qkp(sum,r-l+1)%mod;
		//答案为累乘的结果 
	}
	cout<<ans<<endl;
}

K.Subdivision

思路:根据第二个样例可看出,肯定是把点加到最后层次的边上为最优,因为若在很早就把点给加到边上了,后面本来可以往下走的边就被“堵塞”了,所以肯定是越晚加点越好,所以我们可以跑一遍bfs,若跑到叶子节点了或者和之前跑过的节点“碰头”了,则说明不能再晚加点了,只能现在加点,答案加上k-步数。若还能再跑,则答案+1,表示当前节点对于答案的贡献为1。注意判断步数与k的大小关系。

void solve() {
	int n,m,k,ans=1;
	cin>>n>>m>>k;
	for(int i=1; i<=m; i++) {
		int a,b;
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	queue<PII>q;
	q.push({1,0});
	while(!q.empty()) {
		int u=q.front().first,fa=q.front().second;
		q.pop();
		for(auto x:e[u]) {
			if(x==fa)continue;
			if(dep[x]||e[x].size()==1) {//若跑到了根节点或者碰头了,则答案加上k-步数 
				ans+=max(0ll,k-dep[u]);
				continue;
			}
			dep[x]=dep[u]+1;
			if(dep[u]+1<=k)ans++;
			q.push({x,u});
		}
	}
	cout<<ans<<endl;
}

M.Water

思路:首先,该题的x可以表示为sA+rB,也就是sA+rB=x。因为+A和+B都可以通过倒满一杯水然后喝掉来获得,而-A可以通过用一直用满杯的B杯倒满A杯,直至B杯中剩余kB-A量的水。-B亦可以通过次方式获得。既然+A,+B,-A,-B都可以获得,那么我们能够得到的水量自然能用sA+rB来表示。

根据裴蜀定理,若无解,则x%gcd(s,r)!=0,反之则一定有解。而这个sA+rB的式子可以总共分为两种情况:

1.s*r>=0,并且s和r不能同时小于等于0,因为x>=0。此时的最小操作数很显然,就是重复倒s杯容量为A的水然后喝下,重复倒r杯容量为B的水然后喝下,总操作数为2*(r+s)。

2.s*r<0,也就是s和r中有一个小于0的情况。不妨设s>r且s>0,r<0。此时表达式可以先转换一下:

sA+rB=>(s+r)A-r(A-B)

(s+r)A可以由s*r>=0时一样的方式来获得,操作数为2*(r+s),而-r(A-B)则可以通过:先倒满A杯->将A杯的水倒入B杯(此时A杯剩A-B的水,B杯满水)->喝掉A-B->倒掉B,总共四步来获得。并且最后那一步的B杯不用倒,因为B杯已经用不到了,省下了一步,总共是4*(-r)-1步。

然后我们将这两部代入公式得到:

(s+r)A-r(A-B)

=>2*(r+s)+4*(-r)-1

=>2*s-2*r-1

=>2*(s-r)-1

因此,此时的操作数至少为2*abs(s-r)+1步。

最后,我们可以先通过exgcd找出一组解,也就是s0*A+r0*B=x中的s0和r0。又因为该式子可以表示为(s0+t*B)A+(r0-t*A)B=x,也就是说解可以表示为:ss=s0+t*B,rr=r0-t*A

=>ss*A+rr*B=x

此时ss或rr趋于0的时候答案最小,计算s0+t*B=0得到t=-s0/b,计算r0-t*A=0得到t=r0/A,枚举t的上下界取min值即可。

代码:

void exgcd(int a,int b,int &x,int &y) {
	if(!b)x=1,y=0;
	else exgcd(b,a%b,y,x),y-=a/b*x;
}
void f(int t) {
	int ss=s+b*t,rr=r-a*t;
	if(rr>=0&&ss>=0)ans=min(ans,2*(rr+ss));
	else ans=min(ans,2*abs(rr-ss)-1);
}
void solve() {
	ans=inf;
	cin>>a>>b>>x;
	int g=__gcd(a,b);
	if(x%g) {
		cout<<-1<<endl;
		return;
	}
	exgcd(a,b,s,r);
	a/=g,b/=g,x/=g,s*=x,r*=x;
	for(int i=-s/b-1; i<=-s/b+1; i++)f(i);
	for(int i=r/a-1; i<=r/a+1; i++)f(i);
	cout<<ans<<endl;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值