Codeforces Round #705 (Div. 2)【A-D详细题解】

比赛链接

A. Anti-knapsack

1493 A. Anti-knapsack(思维,构造)

因为在 [ k + 1 , n ] [k+1,n] [k+1,n]内的数肯定可以拿,这里有 n − k n-k nk个数

而在 [ 1 , k ] [1,k] [1,k]内的数,我们只拿走在 [ ( k + 1 ) 2 , k − 1 ] [\frac{(k+1)}{2},k-1] [2(k+1),k1]内的数字

所以加起来,我们拿了 n − ( k + 1 ) 2 n-\frac{(k+1)}{2} n2(k+1)个数字

考虑这样做为什么是对的

首先我们知道 1 1 1 k − 1 k-1 k1不能同时拿

类似的 2 2 2 k − 2 k-2 k2不能同时拿, 3 3 3 k − 3 k-3 k3不能同时拿…

所以我们可以把 [ 1 , k − 1 ] [1,k-1] [1,k1]分成 k − 1 2 \frac{k-1}{2} 2k1组,每组内部不能同时拿

这样至少有 k − 1 2 \frac{k-1}{2} 2k1个数拿不到(当 k − 1 k-1 k1是奇数时中间那个数不分组,因为它可以拿)

所以 [ 1 , k − 1 ] [1,k-1] [1,k1]最多拿走 ( k − 1 ) − k − 1 2 (k-1)-\frac{k-1}{2} (k1)2k1个数

然后 [ k + 1 , n ] [k+1,n] [k+1,n]全拿,这样最大是 ( n − k ) + ( k − 1 ) − k − 1 2 = n − k + 1 2 (n-k)+(k-1)-\frac{k-1}{2}=n-\frac{k+1}{2} (nk)+(k1)2k1=n2k+1

然后发现我们一开始构造的就是最优解。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int n,k;
int main()
{
	int t; cin >> t;
	while( t-- )
	{
		cin >> n >> k;
		cout << n-(k+1)/2 << endl;
		for(int i=(k+1)/2;i<=n;i++)
		{
			if( i!=k )	cout << i << " ";
		}
		cout << endl;
	}
}

B. Planet Lapituletti

暴力,没什么好说的

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
string s;
int a[6],h,m;//���������
int zhuan(int x)
{
	if( x==1 )	return 1;
	if( x==2 )	return 5;
	if( x==3 )	return -1;
	if( x==4 )	return -1;
	if( x==5 )	return 2;
	if( x==6 )	return -1;
	if( x==7 )	return -1;	
	if( x==8 )	return 8;
	if( x==9 )	return -1;
} 
bool isok()
{
	a[1] = zhuan( s[4]-'0' );
	a[2] = zhuan( s[3]-'0' );
	a[3] = zhuan( s[1]-'0' );
	a[4] = zhuan( s[0]-'0' );
	if( a[1]==-1||a[2]==-1||a[3]==-1||a[4]==-1 )	return false;
	int x = a[1]*10+a[2];
	int y = a[3]*10+a[4];
//	cout << "��˵ʲô\n";
	if( x>=h||y>=m )	return false;
	return true;
}
int main()
{
	int t; cin >> t;
	while( t-- )
	{
		cin >> h >> m >> s ;
		int sum = ( s[0]-'0' )*10+(s[1]-'0');
		sum = sum*m;
		sum += ( s[3]-'0' )*10+( s[4]-'0' );
	//	cout << sum << endl;
		for(int i=sum;;i++)
		{
			if( i==h*m ){ cout << "00:00\n"; break; }
			int x = i/m, y = i%m;
			s[0] = x/10+'0', s[1] = x%10+'0';
			s[3] = y/10+'0', s[4] = y%10+'0';
			if( isok() )
			{
		//		cout << "??????\n";
				for(int j=0;j<=4;j++)	cout << s[j];
				cout << endl;
				break;
			}
		}
	}
}

1493 C. K-beautiful Strings

1493 C. K-beautiful Strings(从后往前贪心[官方题解])

转自官方题解

我们最好的答案是和原串 s s s相等

如果达不到,就看一下前 n − 1 n-1 n1个字母是否能相等,如果还达不到就看一下前 n − 2 n-2 n2个字母能不能相等…

于是我们从后往前枚举第 k k k个位置,判断 [ 1 , k − 1 ] [1,k-1] [1,k1]和原串相等,在第 k k k个位置大于原串是否可行

基于这个贪心准则,我们计算一个 c n t [ i ] cnt[i] cnt[i]表示 [ 1 , k − 1 ] [1,k-1] [1,k1]中字母 i i i的出现次数

然后计算一个 s u m sum sum表示如果 [ 1 , k − 1 ] [1,k-1] [1,k1]和原串相等,最少需要补齐多少个字母变成 k k k的倍数

s u m = ∑ i = 0 25 ( k − c n t [ i ] % k ) % k sum=\sum\limits_{i=0}^{25}(k-cnt[i]\%k)\%k sum=i=025(kcnt[i]%k)%k

但是第 k k k位比原串大,但是具体放哪个字母呢??

我们可以把这个字母贪心的从 s [ k ] + 1 s[k]+1 s[k]+1枚举到 ′ z ′ 'z' z,看最好能放哪个字母

在第 k k k位放字母 w w w,显然需要更新 s u m sum sum,此时如果 i + s u m < = n i+sum<=n i+sum<=n说明是可行的

因为第 k k k位比较大,已经保证了字典序比较大,后续只需要满足字母是 k k k的倍数即可

没有填满的部分全部填 ′ a ′ 'a' a即可

因为n是k的倍数,前面填充的部分用sum补齐了,所以也是k的倍数。

那么剩下的部分也一定是k的倍数,所以贪心放a没有问题

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int cnt[27],n,k;
string s;
int get(int x){ return (k-x%k)%k; }
int main()
{
	int t; cin >> t;
	while( t-- )
	{
		cin >> n >> k >> s;
		for(int i=0;i<n;i++)	cnt[s[i]-'a']++;
		int sum = 0, flag = 1;
		for(int i=0;i<=25;i++)	sum += get( cnt[i] );
		if( sum==0 )	cout << s << endl;
		else if( n%k!=0 )	cout << -1 << endl;	
		else
		{
			for(int i=n-1;i>=0;i--)
			{
				sum = sum-get( cnt[s[i]-'a'] )+get( --cnt[s[i]-'a'] );
				for(int j=s[i]-'a'+1;j<=25;j++)//枚举放哪个字母
				{
					int las = sum;
					sum = sum-get( cnt[j] )+get( ++cnt[j] );
					if( (i+1)+sum<=n )
					{
						for(int pos=0;pos<i;pos++)	cout << s[pos];
						cout << char( j+'a' );
						for(int pos=1;pos<=n-sum-(i+1);pos++)	cout << 'a';
						for(int w=0;w<=25;w++)
						{
							int f = get( cnt[w] );
							while( f )	f--,cout << char( w+'a' );
						}
						cout << endl; flag = 0;
						break;
					}
					cnt[j]--; sum = las;	
				}
				if( flag==0 )	break; 
			}
		}
		memset( cnt,0,sizeof cnt );
	}
}

D. GCD of an Array

1493 D. GCD of an Array(map+multiset应用)

动态维护每一个质因子的最小值,相乘就是答案.

因为每次只乘上一个数,一个数的质因子最多只有 l o g ( n ) log(n) log(n)

所以整体质因子只有 n l o g ( n ) nlog(n) nlog(n)个,空间复杂度足够

我们可以开一个二维 m a p map map记作 m p [ i ] [ j ] mp[i][j] mp[i][j]表示 a [ i ] a[i] a[i]中有多少质因子 j j j

再开一个 m u l t i s e t multiset multiset记作 c n t [ i ] cnt[i] cnt[i]

c n t [ i ] cnt[i] cnt[i]里面的数是每个 a [ i ] a[i] a[i]拥有的质因子 i i i个数,如果质因子是 0 0 0就不插入了

那么如果 c n t [ i ] cnt[i] cnt[i]里有 n n n个数,说明所有的数都有 i i i这个质因子

那么 c n t [ i ] . b e g i n ( ) cnt[i].begin() cnt[i].begin()就是所有数中最少的 i i i的质因子, i c n t [ i ] . b e g i n ( ) i^{cnt[i].begin()} icnt[i].begin()就是质因子 i i i的贡献

所以每次插入 x x x,对 x x x分解质因子,动态维护 m p [ ] [ ] mp[][] mp[][],然后动态维护 c n t cnt cnt即可

主要是利用了 m u l t i s e t multiset multiset平衡树的特性,动态给数值排序第一个数最小

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 3e5+10;
const int mod = 1e9+7;
map<int,int>mp[maxn];
multiset<int>cnt[maxn];
int nxt[maxn],n,q,ans=1;
void add(int i,int x)
{
	while( x!=1 )
	{
		int div = nxt[x], add = 0;
		while( nxt[x]==div )	add++, x=x/nxt[x];
		
		int now = mp[i][div];//之前有多少div这个质因子 
		mp[i][div] += add;
		int mi = 0;//之前所有数的最小质因子数量 
		if( cnt[div].size()==n )	mi = (*cnt[div].begin() );
		if( now )	cnt[div].erase( cnt[div].find(now) );//抹掉原来的因子 
		cnt[div].insert( now+add );//加上现在的因子 
		if( cnt[div].size()==n )//计算质因子的贡献 
		{
			int mx = (*cnt[div].begin() );
			for(int j=mi+1;j<=mx;j++)	ans = ans*div%mod;
		}
	}
}
signed main()
{
	cin >> n >> q;
	for(int i=2;i<maxn;i++)
	{
		if( nxt[i]==0 )//自己是质因子 
		{
			nxt[i] = i;
			if( i>10000 )	continue;
			for(int j=i+i;j<maxn;j+=i)
				if( nxt[j]==0 )	nxt[j] = i;
		}
	}
	int x;
	for(int i=1;i<=n;i++)	cin >> x, add(i,x);
	for(int i=1;i<=q;i++)
	{
		int l,x; cin >> l >> x;
		add(l,x);
		cout << ans << "\n";
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值