最近的CF几乎都没打,感觉挺水的一个题,不过自己仿佛状态不在,看题解才知道做法。
输入l, r, k (1 ≤ l ≤ r ≤ 1012; 1 ≤ k ≤ min(106, r - l + 1)).
从[l,r]选至多k个数使得选出的数的异或值最小,输出最小异或值和方案。
分类讨论,首先如果r-l+1<=4,枚举集合解决之。
先面讨论r-l+1>=5的情况:
此时有至少5个数可以选择,故至少有连续的4个数满足2x,2x+1,2x+2,2x+3。
k==1时显然方案为{l}。k==2时,显然方案为{2x,2x+1}。k>=4时,显然方案为{2x,2x+1,2x+2,2x+3}。
k==3时再另外考虑:
首先,异或值至多为1(参考k==2)
我们现在来找异或值可否为0。先假设可以,则显然是选3个数。不妨设x>y>z。
111...1111
111...1110
000...0001
显然x,y,z前半部分必定是如上这样的,但由于我们要使得x,y,z尽量靠近,所以x,y,z前半部分必然是如下
11
10
01
之后,每添加一位,有可能是yi=zi=1,xi=0或xi=zi=1,yi=0或xi=yi=1,zi=0。
由于要x,y,z尽量靠近,所以显然采取yi=zi=1,zi=0。
所以x,y,z的二进制形式如下
110...0
101...1
011...1
至此,问题大致解决,剩下的就是些细节问题,问题不大。
#include <cstdio> #include <cstring> #include <iostream> #include <vector> using namespace std; #define ll long long int cnt(int i){ int ret=0; while(i) i-=i&(-i), ++ret; return ret; } int main(){ ll l,r; int k; while(~scanf("%I64d%I64d%d",&l,&r,&k)){ if(r-l+1<5){ int n=r-l+1; ll ansxor=1ll<<60; vector<ll>val; for(int i=1;i<(1<<n);++i){ ll xx=0; for(int j=0;j<n;++j) if(i&(1<<j)) xx^=l+j; if(xx<ansxor && cnt(i)<=k){ ansxor=xx; val.clear(); for(int j=0;j<n;++j) if(i&(1<<j)) val.push_back(l+j); } } printf("%I64d ",ansxor); printf("%d ",val.size()); for(int i=0;i<val.size();++i) printf("%I64d%c",val[i],i==val.size()-1?' ':' '); } else if(r-l+1>=5){ if(k==1){printf("%I64d 1 %I64d ",l,l);continue;} if(k==2){ if(l&1) l++; puts("1"); puts("2"); printf("%I64d %I64d ",l,l+1); } else if(k>=4){ if(l&1) l++; puts("0"); puts("4"); printf("%I64d %I64d %I64d %I64d ",l,l+1,l+2,l+3); } else if(k==3){ ll x=-1,y,z; for(ll i=3;i<=r;i=i<<1){ if((i^(i-1))>=l){ x=i; y=i - 1; z=i^(i-1); break; } } if(x!=-1){ puts("0"); puts("3"); printf("%I64d %I64d %I64d ",x,y,z); } else { if(l&1) l++; puts("1"); puts("2"); printf("%I64d %I64d ",l,l+1); } } } } return 0; }