- Lucky Numbers
题目大意:
给定数字位数,即这个数的位数要小于或者等于它,且这个数字只能由7和8组成,问有多少种情况。
分析:
n位,每一位都有2种选择,2^n
n-1位,每一位,2^(n-1)
…
1位 2^1
#include <bits/stdc++.h>
using namespace std;
int n;
typedef long long ll;
ll qmi(ll a,int k){
ll res=1;
while(k){
if(k&1) res*=a;
k>>=1;
a=(ll)a*a;
}
return res;
}
int main()
{
cin>>n;
ll ans=0;
for(int i=n;i>=1;i--) ans+=qmi((ll)2,i);
cout<<ans<<endl;
return 0;
}
- Tea with Tangerines
题目大意:
有 n 块橘子皮,每块大小是a[i]。你可以做一次操作将一块橘子皮分成任意大小的两块,整个过程橘子皮总量是不变的。问要使任意两块橘子皮 ,x,y (x≤y) 都满足 2x>y 的最小操作数。
分析:
为了满足分割后每块皮都小于最小块 minsize 的两倍,我们只需将每块皮分割成若干个(minsize×2−1) 即可。
真正分的时候如果按这种思路分出了更小的皮,只需从任意另两块上取一部分给它即可,所以不会影响最终答案。
分成块数是a[i]/(minsize*2-1)上取整,操作数为块数-1
#include <bits/stdc++.h>
using namespace std;
const int N=210;
int a[N];
int t;
int n;
int main()
{
cin>>t;
while(t--){
cin>>n;
int minnum=1e9;
for(int i=0;i<n;i++){
cin>>a[i];
minnum=min(minnum,a[i]);
}
int flag=minnum*2-1;//根据最小的块,得到至少要分成这么大小
//每一块/size上取整=要分成多少块,那么分割次数就是块数-1
//上取整
int ans=0;
for(int i=0;i<n;i++) ans+=ceil(a[i]*1.0/flag)-1;
cout<<ans<<endl;
}
return 0;
}
- Counting Orders
题目大意:
求有多少种重新排列a的方式,使得对于任意1=<i<=n,都满足a[i]>b[i],结果对10^9+7取模
分析:
朴素想法:对于每一个b中的数,找a中比其大的个数乘起来
思考问题:在选择某些数的时候考虑其他位置的情况
首先,我们对数组 b 进行排序,因为它不会改变答案。
尝试从 a 的第 n 个元素 an 开始选择值,逐渐向前选择 a1。对于 ai 的选择有多少种方式?
新选择的 ai 必须满足 ai > bi。但是,一些候选值已经被选择为 aj(其中 j > i)。由于 aj > bj ≥ bi,我们知道对于所有 j > i 的值,已经有 (n-i) 个候选值被先前选择了。因此,选择 ai 的方式有 (满足 ak > bi 的 k 的数量) - (n-i) 种。
我们可以使用两个指针或二分查找来高效地找到对于每个 i,满足 ak > bi 的 k 的数量。
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
int t;
int n;
int main()
{
cin>>t;
while(t--){
cin>>n;
vector<int> a(n);
for(int i=0;i<n;i++) cin>>a[i];
sort(a.begin(),a.end());
vector<int> b(n);
for(int i=0;i<n;i++) cin>>b[i];
sort(b.begin(),b.end(),greater<int>());
ll ans=1;
for(int i=0;i<n;i++){
int temp=a.size()-(upper_bound(a.begin(),a.end(),b[i])-a.begin());
ans=ans*max(temp-i,0)%mod;
}
cout<<ans<<endl;
}
return 0;
}
- Mex Master
题目大意:给定长度为n的序列a,规定a的权值为mex{a1+a2,a2+a3,…}(mex是指一个非负整数序列中最小的未在序列中出现的整数)。现在a可以任意排列,求a的最小权值
分析:
分情况讨论
1,全是0,答案1
2,0的数量小于等于n/2,将非零和零组合,答案0
3,0的数量大于n/2:(1)0的数量和1的数量等于n,0的数量大于1的数量,答案2;(2)答案1
#include <bits/stdc++.h>
using namespace std;
int t;
int n;
int main()
{
cin>>t;
while(t--){
cin>>n;
int x;
int cnt0=0,cnt1=0;
for(int i=0;i<n;i++){
cin>>x;
if(x==0) cnt0++;
if(x==1) cnt1++;
}
//如果全是0,1
//如果一半或者更少是0,组合成非零,0
//如果不是0就是1,并且0的数量大于1,2
//如果0的数量大于一半,1
int b=(n+1)/2;
if(cnt0==n) puts("1");
else if(cnt0<=b) puts("0");
else if(cnt0+cnt1==n) puts("2");
else puts("1");
}
return 0;
}
- Come Together
题目大意:
给定无限大的网格图中 A,B,C 三点的横纵坐标,你需要求出,从 A 分别到 B 与 C 的最短路径最大有多少格点重合。
分析:
网格寻路,四个方向上下左右,也就是考虑A点和B点与C点的横纵坐标的关系
如果同向,选择两者之间较小的距离
#include<bits/stdc++.h>
using namespace std;
int t;
int ax,ay,bx,by,cx,cy;
int main()
{
cin>>t;
while(t--){
cin>>ax>>ay>>bx>>by>>cx>>cy;
int dx1=bx-ax,dx2=cx-ax;//从A点到B,C点横向改变量
int dy1=by-ay,dy2=cy-ay;
int dx=0,dy=0;
if(dx1>0&&dx2>0||dx1<0&&dx2<0) dx=min(abs(dx1),abs(dx2));
if(dy1>0&&dy2>0||dy1<0&&dy2<0) dy=min(abs(dy1),abs(dy2));
cout<<dx+dy+1<<endl;
}
return 0;
}
- Subsequence Addition (Easy Version)
题目大意:
本题为简单版,两题的唯一区别在于数据范围的大小。
数列 a 最开始只有一个数 1,你可以进行若干次操作,每次操作你可以选取 k 个数(k 无限制,小于等于 a 的大小即可),将这 k 个数的和放入 a 的任意一个位置。
给定一个长度为 n 的序列 c,问 a 能否在进行若干次操作后转为c。
分析:
简单模拟,开始总是1,1,2,3,…形成固定模式
将序列c从小到大进行排序,判断首位是否为1,不为1显然无法从a获得
初步检查后将bitset下标1置为1,其余置为0
从小到大枚举c中元素,判断其在bitset中的对应位是1,若不为1说明无法从a序列得到c
若值为1,需要将所有能够拼出的值加上这个数,此时可以拼出的值下标为1,其余为0
从小到大枚举c中元素,若当前枚举元素为c[i],将bitset与其自身左移c[i]位后的值进行按位或运算即可更新所有能够被被拼出的数
模拟过程:
假设数组 c 的值为 [1, 2, 3]:
初始时,位向量 bitset 为 00000000。
首先,枚举数组 c 的第一个元素 c[0] = 1。
将 bitset 左移 1 位后得到 00000000,与原来的 bitset 按位或运算,得到新的 bitset = 00000001。
接下来,枚举数组 c 的第二个元素 c[1] = 2。
将 bitset 左移 2 位后得到 00000010,与原来的 bitset 按位或运算,得到新的 bitset = 00000011。
最后,枚举数组 c 的第三个元素 c[2] = 3。
将 bitset 左移 3 位后得到 00001100,与原来的 bitset 按位或运算,得到新的 bitset = 00001111。
最终的位向量 bitset 为 00001111,表示我们能够拼出的数为 1, 2, 3。
知识补充:
转载知乎
std::bitset
是 C++ 标准库中的一个类,用于表示二进制位序列。它提供了一种方便的方式来处理二进制数据,尤其适用于位运算操作。
std::bitset
类型表示一个固定长度的位序列,每个位都只能是 0 或 1。这个固定长度在创建对象时指定,并且不能在运行时更改。类似于整数类型,std::bitset
支持多种操作,包括位运算、位查询和位设置。
下面是 std::bitset
类型的创建方式:
#include <bitset>
std::bitset<N> bitset1; // 创建一个长度为 N 的 bitset,所有位都被初始化为 0
std::bitset<N> bitset2(value); // 使用二进制整数 value 初始化一个长度为 N 的 bitset
std::bitset<N> bitset3(string); // 使用二进制字符串 string 初始化一个长度为 N 的 bitset
std::bitset<N> bitset4(bitset); // 使用另一个 bitset 初始化一个长度为 N 的 bitset
其中,value
是一个无符号整数,string
是一个只包含 '0'
和 '1'
的字符串,bitset
是另一个 std::bitset
对象。
下面是 std::bitset
类型的一些常用操作:
size()
返回std::bitset
的长度count()
返回std::bitset
中值为 1 的位的数量any()
返回std::bitset
中是否存在值为 1 的位none()
返回std::bitset
中是否所有位都是 0all()
返回std::bitset
中是否所有位都是 1test(pos)
返回std::bitset
中位于pos
位置的值set(pos)
将std::bitset
中位于pos
位置的值设为 1reset(pos)
将std::bitset
中位于pos
位置的值设为 0flip(pos)
将std::bitset
中位于pos
位置的值取反to_ulong()
返回std::bitset
转换成的无符号整数值to_ullong()
返回std::bitset
转换成的无符号长整数值
std::bitset
重载了许多二进制运算符,如 &
、|
、^
、~
等,使其支持类似于整数类型的位运算操作。例如:
std::bitset<4> bitset1("1010");
std::bitset<4> bitset2("0110");
std::bitset<4> bitset3 = bitset1 & bitset2; // 按位与运算
std::bitset<4> bitset4 = bitset1 | bitset2; // 按位或运算
std::bitset<4> bitset5 = bitset1 ^ bitset2; // 按位异或运算
std::bitset<4> bitset6 = ~bitset
还可以使用左移、右移运算符进行位移操作:
std::bitset<4> bitset1("0101");
std::bitset<4> bitset2 = bitset1 << 2; // 左移 2 位,结果为 "010100"
std::bitset<4> bitset3 = bitset1 >> 1; // 右移 1 位,结果为 "0010"
std::bitset
还支持 to_string()
方法,将其转换成二进制字符串表示:
std::bitset<4> bitset1("1010");
std::string str = bitset1.to_string(); // "1010"
std::bitset
可以作为容器类型使用,可以使用下标访问、迭代器等方式访问其元素。此外,它还可以通过位集合(bitset set operations)进行集合运算,如并集、交集、补集等,可以使用 std::bitset
的成员函数 set()
、reset()
、flip()
进行相应的集合操作。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int t,n;
bitset<N> s;
int c[N];
int main()
{
scanf("%d",&t);
while(t--){
s.reset();
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&c[i]);
sort(c+1,c+n+1);
s[1]=1;
if(c[1]!=1) cout<<"NO"<<endl;
else{
bool flag=true;
for(int i=2;i<=n;i++){
if(s[c[i]]) s|=s<<c[i];
else{
flag=false;
break;
}
}
if(flag) puts("YES");
else puts("NO");
}
}
return 0;
}
- Subsequence Addition (Hard Version)
数据范围
分析:
假设我们有一个通过一系列操作生成的数组a,其中元素的总和为s。我们想要证明,在数组a中添加任何介于1和s之间的数x(1≤x≤s),我们仍然可以使用改变后的数组a来生成任何介于1和s+x之间的数。
我们首先假设在长度为l的数组a中添加了数x(1≤x≤s)。根据题设中的操作规则,我们可以使用原始数组a的元素来生成任何介于1和s之间的数b。
现在我们考虑使用元素x和原始数组a的某个子集来生成目标数b’,其中b满足x≤b≤s+x。
如果目标数b’小于或等于s,我们可以使用原始数组a的元素来生成它,因为我们已经证明了原始数组a可以生成介于1和s之间的任何数。
如果目标数b’大于s,我们可以将其表达为b’=s+k,其中k>0。我们需要注意到,我们可以使用原始数组a的某个子集来生成一个数k,因为原始数组a的元素总和是s。
综上所述,我们可以通过原始数组a的元素和新添加的元素x来生成介于1和s+x之间的任何数。也就是说,对于长度为l+1的新数组,我们仍然可以生成介于1和s+x之间的任何数。
由于上述论证适用于初始数组a,我们可以使用归纳法以及这个结论来证明对于所有的数组都成立。因此,我们只需要验证数组是否满足这个条件。我们可以对数组进行排序,并对每个i(2≤i≤n)检查是否满足ci≤∑i−1j=1cj的条件。
#include <bits/stdc++.h>
using namespace std;
int t;
const int N=2e5+10;
int n;
int main()
{
cin>>t;
while(t--){
cin>>n;
vector<int> a(n);
for(int i=0;i<n;i++) cin>>a[i];
sort(a.begin(),a.end());
if(a[0]!=1) cout<<"NO"<<endl;
else{
bool flag=true;
long long sum=a[0];
for(int i=1;i<n;i++){
if(sum<a[i]){
flag=false;
break;
}
sum+=a[i];
}
if(flag) puts("YES");
else puts("NO");
}
}
return 0;
}
- Hamon Odyssey
题目大意:
满足&值最小的的情况下,尽可能多的分组
分析:
首先明白&值不增,全部化为一组,&值一定满足最小
根据样例出思路
如果某段出现了&值为0的情况,可以进行分组
最后考虑这样分组最后剩下的&值和全部化为一组的&值的大小情况,如果分组的&值大于一组的&值,将最后的一组归到&值为零的组中(A&0=0),在和一组的组数进行比较
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N];
int t;
int n,k;
int main()
{
cin>>t;
while(t--){
cin>>n;
int flag;
for(int i=0;i<n;i++){
cin>>a[i];
if(i==0) flag=a[i];
else flag&=a[i];
}
int res=0;
int t;
for(int i=0;i<n;i++){
t=a[i];
while(t!=0&&i<n){
t&=a[i];
if(!t) break;
i++;
}
res++;
}
// cout<<t<<' '<<flag<<endl;
if(t>flag) cout<<max(res-1,1)<<endl;
else cout<<res<<endl;
}
return 0;
}
/*
&不增
*/