ACDC-2024-10

C. Divisor Chain(Problem - C - Codeforces

题目大意:给定一个数x,我们可以从它的因数中选一个y,然后用x-y替换x,最后要使得x变成1,请输出这个过程中产生的数的个数以及每个数的具体值。另外每个数最多被选择两次。

思路:这题看上去真的没什么头绪,既要选择因数,又要保证每个因数最多被选择两次。正着想确实很麻烦,我们可以倒着来看。我们从1开始推,发现1只能由2得到,那么2可以由什么数得到呢,显然k(x-2)=x,那么x=2k/(k-1),那么只有3和4是符合要求的,那么我们由此扩展a可以由什么数得到呢,k(x-a)=x,x=ka/(k-1),因为x是整数,所以要么k和k-1可以约掉,那么x=2a,要么a和k-1可以约掉,那么x=a+1,我们由此来倒推:

我们发现往后推的过程中,只有1-2-4-8-16-...这条路是可以不断产生分叉的,所以我们是不是可以想办法把x转化成满足这个形式的数,那么就要从二进制的角度来看,显然我们可以从低位开始,一位一位的减掉一个2的幂,这样每个数确实最多被用两次,那么本题就解决了。我的入手点是正着推毫无头绪,所以从1开始倒着推找到特殊情况。 

#include<bits/stdc++.h>
using namespace std;
int n;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		vector<int>q;
		for(int i=0;i<=31;i++)
		{
			if(n>>i&1) q.push_back(1<<i);
		}
		int d=q.back();
		q.pop_back();
		while(d!=1)
		{
			q.push_back(d/2);
			d/=2;
		}
		q.push_back(1);
		cout<<q.size()<<endl;
		for(auto it:q)
		{
			cout<<n<<" ";
			n-=it;
		}
		cout<<endl;
	}
}

A. Fill in the Matrix(Problem - A - Codeforces

题目大意:有一个n行m列的矩阵,每行都是一个排列,然后我们需要求每一列的mex,然后再将每一列的mex放在一起求一个mex,请给出每一行的排列的顺序,使得最后的mex最大。

思路:这里显然我们要尽可能多的得到从0开始不同的数,那么我们实际上可以通过指定每一列的mex进而凑出来,我们以一个5列的矩阵为例:

4 0 1 2 3 

3 4 0 1 2

2 3 4 0 1

1 2 3 4 0

这时刚好每一列的mex是一个值,合起来的mex是5,也就是最大的情况,此时的行列关系为n+1=m。

显然不会每次都这么好能满足这个关系,所以我们来考虑不等的时候的情况。

n-1<m:

4 0 1 2 3 

3 4 0 1 2

2 3 4 0 1

0 1 2 3 0 所以此时的mex应该是4

4 0 1 2 3 

3 4 0 1 2

0 1 2 0 0 此时的mex是3,

4 0 1 2 3 

0 1 0 0 0 此时的mex是2,

所以当n-1<m的时候,mex就是n+1,再换一个值验证也可以

n-1>m:

4 0 1 2 3 

3 4 0 1 2

2 3 4 0 1

1 2 3 4 0

1 2 3 4 0

0 1 2 3 4 并不会改变什么,因为我们可以先把前m+1行凑出来,然后随便挑一行重复写就ok了.

另外要注意只有一列的情况,只有一列的时候mex是0,因为这一列只能填0,然后这列的mex是1,总的mex是0.

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		if(m==1) 
		{
			printf("0\n");
			for(int i=1;i<=n;i++) printf("0\n");
		}
		else if(n>=m-1)
		{
			printf("%d\n",m);
			for(int i=1;i<=m-1;i++)
			{
				int k=0;
				for(int j=1;j<=i;j++) printf("%d ",m-i+j-1);
				for(int j=1;j<=m-i;j++) printf("%d ",j-1);
				printf("\n");
			}
			for(int i=1;i<=n-m+1;i++) 
			{
				printf("%d ",m-1);
				for(int j=0;j<m-1;j++) printf("%d ",j);
				printf("\n");
			}
		}
		else
		{
			printf("%d\n",n+1);
			for(int i=1;i<=n;i++)
			{
				int k=0;
				for(int j=1;j<=i;j++) printf("%d ",m-i+j-1);
				for(int j=1;j<=m-i;j++) printf("%d ",j-1);
				printf("\n");
			}
		}
	}
}

C. Slay the Dragon(Problem - C - Codeforces

题目大意:现在有n名守卫以及若干条龙,每个龙有两个属性,防御力x和攻击力y,当我们对付这条龙的时候,只能派出一名守卫,而且该守卫的能力值要大于等于x,同时剩下守卫的能力和要大于等于y。我们现在可以对守卫进行强化,能力值每增加1,产生花费为1,问如何在最小花费下完成任务。对于每条龙都是独立的,相当于我们这里是对每条龙进行预测,不一定哪条龙会来。

思路:显然我们派出的守卫要和x尽可能地接近,因为这样不会浪费过多的能力值去攻击,多了就浪费了,剩下地求和再看跟y差多少就补多少。如何找与最近地值呢,显然可以将守卫的能力排序然后二分解决。那么如何找尽可能接近的值呢,大于的接近和小于的接近都是接近,所以我们可以查找大于等于x的最小值,然后处理这个值和它前一个值。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010];
signed main()
{
	int n;
	scanf("%lld",&n);
	int sum=0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum += a[i];
	sort(a+1,a+1+n);
	int q;
	scanf("%lld",&q);
	while(q--)
	{
		int b,c;
		scanf("%lld%lld",&b,&c);
		int l=1,r=n;
		while(l<r)//找大于等于b最小的数
		{
			int mid=l+r>>1;
			if(a[mid]>=b) r=mid;
			else l=mid+1;
		}
		int res=0;
		if(a[l]<b) res=max(c-(sum-a[l]),0ll)+(b-a[l]);//全部小于b的情况
		else 
		{
			res=max(c-(sum-a[l]),0ll);
			if(l>1)//要考虑一下,有可能小于的时候需要补的少一些,将大的留着防御
			{
				int tmp=max(c-(sum-a[l-1]),0ll)+b-a[l-1];
				res = min(res,tmp);
			}
		}
		printf("%lld\n",res);
	}
}

这里也可以用lower_bound来查找。如果全部小于x的话,lower_bound(a,a+n,x)-a==a.size(),所以可以放心的使用,如果全部小于的话不会混淆。

D. Gold Rush(Problem - D - Codeforces

题目大意:现在有一堆金子,共有n块,每次可以将一堆金子分成两堆,分出来的两堆中一堆是原来的三分之一,一堆是原来的三分之二,问最后能否恰好得到一个有m块金子的堆。

思路:这里我们可以直接暴力bfs解决。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		if(m>n) printf("NO\n");
		else
		{
			queue<int>q;
			q.push(n);
			int flag=0;
			while(q.size())
			{
				auto it=q.front();
				q.pop();
				if(it==m) 
				{
					flag=1;
					break;
				}
				if(it%3) continue;
				q.push(it/3);
				q.push(it/3*2);
			}
			if(flag) printf("YES\n");
			else printf("NO\n");
		}		
	}
}

B. Vika and the Bridge(Problem - B - Codeforces

题目大意:现在有一块木板,木板上有n个格子,总共有k种颜色,每个格子染上一种颜色,我们可以改变其中一个格子的颜色,要求只能走同一种颜色的格子,问每次跨越的格子的最大值为我们的结果,需要使结果最小,问这个最小值是多少。

思路:这题求最大值最小,所以考虑二分能否用来解决。那么我们二分出一个跨越的长度,如何去检查呢?这就是这道题比较妙的地方,有很多种颜色,所以我们并不能确定到底走哪种颜色,我们可以这么来处理,维护每个颜色上一次被访问到的位置,如何此时被访问到的位置和上个位置之间的方块数量是小于等于二分值的,那么显然不需要修改中间某个格子的颜色,如果是大于的话,那么就需要考虑我们只中间的一个格子能否使得当前颜色两两之间跨过的方块数量小于等于mid,显然就是看这个距离的一半与mid的关系,我们遍历之后就可以得到每个颜色对应的修改次数,一旦有一个颜色的修改次数小于等于1,那么这个二分值就是可以的,我们可以往更小的值去找,否则我们就要找更大的值了。

#include<bits/stdc++.h>
using namespace std;
int a[200010],cnt[200010],last[200010];
int n,k;
int check(int mid)
{
	for(int i=1;i<=k;i++)
	{
		last[i]=0,cnt[i]=0;
	}
	for(int i=1;i<=n;i++)
	{
		int c=a[i];
		int d=i-last[c]-1;
		if(d>mid)
		{
			if(d/2<=mid) cnt[c]++; 
			else cnt[c]=k;//不可能实现
		}
		last[c]=i;
	}
	for(int i=1;i<=k;i++)
	{
		int d=n-last[i];
		if(d>mid)
		{
			if(d/2<=mid) cnt[i]++; 
			else cnt[i]=k;//不可能实现
		}
		if(cnt[i]<=1) return 1; 
	}
	return 0;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		int l=0,r=n-2;
		while(l<r)
		{
			int mid=l+r>>1;
			if(check(mid)) r=mid;
			else l=mid+1;
		}
		printf("%d\n",l);
	}
}

C1. Good Subarrays (Easy Version)(Problem - C1 - Codeforces

题目大意:现在有一个数组a[],我们需要从中选取一段区间,区间中的数组成一个数组b[],要使得b[i]>=i,问有多少种选法。

思路:这题是双指针一个特别巧妙地用法,我们以一个数为开头,显然可以通过遍历找到它所有符合要求的位置,假设我们从a[l]开始,符合要求的a最多到a[r],那么a[l+1]也是可以到a[r]的,由此,对于每个a[i]我们就不用从i开始遍历,可以设定一个j的值,用双指针来解决。这题关键就是想到暴力的时候有哪些部分是冗余的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n;
		scanf("%lld",&n);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		int res=0;
		for(int i=1,j=1;i<=n;i++)
		{
			int d=j;
			while(d-i+1 <= a[d]&&d<=n) d++;
			j=d-1;//d=n还成立的时候,退出循环的时候就是d=n+1了
			res += (j-i+1);//从i到j
		}
		printf("%lld\n",res);
	}
}

F. Money Trees(Problem - F - Codeforces

题目大意:现在有一排树,每棵树有两个属性,果实数量a[i]和高度h[i],我们需要挑选一个区间[l,r],使得对于区间中的这些树满足h[i]%h[i+1]==0,同时sum(a[i])<=k,问区间最长是多长。

思路:这题显然也是从一个点开始可以往后遍历到它能到的最远距离,如果每个点都这么操作的话,就会超时,而且显然如果我们找到了[l,r]是符合要求的,那么[l+1,r]也是符合要求的。所以这里可以用双指针,但是这里和上面有一些不同。我们需要统计出这个区间的一些属性,区间长度和最大值,对于这两个值我们可以维护循环外的变量来表示。定义一个cnt表示区间的个数,sum表示区间的和,另外j在往前查找的时候,一个数能不能被选是与它上一个数有关系的。所以我们还要记录一下上一个数是什么,这就是与上个题最主要不同的地方,在上个题中,一个数能不能被选,只与它到我们设定的开头的距离有关。因为维护区间的值是循环外的变量,所以我们访问下一个数的时候需要将当前树的相关属性刨掉,但是可能当前树根本不能被选,那么就相当于当前树是新开的,否则是可以被选的,如果当前树是新开的,且不可以被选的话,此时,cnt和sum刨掉当前树后都变成赋值,我们可以把它们设为0,同时如果cnt为0,那么就证明下一个树需要新开区间,不能从前面继承区间,所以我们将last的值修改成下一个位置的值,保证while循环可以正确运行。这题维护cnt当然也可以像上题一样通过相减得到,不过直接维护也行。

这题的维护和上一题还是不太一样,上一题从i新开的时候,j是停留在i-1的位置的,但是d-i+1=0,所以不会影响判断,但是这一题就不一样了,因为涉及到求相邻两个数的关系,所以last的位置一定要正确。

#include<bits/stdc++.h>
using namespace std;
int a[200010],h[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,k;
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int i=1;i<=n;i++) scanf("%d",&h[i]);
		int j=1,c=0,last=h[1],sum=0,cnt=0;
		for(int i=1;i<=n;i++)
		{
			while(j<=n&&last%h[j]==0&&sum+a[j]<=k)
			{
				cnt++;
				sum += a[j];
				last=h[j];
				j++;
			}
			c=max(c,cnt);
			//只有当前区间是新开的才会出现i不被选,那么sum和cnt本身就是0,否则当前区间就会被选,那么减掉不影响
			sum -= a[i];
			sum=max(sum,0);
			cnt--;
			cnt=max(cnt,0);
			if(!cnt) j=i+1,last=h[j];//说明下一次需要新开
		}
		printf("%d\n",c);
	}
}

 B. Make Almost Equal With Mod(Problem - B - Codeforces)

题目大意:给定一个数组a[],我们需要选一个数k,令a[i]=a[i]%k,最后使得数组中只剩下两种值,输出一个合法的k。

思路:这题的切入点在于如果既有奇数又有偶数的话,那么直接mod 2即可。那么如果只有奇数或者只有偶数呢,这个就很难办,那么我们想想取模之后得到的是什么,显然直接看不出来,由于这个mod 2,那么我们从二进制的角度看看呢,显然如果我们mod上一个2的整数次幂,那么实际就相当于留下了这个整次幂后面的尾数。所以我们可以从低位开始,看看它们从哪一位开始出现不一样,那么往前一位的2的整次幂就是结果。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200];
signed main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		int i;
		for(i=0;i<=60;i++)
		{
			int a0=0,a1=0;
			for(int j=1;j<=n;j++)
			{
				if(a[j]>>i&1) a1=1;
				else a0=1;
			}
			if(a1&&a0) break;
		}
		int ans=1ll<<i+1;
		printf("%lld\n",ans);
	}
}

B. Friendly Arrays (Problem - B - Codeforces

题目大意:给定数组a[]和数组b[],我们可以对a[]进行若干次操作,每次从b[]中挑一个bj,令a[i]=a[i] | b[j],最后求a[]异或和的最大值和最小值。

思路:我们从结果入手,a[]异或和最大,那么在a[]有奇数个元素的时候,肯定是a[]中含1的位数越多越好,在偶数个元素的时候,最小值肯定是a[]中含1的位数越多越好。那么怎么才能使它们变多呢,这里就用到了题目中的操作了,我们可以让所有的b都与a取与,这样b中的1就能全部转化到a中去了,a中含1的位数也就变多了。

#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		int q=0;
		for(int i=1;i<=m;i++)
		{
			int x;
			scanf("%d",&x);
			q |= x;
		}
		int ans1=0,ans2=0;
		if(n%2)//1多就大
		{
			for(int i=1;i<=n;i++) ans2^=a[i];
			for(int i=1;i<=n;i++)
			{
				a[i] |= q;
				ans1 ^= a[i];
			}
		}
		else
		{
			for(int i=1;i<=n;i++) ans1^=a[i];
			for(int i=1;i<=n;i++)
			{
				a[i] |= q;
				ans2 ^= a[i];
			}
		}
		printf("%d %d\n",ans2,ans1);
	}
}

C. Colorful Table(Problem - C - Codeforces

题目大意:现有一个大小为n的数组a[],我们生成一个n*n的矩阵b[][],b[i][j]=min(a[i],a[j]),给定一个整数k,我们需要得到得到一个最小的矩形,这个矩形包含所有的x(1<=x<=k),输出矩形的长+宽。

思路:我们先来想暴力做法,显然我们对于每个数循环遍历其他的数,如果得到的min值是它本身的话,那么就说明一个(i,j),(j,i)两个点确定的矩形是包含它的,那么可以维护出所有包含它的矩形的最大值和最小值,然后我们想要的矩形就得到了,但是暴力肯定会超时,所以就要来找规律。这里的关键在于这个最小值,对于最小的数而言,所有的j与它的min都是它本身,所以它的边界肯定是整个矩形,对于第二小的数而言,它只有与最小的数的min不是它自己,所以我们需要从剩下的数中找到最大值和最小值。这里我们可以用一个set来维护所有的位置,同时由于不同位置上的值可能相同,所以我们把每个值对应的位置记录下来,然后从小到大去遍历,对于每一个值,我们去当前的set中取出首尾元素,然后求出目标值,然后将当前值对应的所有位置弹出。然后这个问题就解决了。

#include<bits/stdc++.h>
using namespace std;
int cnt[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,k;
		scanf("%d%d",&n,&k);
		for(int i=1;i<=k;i++) cnt[i]=0;
		vector<int>q[k+1];
		multiset<int>s;
		for(int i=1;i<=n;i++)
		{
			int x;
			scanf("%d",&x);
			cnt[x]++;
			q[x].push_back(i);
			s.insert(i);
		}
		for(int i=1;i<=k;i++)
		{
			int ans;
			if(!cnt[i]) ans=0;
			else
			{
				int a=*s.begin(),b=*s.rbegin();
				ans=2*(b-a+1);
				for(auto it:q[i]) s.extract(it);
			}
			printf("%d ",ans);
		}
		printf("\n");
	}
}
  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值