Codeforces Round #781 (Div. 2)
A. GCD vs LCM
如果一个数是奇数,那么就输出(n/2,n/2+1,1,1)
如果一个数是偶数,那我们分两种情况考虑
如果这个数是4的倍数,那么就输出(2,n-6,2,2)
否则就输出((n-2)/2-1,(n/2)/2-1,1,1)
#include<cstdio>
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
if(n==4)printf("1 1 1 1\n");
else if(n%2==1){
int x=n-2;
printf("%d %d %d %d\n",x/2,x/2+1,1,1);
}else {
if((n/2)%2==1){
printf("%d %d %d %d\n",(n-2)/2-1,(n-2)/2+1,1,1);
}else {
printf("%d %d %d %d\n",2,n-6,2,2);
}
}
}
return 0;
}
B. Array Cloning Technique
题目大意:有一个数组,每次你可以选择这样两个操作中的一个进行操作
操作1:复制一个数组作为副本
操作2:在你复制出来的数组和原数组进行元素交换(一次只能交换一对)
问你至少多少次可以使原数组所有值都相等。
策略:先找到这个数组内出现次数最多的数X,然后你每次都复制原数组,然后把复制的数组中的X都交换到原数组中。
#include<cstdio>
#include<algorithm>
#define M 100005
using namespace std;
int A[M];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
sort(A+1,A+n+1);
int cnt=1;
int mx=1,ans=0;
for(int i=2;i<=n;i++){
if(A[i]!=A[i-1])cnt=1;
else cnt++;
mx=max(mx,cnt);
}
//printf("mx=%d\n",mx);
while(mx<n){
ans++;
ans+=min(mx,n-mx);
mx*=2;
}
printf("%d\n",ans);
}
return 0;
}
C. Tree Infection
题目大意:有一颗树,每天会发生这样两件事
1.对于任意节点,如果这个节点的一个子节点已经被感染,那么你可以选择一个他的另外一个子节点
2.选择一个没有被感染的点,感染他
策略:考虑到一个点只会影响到他的兄弟,那么我们可以统计每个点他的子节点有多少个(也就是每个节点按照他们的父亲分类成若干集合),得到一个数组(注意根结点也要考虑到),那么我们每次的操作相当于在投下病毒种子且感染一个人。
贪心的考虑我们优先把每个集合都感染一次,在这个过程中按照集合的大小从大到小感染,然后每个集合都被感染后每次取出当前未感染人数最大的集合进行操作2.这里我们只要存下每个集合上次访问的时间tim和上次访问过后剩余的人数d,存入优先队列中按照tim+d排序即可。
#include<cstdio>
#define M 1000005
int main(){
int T;
scanf("%d",&T);
while(T--){
int d;
int res=0;
for(int i=1;i<=30;i++){
int I=1<<(i-1);
int add=I-res;
printf("? %d %d\n",(1<<(i))+add,add);
fflush(stdout);
scanf("%d",&d);
if(d%((1<<(i)))==0)res|=1<<(i-1);
}
printf("! %d\n",res);
}
return 0;
}
D. GCD Guess
有一个x,你可以每次询问一个(a,b),他会告诉你gcd(x+a,x+b),你要在30次询问中确定x。
看到30,再看到x的范围1e9,考虑到按位去求解x。
假设我们已经求解了x的前i-1位,我们要求解x的第i位。假设x为4(第1位开始)
假设这个数x的二进制表示为1010101(下面这些数也都是2进制表示),我们已经知道了这个数的前三位为101,于是我们可以先取a为11(二进制,1000-101=11),x+11=1011000,然后我们只用考虑x+11是不是10000( 2 4 2^4 24)的倍数即可,我们考虑gcd(x+11,x+11+10000),如果这个值为10000的倍数,那么x+11第4位就是0,那么x第4位就是1。
#include<cstdio>
#define M 1000005
int main(){
int T;
scanf("%d",&T);
while(T--){
int d;
int res=0;
for(int i=1;i<=30;i++){
int I=1<<(i-1);
int add=I-res;
printf("? %d %d\n",(1<<(i))+add,add);
fflush(stdout);
scanf("%d",&d);
if(d%((1<<(i)))==0)res|=1<<(i-1);
}
printf("! %d\n",res);
}
return 0;
}
E. MinimizOR
给你一个序列,每次询问[L,R]区间中的 m i n ( a i ∣ a j ) ( i ≠ j ) min(a_{i}|a_{j})(i\neq j) min(ai∣aj)(i=j)
首先我们把询问离线,然后将询问按照左端点从大到小排序(个人喜好)。然后维护这样一个类似于字典树的数据结构,先将每个数传化为二进制形式,树上的节点x存的信息就是x到根这串节点所代表的前缀所对应的数中编号最小的两个数。
假设查询区间为[L,R],此时这颗树维护的数是在[L,n]范围内的,我们在1节点。
如果2节点对应的前缀中编号第二小的数都小于等于R,那么我们选择2节点向下遍历肯定更优。
如果3节点对应的编号第二小的数小于等于R,就说明3节点存在区间内的数的数量大于等于两个,这个时候如果2节点内没有数在查询区间里了,那我们就可以安心的去3节点查询,但如果此时2节点还剩下了一个数,我们可以考虑把这个数或上节点1决策的那一位的1,然后丢到3节点里(就和之前加入一个新的数一样,只不过只在3节点对应的这一位进行这样的操作)实现上述操作我的做法可能有点复杂,我是记录下哪些节点被上述这种操作更新了信息,在询问结束后将信息还原。(然后这里我是拿了一个set存这种类型的数,然后判断这个数如果已经不合法了就是我们所走的路径所组成的前缀里有一些0的位置他有1)
剩余的情况就是最后只剩下了两个数,一个数在2节点,一个数在3节点,那么我们就可以得到答案就是这两个数或起来的答案在或上我们之前的路径所对应的前缀。(因为剩下的数可能是我们修改过后的数,所以我们要或上之前的路径所对应的前缀)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
#include<stack>
#define M 100005
using namespace std;
struct Que{
int L,R,id;
bool operator <(const Que &_)const{
return L>_.L;
}
}Q[M];
int A[M],Ans[M];
struct Tree{
int son[M*31][2];
int val[M*31][2],Cp[M*31][4];
int tot_id;
stack<int>stk;
set<int>tmp;
set<int>::iterator it1,it2;
bool in_stk[M*31];
void Clear(){
memset(in_stk,0,sizeof(in_stk));
}
void Init(){
son[0][0]=son[0][1]=0;
val[0][0]=val[0][1]=1e9;
tot_id=0;
}
void Newnode(int &now){
now=++tot_id;
son[now][0]=son[now][1]=0;
val[now][0]=val[now][1]=1e9;
}
void Updata(int now,int d){
if(d<val[now][0]){
val[now][1]=val[now][0];
val[now][0]=d;
}else if(d<val[now][1])val[now][1]=d;
}
void Push(int x){//备份一下
if(in_stk[x])return;
in_stk[x]=true;
Cp[x][0]=val[x][0];
Cp[x][1]=val[x][1];
Cp[x][2]=son[x][0];
Cp[x][3]=son[x][1];
stk.push(x);
}
void Back(){
while(!stk.empty()){
int x=stk.top();stk.pop();
in_stk[x]=false;
val[x][0]=Cp[x][0];
val[x][1]=Cp[x][1];
son[x][0]=Cp[x][2];
son[x][1]=Cp[x][3];
}
}
void Add(int x,int d){
int now=0;
for(int i=30;i>=1;i--){//跟字典树一样的东西
int op=(bool)((1<<(i-1))&d);
if(!son[now][op])Newnode(son[now][op]);
now=son[now][op];
Updata(now,x);
}
}
void Add_tmp(int now,int i){
for(it1=tmp.begin();it1!=tmp.end();it1++){
int op=(bool)((1<<(i-1))&A[*it1]);
if(!son[now][op])Newnode(son[now][op]);//就和新加节点一样的操作
Updata(son[now][op],*it1);
}
}
void Check(int res,int II){
for(it1=tmp.begin();it1!=tmp.end();it1=it2){
it2=it1;it2++;
if((A[*it1]&II)-(A[*it1]&res)!=0){//如果这个数前缀有了路径里没有的1
tmp.erase(it1);
}
}
}
void Print(){
printf("tot_id=%d\n",tot_id);
for(int i=1;i<=tot_id;i++)printf("%d %d %d %d ",val[i][0],val[i][1],son[i][0],son[i][1]);
puts("");
}
int Query(int R){
while(!stk.empty())stk.pop();
int now=0;
int tmp_tot_id=tot_id;
int res=0,II=0;//res记录路径 II全1路径
tmp.clear();
for(int i=30;i>=1;i--){
Check(res,II);
II|=1<<(i-1);
int lst=now;
int Lson=son[now][0],Rson=son[now][1];
if(Lson)Push(Lson);
if(Rson)Push(Rson);
if(now)Push(now);
Add_tmp(lst,i);
Lson=son[now][0],Rson=son[now][1];
if(val[Lson][1]<=R){now=Lson;}//向左走
else if(val[Rson][1]<=R){//向右走
res|=1<<(i-1);
if(val[Lson][0]<=R)tmp.insert(val[Lson][0]);
now=Rson;
}
else {
int ans=A[val[Lson][0]]|A[val[Rson][0]]|res;
tot_id=tmp_tot_id;
Back();
return ans;
}
}
//俩数一样的情况
Back();
tot_id=tmp_tot_id;
return A[val[now][0]]|res;
}
}TT;
int main(){
int T;
scanf("%d",&T);
TT.Clear();
while(T--){
TT.Init();
int n,q;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
scanf("%d",&q);
for(int i=1;i<=q;i++){Q[i].id=i;scanf("%d%d",&Q[i].L,&Q[i].R);}
sort(Q+1,Q+q+1);
for(int i=1;i<=q;i++){
if(i==1)for(int j=n;j>=Q[i].L;j--)TT.Add(j,A[j]);
else for(int j=Q[i-1].L-1;j>=Q[i].L;j--)TT.Add(j,A[j]);
Ans[Q[i].id]=TT.Query(Q[i].R);
}
for(int i=1;i<=q;i++)printf("%d\n",Ans[i]);
}
return 0;
}