A题
数学
题意:
为给定一个整数n,判断它是否存在一个大于1的奇数约数。
既然是约数,又是奇数,那就不能存在质因数2。
因此直接把n中的所有质因数2除去,判断是否大于1即可。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int main()
{
IOS
int t;cin>>t;
while(t--)
{
ll n;cin>>n;
while(n%2==0) n/=2;
if(n>1) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
B题
数学
题意为给定一个正整数,问你能否由自然数个2020加上自然数个2021得到。
2021可以看作2020+1。
那么一个数x如果能表示为x=a
×
\times
× 2020+ b
×
\times
× 2021=(a+b)
×
\times
× 2020 +b
×
\times
× 1
注意到2020前面的系数必定大于末尾1的系数。
故我们可以用x/2020得到2020的系数k1,再用x-2020
×
\times
× k1得到1的系数k2,比较k1和k2的大小即可。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int main()
{
IOS
int t;cin>>t;
while(t--)
{
ll n;cin>>n;
ll cas=n/2020;//2020最多能取多少个,决定了有多少个2020能+1后变成2021
if(n-cas*2020>cas) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
}
C题
组合数学
题意:
有a个男生b个女生,需要挑选两对男生女生的组合来跳舞。
有k对男生女生是愿意和对方组合的,问共有多少种选择方法。
见注释
#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
int boy[maxn],girl[maxn];//boy[i]和girl[i]分别记录标号为i的男/女生在多少对组合中出现了
int x[maxn],y[maxn];
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int a,b,k;cin>>a>>b>>k;
for(int i=0;i<k;i++) cin>>x[i];
for(int i=0;i<k;i++)
{
cin>>y[i];
boy[x[i]]++;
girl[y[i]]++;
}
ll ans=0;
for(int i=0;i<k;i++)
{
ans+=(k-boy[x[i]]-girl[y[i]]+1);
//总共k对组合
//我们选择了(x[i],y[i])这一对,那么包含了x[i]的有boy[x[i]]对,包含y[i]的有girl[y[i]]对
//其中(x[i],y[i])被重复计算了,所以包含x[i]或者y[i]的共有boy[x[i]]+girl[y[i]]-1
//那么既不包含x[i]又不包含y[i]的就是k-(boy[x[i]]+girl[y[i]]-1)对了
}
cout<<ans/2<<endl;//注意到是C(1,2)的取法,最后要除以2
for(int i=1;i<=a;i++) boy[i]=0;
for(int i=1;i<=b;i++) girl[i]=0;
}
}
D题
贪心,双指针
题意:
现在想释放m的内存,有n种app占用内存,每种app都有重要程度,用1和2表示,每种app也有占用的内存大小。
现在询问最少释放m内存的情况下,释放app的总重要程度最少是多少。
我们可以按照重要程度1和2分为两类,这两类分开去考虑。
假定重要程度1的选择i个,重要程度2的选择j个。
注意到对于同一类,我们必定贪心选择占用内存大的。
而且随着i的增大,j在不断变小。
我们可以借由双指针算法,枚举第一类的i选择多少个,移动第二类的j指针,枚举所有情况即可。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
ll a[maxn];
bool cmp(ll a,ll b)
{
return a>b;
}
int main()
{
IOS
int t;cin>>t;
while(t--)
{
ll n,m;cin>>n>>m;
ll sum=0;//sum为所有任务占用的内存综合
deque<ll>x,y;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
for(int i=1;i<=n;i++)
{
int f;
cin>>f;
if(f==1) x.push_back(a[i]);//x保存普通app的内存占用值
else y.push_back(a[i]);//y保存重要app的内存占用值
}
sort(x.begin(),x.end(),cmp);
sort(y.begin(),y.end(),cmp);
x.push_front(0);
y.push_front(0);
for(int i=1;i<x.size();i++) x[i]+=x[i-1];//前缀和后,x[i]代表清理i个普通app,最多可以清理出的内存
for(int i=1;i<y.size();i++) y[i]+=y[i-1];
if(sum<m) cout<<-1<<endl;
else
{
int ans=INF;
int tar=0;//标记重要app最少要释放几个
for(int i=x.size()-1;i>=0;i--)//i代表普通app选几个
{
while(tar<y.size()&&y[tar]+x[i]<m) tar++;//普通app选的越少,重要app对应要选的越多
if(tar<y.size()) ans=min(ans,tar*2+i);
}
cout<<ans<<endl;
}
}
}
E题
组合数学
题意:
给定n个数字,让你选择k个数出来,使得它们的和最大。问有多少种取法。
先把这n个数从小到大排序,我们肯定选择的是这n个数中最大的k个数。
那么下标n-k的数记作tar,就是我们选择的k个数中最小的。
选择的k个数中大于tar值的,是不能用其他数字代替的,一旦代替总和就变小了。能进行替换的,只有这k个数中的tar值,且只能被其他的tar值替换。
因此我们只需要计算出这k个最大的数中,我们需要x个tar值,同时给定的k个数中有y个tar值。
y个当中取x个,即C(x,y)。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int mod=1e9+7;
ll qpow(ll a,ll p)
{
ll ret=1;
while(p)
{
if(p&1) ret=ret*a%mod;
a=a*a%mod;
p>>=1;
}
return ret;
}
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n,k;cin>>n>>k;
vector<int>num(n);
for(int i=0;i<n;i++) cin>>num[i];
sort(num.begin(),num.end());
int tar=num[n-k];//选择的k个数中,最小的那个为tar
int cas0=0,cas1=0;
for(int i=0;i<n;i++)
{
if(num[i]==tar)
{
cas0++;//tar这个数字共出现了几次
if(i>=n-k) cas1++;//在选择的最大的k个数中,最小的tar要选择几次
}
}
ll ans0=1,ans1=1;
for(int i=1;i<=cas1;i++)//计算组合数C(cas0,cas1),ans0为cas0!,ans1位cas1!
{
ans0=ans0*(cas0+1-i)%mod;
ans1=ans1*i%mod;
}
cout<<ans0*qpow(ans1,mod-2)%mod<<endl;//乘法逆元计算ans0/ans1
}
}
F题
构造
题意:
给定只包含0和1的原矩阵和目标矩阵,你可以对原矩阵进行任意次如下两种操作:
1.选择第i行,第i行的所有0变为1,1变成0
2.选择第j列,第j列的所有0变为1,1变成0
实际上我们只需要关心每一行和每一列的操作次数的奇偶性即可。
比如[i][j]这个位置,由于这个位置只能被第i行和第j列的操作改变,每次操作会使得这个位置的0变1或者1变0。总的操作次数如果是x,x是奇数的时候,该位置值变为相反的情况,x是偶数的时候不变。
我们可以依据每一个位置,原矩阵和目标矩阵的值是否相同,来判断每一对i和j操作次数相加的奇偶性。
由于偶数=偶数+偶数或奇数+奇数,而奇数=偶数+奇数。
实际上也可以理解为,我们得到了每一行和每一列的操作次数之间,奇偶性是否相同的关系。
由此,我们可以以第一行的操作次数为对比基准,处理出每一列相对于第一行的操作次数奇偶性是否相同,用0和1标记。
之后再从上到下一行一行逐行比对,每行借助第一列判断出该行相对于第一行的奇偶性标记,再判断其他列与该行的关系是否存在冲突即可。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1007;
int n;
int cas[maxn];//cas[i]的值为0或者1
//cas[i]=0代表第i列的操作次数和第0行的操作次数的奇偶性相同
//cas[i]=1代表第i列的操作次数和第0行的操作次数的奇偶性相反
string field1[maxn],field2[maxn];
int main()
{
IOS
int t;cin>>t;
while(t--)
{
cin>>n;
for(int i=0;i<n;i++) cas[i]=0;
for(int i=0;i<n;i++) cin>>field1[i];
for(int i=0;i<n;i++) cin>>field2[i];
bool flag=1;
//所有的奇偶性标记均与第一行的操作次数对比
for(int j=0;j<n;j++)
{
if(abs(field1[0][j]-field2[0][j])) cas[j]=1;//如果原矩阵和目标矩阵这一位置的值不同
//代表第0行的操作次数与第j列的操作次数加起来要是奇数,也就是两者的操作次数奇偶性相反
else cas[j]=0;
}
for(int i=1;i<n;i++)
{
int temp;
if(abs(field1[i][0]-field2[i][0])) temp=!cas[0];//根据第一列的情况判断第i行操作的奇偶性标记
else temp=cas[0];
for(int j=1;j<n;j++)
{
if(abs(field1[i][j]-field2[i][j]))
{
if(temp==cas[j]) flag=0;//如果出现矛盾的情况则判断不可行
}
else
{
if(temp!=cas[j]) flag=0;
}
}
}
if(flag) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
G题
埃式筛法,dp
题意:
给定一个整数数列,要求你删除一些数字后,使得剩下的数字两两之间,其中一个能被另一个整除。
问最少需要删除多少个。
注意到题目的要求是剩下的数列中的数字两两之间必须能整除,实际上很容易能得到一个规律:
剩下的数列从小到大排序后,从左到右依次看过去,每个数都能被左侧恰好相邻的那个数整除。
我们可以借此写出一个dp关系,dp[i]代表以数值i为剩余数列最大数的情况下,数列的最长长度。
那么对于一个数字x来说,以他作为剩余数列最大数的情况下,最长的序列长度为x的所有约数i的dp[i]中的最大值,加上x出现的次数cas[x],即为dp[x]。
注意到我们这里的转移方程只需要转移约数的部分,我们可以借助埃式筛法来实现O(nlogn)级别的dp。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
int cas[maxn];//cas[i]保存i这个数字出现了几次
int dp[maxn];//dp[i]代表以i为最后剩下的数中最大的那个数的情况下,可以保留下来的最多数字个数
int main()
{
IOS
int t;cin>>t;
while(t--)
{
int n;cin>>n;
memset(cas,0,sizeof(cas));
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++)
{
int x;cin>>x;
cas[x]++;
}
int ans=0;
for(int i=1;i<maxn;i++)//埃筛过程,复杂度nlogn
{
if(cas[i])
{
dp[i]+=cas[i];
ans=max(ans,dp[i]);
for(int j=i*2;j<maxn;j+=i)
if(cas[j]) dp[j]=max(dp[j],dp[i]);//所有的数只能是最小那个数的整数倍
//也就是每个数只有从它的某一个约数转移过来,因此这里是取max而不是累加
}
}
for(int i=0;i<maxn;i++) ans=max(ans,dp[i]);
cout<<n-ans<<endl;
}
}