Codeforces Round #830 (Div. 2)

A. Bestie

题目链接:A. Bestie

样例:

 input:

9
1
1
1
2
2
2 4
3
3 6 9
4
5 10 15 20
5
120 60 80 40 80
6
150 90 180 120 60 30
6
2 4 6 9 12 18
6
30 60 90 120 125 125

output:

0
1
2
2
1
3
3
0
1

题意:

给定一个长度为n的数组 a,想令 gcd( a[1]~a[n] )=1 ,即只要a数组中有两个数互质即可。有一种操作为  令a[i]=gcd(i,a[i])   代价为 n-i+1, 代价最小为多少?

思路:

代价为 n-i+1,即数组后面的数代价小,前面代价大,所以优先考虑变后面。我们知道连续的两个自然数的gcd为1,如(1,2)(5,6)  ,那么代表着只需要动后面两个数即可。这样代价范围仅为0~3

代价为0,即数组中本来存在互质数

代价为1,操作一次a[n]=gcd(a[n],n) 

代价为2,操作一次a[n-1]=gcd(a[n-1],n-1) 

那么代价最大为3,即 a[n]=gcd(a[n],n)  ,a[n-1]=gcd(a[n-1],n-1) 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=25;
int t,n;
int a[MAXN];
 
int main()
{
	ios::sync_with_stdio(false);
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		int t=a[1],t1;
		for(int i=2;i<=n;i++) t=__gcd(t,a[i]);
		if(t==1)
		{
			cout<<0<<endl;
			continue;
		}
		t1=a[n];
		a[n]=__gcd(a[n],n);
		t=a[1];
		for(int i=2;i<=n;i++) t=__gcd(t,a[i]);
		if(t==1)
		{
			cout<<1<<endl;
			continue;
		}
		a[n]=t1;
		a[n-1]=__gcd(a[n-1],n-1);
		t=a[1];
		for(int i=2;i<=n;i++) t=__gcd(t,a[i]);
		if(t==1)
			cout<<2<<endl;
		else
			cout<<3<<endl;
	}
	return 0;
}

 B. Ugu

题目链接:B. Ugu

样例 

input:

8
1
1
2
10
3
101
4
1100
5
11001
6
100010
10
0000110000
7
0101010

output:

0
1
2
1
2
3
1

 题意:

给定一个长度为n的 01串 s,有一种操作为选择一个下标 i ,让i之后的01全部翻转。问多少次操作可以使s变为递增串。

思路:

从头开始0都是合法的,但如果遇到了1,若1后面还有0,如000011100111中红色部分就是不合法的,所以必须在遇到1的块中的最后一个1,即绿色1地方进行翻转,那么后面部分的0变1,1变0;

串变为0000111  11   000,此时,原本的0变为1合法了,但原本的1变为0不合法了。

故每次遇到0的块,进行翻转,0的块变为1后,0的块的后面的1块又变为0了,所以每个块都进行一次翻转,所以从头开始遇到第一个 1时,计算1 之后的0 和 1的块数连续的1或者连续的0组成一个块。这个块数即为答案。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+1;
int T,n;
char s[MAXN];
int main()
{
	cin>>T;
	while(T--)
	{
		cin>>n;
		scanf("%s",s+1);
		int cnt=0,i;
		for(i=1;i<=n;i++)
			if(s[i]=='1') break;
		char pre='1';
		for(i;i<=n;i++)
		{
			if(s[i]!=pre) cnt++;
			pre=s[i];
		}
		cout<<cnt<<endl;
	}
	return 0;
}

D1. Balance (Easy version)

题目链接:D1. Balance (Easy version)

样例 1

input:

15
+ 1
+ 2
? 1
+ 4
? 2
+ 6
? 3
+ 7
+ 8
? 1
? 2
+ 5
? 1
+ 1000000000000000000
? 1000000000000000000

output:

3
6
3
3
10
3
2000000000000000000

 样例 2

input:

6
+ 100
? 100
+ 200
? 100
+ 50
? 50

 output:

200
300
150

 题意:

给定一个集合 T ,初始 T 中只有一个元素0,有两种操作,一种操作为‘+ x’,代表向集合T中添加一个元素 x,一种操作为 ‘? k’, 代表询问最小的不在集合T中的最小的k的倍数是多少?由于x的范围为1~1e18,故答案最大为2e18,用long long可以存下。

思路:

 map<long long,long long>mp,mp1 

其中mp记录集合T。

mp1记录询问过的集合,mp1->first记录的是已经询问过的k,mp1->second 记录上次询问的 k 得出的最小的不在集合 T 中最小k的倍数是多少。

这样对于每次询问的k,如果mp1中记录过 k已经询问过,则便从mp1中记录的位置开始寻找;否则就从k开始,每次增加k开始寻找。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+1;
typedef long long ll;
int T;
ll t;
map<ll,ll> mp,mp1;
int main()
{
	ios::sync_with_stdio(false);
	cin>>T;
	char op;
	while(T--)
	{
		cin>>op>>t;
		if(op=='+')
		{
			mp[t]=1;
		}
		else
		{
			ll i;
			if(mp1.count(t)==0)
			{
				for(i=t;;i+=t)
				{
					if(mp.count(i)==0)
					{
						cout<<i<<endl;
						break;
					}
				}
				mp1[t]=i;
			}
			else
			{
				for(i=mp1[t];;i+=t)
				{
					if(mp.count(i)==0)
					{
						cout<<i<<endl;
						break;
					}
				}
				mp1[t]=i;
			}
		}
	}
	return 0;
}

 D2. Balance (Hard version)(可能被hack,具体见评论)

题目链接:D2. Balance (Hard version)

题意:

 相比easy vesion多了一个移除操作。

给定一个集合 T ,初始 T 中只有一个元素0,有三种操作

‘+ x’,代表向集合T中添加一个元素 x

‘- x’, 代表从集合T中移除一个元素 x

‘? k’, 代表询问最小的不在集合T中的最小的k的倍数是多少?

由于x的范围为1~1e18,故答案最大为2e18,用long long可以存下。

思路:

 map<long long,long long>mp,mp1,mp2

其中mp记录集合T.

mp1记录询问过的k的集合,mp1->first记录的是已经询问过的k,mp1->second 记录上次询问的 k 得出的最小的不在集合 T 中最小k的倍数是多少。

mp2记录的为两次询问之间删除的x的集合,mp2[x]==1即代表x被删不在集合T中。

这样对于每次询问的k,mp1中未记录k则代表第一次问,则从k开始每次自加k寻找。

若以前询问过,则mp1中记录的位置可能失效,因为或许有更小的k的倍数被从T中移出了。

map是有序的,我们想判断被移除的数中是否有k的倍数,k的倍数则一定比k大;所以从mp2中第一个大于等于k的位置(用map的lower_bound寻找这个位置)开始寻找有没有比mp1中记录的位置更小的k的倍数,有则输出,没有则从上次记录的位置开始寻找,同时不断更新mp1的记录位置。

可行性分析:

设在某次询问前 加操作为n1次,减操作为n2次,询问操作n3次,这三种操作是相互制约的关系,n1+n2+n3至多为2e5,某个操作多了其他操作次数必然少。

对于某个数,从mp中查询为log(n1-n2),mp1中查询为log(n3),mp2中查询为log(n2),时间很小 相当于无影响。

最卡时间的方法一定为询问1

对于每个k,第一次询问的时间复杂度为O(n1/k)

其他情况,设在上次询问k 和本次询问k 期间 集合T中增加了 n个数。

设mp2中比k大的数有t个,以后的查询则至多为O(log(n2-t)+t)+O(n/k)

对于3s的限制很轻松

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+1;
typedef long long ll;
int T;
long long t;
map<ll,ll> mp,mp1,mp2;
int main()
{
	ios::sync_with_stdio(false);
	cin>>T;
	char op;
	int cnt=0,pre=-1;
	while(T--)
	{
		cin>>op>>t;
		bool flag=0;
		if(op=='+')
		{
			mp[t]=1;
			if(mp2.count(t)!=0) mp2.erase(t);
			cnt=1;
		}
		else if(op=='-')
		{
			mp.erase(t);
			mp2[t]=1;
			cnt=1;
		}
		else
		{
			ll i;
			if(pre==t&&cnt==0)
			{
				cout<<mp1[t]<<endl;
				continue;
			}
			cnt=0;
			if(mp1.count(t)==0)
			{
				for(i=t;;i+=t)
				{
					if(mp.count(i)==0)
					{
						cout<<i<<endl;
						break;
					}
				}
				mp1[t]=i;
			}
			else
			{
				bool f=0;
				for(auto iter=mp2.lower_bound(t);iter != mp2.end(); iter++)
			    {
			    	if(iter->first>mp1[t]) break;
			    	if((iter->first)%t==0)
			    	{
			    		f=1;
			    		cout<<iter->first<<endl;
			    		break;
					}
				}
				if(f) continue;
				for(i=mp1[t];;i+=t)
				{
					if(mp.count(i)==0)
					{
						cout<<i<<endl;
						break;
					}
				}
				mp1[t]=i;
			}
		}
		pre=t;
	}
	return 0;
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值