P8453 美元巨大
前言
蒟蒻是参考这位大佬的思路发表的题解,这么好的思路却没有出现在题解里着实比较可惜了。
题意
关于这道题目,关键就是 \(\bigoplus\) 和 \(|\) 的安排,如果您现在还搞不清楚位运算的含义的话,可以看下面的
或(\(|\)) 两个对应位中有一个为 \(1\) 时为 \(1\)。
异或(\(\bigoplus\)) 两个对应位不同时为 \(1\)。
——OI-wiki
同时或和异或满足: \(a|b=b|a,a\bigoplus b=b\bigoplus a\)。
这样的话我们就可以利用交换律将相同的位数放到一起,使用 std::sort 排序,同时记录每一个数一开始的位置。
同时,两个相同的数进行异或将会得到 \(0\),而两个相同的数进行或运算的话则会不变,而题目中给出的是得到的数最大,所以我们要尽可能让高位保持为 \(1\),这样我们的目标就有了。
思路
从上方的异或和或概念的理解,我们可以轻而易举的知道一下观点,如果一个数出现奇数次那无论再怎么异或或者或都无法将他从 \(1\) 变成 \(0\) (这里非常关键),所以我们在一开始可以对所有数从大到小进行排序,然后以对所有数进行预处理,就是如果碰到出现次数为偶数个的数就在他最后一次出现的时候给他所处的位置标为或运算,这样就可以将他的出现次数变为奇数(即保证他的位数为 \(1\)),这样我们在进行便利,即碰到奇数次我们就利用刚刚得出的定理(即出现奇数次的位数,无论怎么异或或者或都没有办法使他从 \(1\) 变为 \(0\)),所以我们可以里用这个性质,利用奇数次出现的数尽可能多的消掉异或的个数,最后再以倒序的方法输出即可,记得要去除前导零。
稍微总结一下:
1.初始化。
2.在输入的同时,利用结构体记录每个数原来的位置,同时利用桶来记录每个数出现的次数。
3.预处理数组,尽可能多的将每个位数出现的次数变为奇数次。
4.遍历一整个数组(记得要注意剩余异或和或的个数,以及预处理过的数不可以进行二次修改,详细见代码)。
5.最后对于每个数字进行输出。
遵循一个原则:尽量将高位出现的次数变为奇数次。
代码
#include<bitsdc++.h>
using namespace std;
int s,m,x,y,n;
struct node{
int val,id;//结构体,val表示位数,id表示每一个位数一开始的位置
}a[25005];
int cnt[70005],ans1[70005],ans2[70005];//cnt储存每一个位数出现的次数,ans1表示第一行输出,ans2则是第二行
bool cmp(node x,node y){
if(x.val!=y.val) return x.val>y.val;
else return x.id<y.id;
}
int main(){
while(cin>>s){
cin>>m;
while(m--){
memset(cnt,0,sizeof(cnt));
memset(ans1,0,sizeof(ans1));
memset(ans2,0,sizeof(ans2));//初始化,很重要不然会报错
cin>>n>>x>>y;
for(int i=1;i<=n;i++){
cin>>a[i].val;
a[i].id=i;
cnt[a[i].val]++;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){/*预处理*/
if(cnt[a[i].val]%2==0 && y>=1){//如果出现个数为偶数,同时或的个数足够
i+=cnt[a[i].val];//使得i指针可以跳到下一个数字
i--,y--,cnt[a[i].val]--;
ans2[a[i].id]=1;//将这个位数最后一次出现的位置表为或
//填1表示或,2表示异或,0表示没填(所以通过ans2也可以判断这个位置有没有被用过)
}
}
for(int i=1;i<=n;i++){
if(cnt[a[i].val]%2==1){
ans1[a[i].val]=1;//位数肯定是1,这是可以保证的
if(ans2[a[i].id]==0 && a[i].id!=1){//这里要特判他不是第一位,因为我们只需要填n-1个数
if(x>0){//如果异或还有就将他抵消
ans2[a[i].id]=2;
x--;
}
else{
ans2[a[i].id]=1;
y--;
}
}
}
else{
if(ans2[a[i].id]==0 && a[i].id!=1){
ans1[a[i].val]=0;//如果为偶数次,因为他没有被一开始的预处理处理过,所以一定是或的数量不够了,所以只能填异或
ans2[a[i].id]=2;
x--;
}
}
}
bool flag=1;//flag用来去除前导零
for(int i=a[1].val;i>=0;--i){//一定记得倒叙输出
if(flag){
if(ans1[i]==0 && i>0){
continue;
}
else flag=0;
}
cout<<ans1[i];
}
cout<<endl;
for(int i=2;i<=n;i++){
if(ans2[i]==1) cout<<"|";
else cout<<"^";
}
cout<<endl;
}
}
return 0;
}

浙公网安备 33010602011771号