题目链接:https://codeforces.com/contest/1475
数论
题目大意为判断n是否有奇数因子。
考虑唯一分解定理,n的所有质因子中只有2为偶数,除去n的质因子只包含2,即n为2的正整数次幂这一情况,其余n都符合判定条件。
#include
#define ll long long
#define next next_
using namespace std;
ll _,n;
int main(){
scanf("%lld",&_);
while(_--){
scanf("%lld",&n);
if(n&1) printf("YES\n");
else{
while(n>1&&n%2==0) n>>=1;
if(n>1) printf("YES\n");
else printf("NO\n");
}
}
return 0;
}
数学
题解要求判断n能否分解为若干个2020与2021的和。
2021为2020+1,计算出n/2020与n%2020的结果,n/2020即为n可以分解出的2020的个数,n%2020为n分解后余下的数,若n%2020小于等于n/2020,则可以将多出来的n%2020平均分配到2020上,构成2021,满足条件,若n%2020大于n/2020,则不符合条件。
#include
#define ll long long
#define next next_
using namespace std;
ll _,n,a,b;
int main(){
scanf("%lld",&_);
while(_--){
scanf("%lld",&n);
a=n/2020;
b=n%2020;
if(b<=a) printf("YES\n");
else printf("NO\n");
}
return 0;
}
数学
题意要求选出两对组合,要求不能有一个人同时在两组,求可行的方案数。
利用两个数组记录男女在组合中出现的次数,再遍历一遍所有组合,用总人数减去当前组的人物编号总共出现的次数,即冲突的组数,最后要加上自己。结果要开long long存储。
#include
#define ll long long
#define next next_
using namespace std;
ll _,a,b,k,x[200010],y[200010],num1[200010],num2[200010],ans;
struct p{
ll a;
ll b;
}c[200010];
bool cmp(p x,p y){
return x.a<y.a;
}
int main(){
scanf("%lld",&_);
while(_--){
ans=0;
scanf("%lld%lld%lld",&a,&b,&k);
for(ll i=1;i<=a;i++) num1[i]=0;
for(ll i=1;i<=b;i++) num2[i]=0;
for(ll i=1;i<=k;i++){
scanf("%lld",&c[i].a);
num1[c[i].a]++;
}
for(ll i=1;i<=k;i++){
scanf("%lld",&c[i].b);
num2[c[i].b]++;
}
for(ll i=1;i<=k;i++)
ans+=k-num1[c[i].a]-num2[c[i].b]+1;
printf("%lld\n",ans/2);
}
return 0;
}
贪心,排序
题意为选出符合条件的应用,使得最少释放m的空间的同时去除的便利值最小。
这道题很像背包,但利用背包求解的时间与空间复杂度都满足不了。
正解如下:
首先若所有应用所占内存总和小于m则无解。
将应用根据便利值为1或2分别降序排序,优先去除便利值为1的应用,若在去除过程中出现释放内存大于等于m,则直接输出当前结果。若便利值为1的应用选取完仍然不满足要求,再按排序后的顺序选取2,并在选取过程中不断将多于的1应用踢出将要去除的应用中,过程中不断记录最小值,最后输出即可。
#include
#define ll long long
#define next next_
using namespace std;
ll _,n,m,suma,sumb,pos1,pos2,tot,ans,sum;
struct p{
ll a;
ll b;
}a[200010];
bool cmp(p x,p y){
if(x.b!=y.b) return x.b<y.b;
return x.a>y.a;
}
int main(){
scanf("%lld",&_);
while(_--){
suma=sumb=tot=ans=sum=0;
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i].a);
suma+=a[i].a;
}
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i].b);
sumb+=a[i].b;
}
if(suma<m) printf("-1\n");
else{
sort(a+1,a+1+n,cmp);
pos1=1;pos2=n+1;
for(ll i=1;i<=n;i++){
if(a[i].b==2){
pos2=i;
break;
}
}
ll i;
for(i=1;i<pos2;i++){
tot+=a[i].a;
ans+=a[i].b;
if(tot>=m) break;
}
if(i==pos2) i--;
sum=ans;
if(tot<m) ans=1e15;
for(ll j=pos2;j<=n;j++){
tot+=a[j].a;
sum+=a[j].b;
while(tot-a[i].a>=m&&i>=1){
tot-=a[i].a;
sum-=a[i].b;
i--;
}
if(tot>=m) ans=min(ans,sum);
}
printf("%lld\n",ans);
}
}
return 0;
}
组合数学,排序
题意为从n个数中选取k个,求取出的k个数之和最大的方案数。
首先记录所有数出现的次数,其次对输入数据进行排序,选取最大的k个数,并记录这k个数中每个数被选取的次数,之后从1-n遍历所有数,利用组合数公式求和即可。
#include
#define ll long long
#define next next_
using namespace std;
ll _,n,k,a[1010],b[1010],c[1010],ans,sum,fac[1010],inv[1010];
const ll mod=1e9+7;
ll pow(ll a,ll b){
ll sum=1;
while(b){
if(b&1) sum=sum*a%mod;
a=a*a%mod;
b>>=1;
}
return sum;
}
void init(){
fac[0]=fac[1]=inv[0]=inv[1]=1;
for(ll i=2;i<=1000;i++){
fac[i]=i*fac[i-1]%mod;
inv[i]=pow(fac[i],mod-2);
}
}
int main(){
init();
scanf("%lld",&_);
while(_--){
ans=1;
scanf("%lld%lld",&n,&k);
for(ll i=1;i<=n;i++) b[i]=c[i]=0;
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i]);
b[a[i]]++;
}
sort(a+1,a+1+n);
for(ll i=n;i>=n-k+1;i--) c[a[i]]++;
for(ll i=1;i<=n;i++){
sum=((fac[b[i]]*inv[c[i]])%mod*inv[b[i]-c[i]]%mod)%mod;
ans=ans*sum%mod;
}
printf("%lld\n",ans);
}
return 0;
}
贪心
题意是让你判断矩阵A通过行列的异或运算能否得到矩阵B。
首先遍历最左一列的数字,若A与B中的不同,则对一整行元素进行异或运算,遍历完之后应该不会有更多行异或的操作,因此,从第二列开始遍历,此时矩阵A与矩阵B在同一列中的元素应该全部相等或者全部相反,若出现矛盾,则矩阵A不能通过变换到达矩阵B。
#include
#define ll long long
#define next next_
using namespace std;
int _,n,a[1010][1010],b[1010][1010];
bool ok;
int main(){
scanf("%d",&_);
while(_--){
ok=true;
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) scanf("%1d",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) scanf("%1d",&b[i][j]);
for(int i=1;i<=n;i++){
if(a[i][1]!=b[i][1]) for(int j=1;j<=n;j++) a[i][j]^=1;
}
for(int j=2;j<=n;j++){
if(a[1][j]==b[1][j]){
for(int i=1;i<=n;i++) if(a[i][j]!=b[i][j]){
ok=false;
break;
}
}
else{
for(int i=1;i<=n;i++) if(a[i][j]==b[i][j]){
ok=false;
break;
}
}
}
if(ok) printf("YES\n");
else printf("NO\n");
}
return 0;
}
DP,数论
题意为求出至少删去几个数能使得数列中的所有数两两之间能被一方整除。
一开始我是仿照DP求不下降子列的方法做的,但那样做的时间复杂度为O(n2),显然会超时。
我们在输入数据时先统计出每个数字出现的次数num[i],用数组f[i]表示以数字i为最大值的数列中的数字个数,则对应转移方程为:f[i]=num[i]+max(f[a1],f[a2],f[a3],…,f[ak]),其中i%aj==0(1<=j<=k)。
从数字1开始枚举到200000,中途将i的所有倍数j的f[j]值都与f[i]取最大值。
#include
#define ll long long
#define next next_
using namespace std;
int _,n,a[200010],f[200010],num[200010],ans;
int main(){
scanf("%d",&_);
while(_--){
ans=0;
scanf("%d",&n);
memset(num,0,sizeof(num));
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
num[a[i]]++;
}
for(int i=1;i<=200000;i++){
f[i]+=num[i];
for(int j=2*i;j<=200000;j+=i) f[j]=max(f[j],f[i]);
}
for(int i=1;i<=200000;i++) ans=max(ans,f[i]);
printf("%d\n",n-ans);
}
return 0;
}