有麻烦请先笑笑

萩 x H

我的时间很少,但我却有很多想法

Codeforces Round #708 (Div. 2)题解A,B,C1,C2,E1,E2

[前言]md unrated掉气死了

比赛链接:Codeforces Round #708 (Div. 2)
官方题解:Tutorial

A.Meximization

题意:给一个n元素数组,让你重新排序,求所有前缀的最大Mex和。
题解:如果一个前缀的前缀Mex=x,那他自己Mex>=x,可以知道所有大的Mex越早出现越好,因此是0,1,2,3,...,后面的随意。

#include<bits/stdc++.h>

using namespace std;
#define f(i,a,b) for(register int i=a;i<=b;++i)
#define ll long long
#define N 2005

inline int read(){int x;scanf("%d",&x);return x;}
int a[N],num[105];
void solve()
{
	memset(num,0,sizeof num);
	int n=read();
	f(i,1,n) num[read()]++;
	int Max=0;
	f(i,0,103) if(!num[i]) {
		Max=i;
		break;
	}
	f(i,0,Max-1) cout<<i<<" ",num[i]--;
	f(i,0,100) 
	{
		while(num[i]) num[i]--,cout<<i<<" ";
	}
	cout<<endl;
}
int main()
{
	int T=read();
	while(T--) solve();
	return 0;
}

B.M-arrays

题意:给一个n数字数组,和一个m值,要求将这个数组划分成最少的子数组,使之每个子数组满足任意两个相邻元素之和为m的倍数(对于只有一个元素的子数组默认满足)。
题解:相邻两个元素之和为m倍数等价于相邻两个元素模m和为m,将所有的元素按照模m的值分组,所有模m的值为x的组和模m值为m-x的组配对,这样一一配对,就可以得到结果。
不过要考虑三个细节:
1:模m的值为0的数作为一组
2:模m的值等于m/2的数为一组
3:如果模m的值为x,m-x的两组数数量分别为s1,s2,则max(s1,s2)-min(s1-s2)必须小于等于1,多出来的几个数单个数字形成组
考虑完这些,问题就简单了。

#include<bits/stdc++.h>

using namespace std;
#define f(i,a,b) for(register int i=a;i<=b;++i)
#define ll long long
#define N 100005

inline int read(){int x;scanf("%d",&x);return x;}
int a[N],cnt[N];
void solve()
{
	int n=read(),m=read(),ans=0;
	f(i,0,m) cnt[i]=0;
	f(i,1,n) 
	{
		a[i]=read();
		if(a[i]%m==0) 
		{
			ans=1;
			continue;
		}
		cnt[a[i]%m]++;
	}
	f(i,1,m-1)
	{
		if(!cnt[i]) continue;
		if(i*2==m)
		{
			ans++;
			cnt[i]=0;
			continue;
		}
		int d=m-i;
		if(cnt[d]>cnt[i]) swap(cnt[d],cnt[i]);
		if(cnt[i]-cnt[d]<=1)
		{
			ans+=1;
		}
		else 
		{
			cnt[i]-=(cnt[d]+1);
			ans+=cnt[i]+1;
		}
		cnt[d]=0;
		cnt[i]=0;
	}
	cout<<ans<<endl;
}
int main()
{
	int T=read();
	while(T--) solve();
	return 0;
}

C1.k-LCM (easy version)

题意:给你一个数n,让你求三个数x,y,z满足x+y+z=n且lcm(x,y,z)<=n/2
题解:分类讨论:
1:当n为奇数,分解成1,(n-1)/2,(n-1)/2 -> lcm(1,(n-1)/2,(n-1)/2)= (n-1)/2 <=n/2
2:当n为偶数且(n-2)%4=0,分解成2,(n-2)/2,(n-2)/2,由于(n-2)%4=0,则(n-2)/2%2=0 -> lcm(2,(n-2)/2,(n-2)/2)= (n-2)/2 <=n/2
3:如果不满足以上两种,则一定满足n%4=0,分解成n/2,n/4,n/4 -> lcm(n/2,n/4,n/4)=n/2

#include<bits/stdc++.h>

using namespace std;
#define f(i,a,b) for(register int i=a;i<=b;++i)
#define ll long long
#define N 100005

inline int read(){int x;scanf("%d",&x);return x;}
int a[N],cnt[N];
void solve()
{
	int n=read(),k=read();
	if(n&1) cout<<1<<" "<<n/2<<" "<<n/2<<endl;
	else
	{
		if((n-2)%4==0) cout<<2<<" "<<(n-2)/2<<" "<<(n-2)/2<<endl;
		else cout<<n/2<<" "<<n/4<<" "<<n/4<<endl;
	}
}
int main()
{
	int T=read();
	while(T--) solve();
	return 0;
}

C2.k-LCM (hard version)

题意:给你数n和k,让你求k个数(k>=3),满足\(a_1 + a_2 + a_3 + ... + a_k = n\)\(lcm(a_1,a_2,a_3,...,a_k)<=n/2\)
题解:更简单了,n = (k-3)+(n-k+3) ,k = (k-3)+3,其中k-3代表k-3个1,剩下的转化成C1题就行了。

#include<bits/stdc++.h>

using namespace std;
#define f(i,a,b) for(register int i=a;i<=b;++i)
#define ll long long
#define N 100005

inline int read(){int x;scanf("%d",&x);return x;}
int a[N],cnt[N];
void solve()
{
	int n=read(),k=read(),s=0;
	s=k-3;
	n=n-s;
	f(i,1,s) cout<<1<<" ";
	if(n&1) cout<<1<<" "<<n/2<<" "<<n/2<<endl;
	else
	{
		if((n-2)%4==0) cout<<2<<" "<<(n-2)/2<<" "<<(n-2)/2<<endl;
		else cout<<n/2<<" "<<n/4<<" "<<n/4<<endl;
	}
}
int main()
{
	int T=read();
	while(T--) solve();
	return 0;
}

E1.Square-free division (easy version)

题意:给一个n元素数组,(a[i]<1e7),求最少能把数组分成几段使得每段满足任意两个数的积不是平方数。
题解:将每个数进行质因数分解,得到\(x={a_1}^{b_1}*{a_2}^{b_2}*{a_3}^{b_3}*...*{a_k}^{b_k}\),\(y={a_1}^{c_1}*{a_2}^{c_2}*{a_3}^{c_3}*...*{a_k}^{c_k}\),相乘得到\(x*y={a_1}^{b_1+c_1}*{a_2}^{b_2+c_2}*{a_3}^{b_3+c_3}*...*{a_k}^{b_k+c_3}\),xy是平方数的条件是所有相同的质因子的次数的奇偶性相同,这样就有一个处理的技巧,对于x,将其所有次数为奇数的质因子相乘(只乘一次),得到的数成为mask,若mask[x]=mask[y],则xy为平方数,所以将a数组直接用其mask代替,问题就转化成了:
将a数组划分成最少的子数组使每个子数组不存在相同的数
这样问题就简单很多了,从头到尾贪心一遍就行了。

#include<bits/stdc++.h>

using namespace std;
#define f(i,a,b) for(register int i=a;i<=b;++i)
#define ll long long
#define N 100005

inline int read(){int x;scanf("%d",&x);return x;}
map<int,int>mp;
int Divide(int x)
{
	int ans=1;
	for(int i=2;i<=sqrt(x);++i)
	{
		if(x%i==0)
		{
			int s=0;
			while(x%i==0) s++,x/=i;
			if(s&1) ans=ans*i;
		}
	}
	if(x!=1) ans=ans*x;
	return ans;
}
void solve()
{
	mp.clear();
	int n=read(),k=read(),ans=0;
	for(int i=1;i<=n;++i)
	{
		int sum=Divide(read());
		if(mp.count(sum)) 
		{
			ans++;
			mp.clear();
		}
		mp[sum]=1;
	}
	cout<<ans+1<<endl;
}
int main()
{
	int T=read();
	while(T--) solve();
	return 0;
}

E2.Square-free division (hard version)

题意:这题和上一题一样,但是增加了一个条件,你最多可以改变其中的k个数。
题解:按照上题的思路,直接将每个数变成mask,问题就成了:
在可以改变k个数的条件下,将a数组划分成最少的子数组使每个子数组不存在相同的数
但是这样还是感觉不清楚,改变k个数,那我们只要改成原数组都不存在的数,那问题不就成了:
在最多可以删除k个数的条件下,将a数组划分成最少的子数组使每个子数组不存在相同的数
这样问题就突然似曾相识了,k<20,n<2e5,一下只就想到了二维dp,另dp[i][j]代表到第i个元素,删数j个,最少可以划分成几个子数组。
首先预处理一个lef[i][j]数组,代表最小的下标使得删除j个的条件下可以将[lef[i][j],i]划分成一组,意思就是在[lef[i][j],i]这个区间有最多j个数多余,去掉这些多余的数,这个区间中的每个数就最多出现一次了。
先预处理lef数组,可以在O(nk)的时间内处理出来(见下面的代码,cnt[i]代表[L,i]区间数字i出现的次数,sum代表几个数多余)

for(int i=0;i<=k;++i)
	{
		int L=1,sum=0;
		for(int j=1;j<=n;++j)
		{
			cnt[a[j]]++;
			if(cnt[a[j]]>1) sum++;
			while(sum>i)
			{
				cnt[a[L]]--;
				if(cnt[a[L]]>=1) sum--;
				L++;
			}
			lef[j][i]=L;
		}
		while(L<=n) cnt[a[L]]--,++L;
	}

lef数组预处理完后,这个问题就比较简单了,直接dp,状态转移:
dp[i][j]=min(dp[i][j],dp[lef[i][q]][j-q]+1); (q<=j)
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define f(i,a,b) for(register int i=a;i<=b;++i)
#define ll long long
#define N 200005
inline int read(){int x;scanf("%d",&x);return x;}
#define maxn 10000007
int a[N],mask[maxn];
int Divide(int x)
{
	if(mask[x]) return mask[x];
	int ans=1,c=x;
	for(int i=2;i<=sqrt(x);++i)
	{
		if(x%i==0)
		{
			int s=0;
			while(x%i==0) s++,x/=i;
			if(s&1) ans*=i;
		}
	}
	if(x!=1) ans*=x;
	mask[c]=ans;
	return ans;
}

int dp[N][22],lef[N][22],cnt[maxn];
void solve() 
{
	int n=read(),k=read();
	f(i,1,n) a[i]=Divide(read());
	for(int i=0;i<=k;++i)
	{
		int L=1,sum=0;
		for(int j=1;j<=n;++j)
		{
			cnt[a[j]]++;
			if(cnt[a[j]]>1) sum++;
			while(sum>i)
			{
				cnt[a[L]]--;
				if(cnt[a[L]]>=1) sum--;
				L++;
			}
			lef[j][i]=L;
		}
		while(L<=n) cnt[a[L]]--,++L;
		//f(j,1,n) cnt[a[j]]=0;
	}
	//f(i,0,n) f(j,0,k) dp[i][j]=99999999;
	//dp[0][0]=0;
	for(int i=1;i<=n;++i)
		for(int j=0;j<=k;++j)
		{
			dp[i][j]=2*n;
			for(int q=0;q<=j;++q)
				dp[i][j]=min(dp[i][j],dp[lef[i][q]-1][j-q]+1);
		}
	int ans=n*2;
	f(i,0,k) ans=min(ans,dp[n][i]);	
	printf("%d\n",ans);
}
int main()
{
	int T=read();
	while(T--) solve();
	return 0;
}
posted @ 2021-03-18 21:37  萩xh  阅读(92)  评论(0编辑  收藏  举报