Codeforces Round #697 (Div. 3)A-G题解

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值