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;
}