A. GCD vs LCM
题意:给定一个正整数n,你需要找到四个正整数a,b,c,d,使得 a+b+c+d=n并且gcd(a,b)=lcm(c,d)
思路:一个小思维题,一开始看样例在那推每一位的公因子傻掉了,然后发现不就是只要a=n-3,bcd都为1不就满足条件了。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin>>t;
while(t--){
int n;
cin>>n;
cout<<n-3<<" "<<1<<" "<<1<<" "<<1<<endl;
}
return 0;
}
B. Array Cloning Technique
题意:给定一个n个整数的数组a,可以进行两个操作,操作1:选择其中一个数组复制他,操作二:将拥有的任意两个数组的元素进行交换。要求得到最后只有一个元素的数组的最小操作次数
思路:贪心即可,只需要找到数组中元素出现次数最多的那一个,对他进行复制后再进行操作二,数组中的该元素个数就会乘2,然后继续对新得到的数组进行重复操作,知道最后数组都为同一个元素即可
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
map<ll,ll> mp;
int main(){
//ios::sync_with_stdio(false);
//cin.tie(0);cout.tie(0);
ll t;
cin>>t;
while(t--){
mp.clear();
ll n,ma=-1;
cin>>n;
for(int i=1;i<=n;i++){
ll a;
cin>>a;
mp[a]++;
ma=max(ma,mp[a]);
}
ll need=n-ma;
if(need==0){
cout<<0<<endl;
continue;
}
ll now=ma,cnt=1,tot=ma;
//cout<<now<<" "<<need;
while(need>tot){
now=now*2;
tot+=now;
cnt++;
}
cout<<cnt+need<<endl;
}
}
C-Tree Infection
题意:给定一棵树,你能进行两种操作,注射:可以任意选一个节点将其感染,传递:将所有有兄弟节点的已被感染的节点的兄弟感染,单次操作可以同时进行两次操作。每一串兄弟节点每次只能感染一个。求最小操作次数。
思路:贪心,首先要先把所有有儿子的节点都感染一遍,这样会使得他们在后面进行传递操作时每次传递的更多。注意对于1这个节点我们也要进行依次注射,接着,我们
可以处理出所有有儿子的节点并将每一串儿子合并整理成一个数组,我们应该优先注射那些儿子最多的数,这一才能使得传递时感染的最多,于是只要将数组排序后,从最大那一项开始往后减cnt-i,开辟先儿子串的任务就完成了。接着,我们就应该考虑应该选择哪一个进行注射了,很容易发现,我们每次都是选择剩余儿子串中最大的那一项进行注射,每次操作时将最大的那一项减2(即边进行传递边进行注射),其他儿子串减1,于是我们可以用一个优先队列得到每次最大的那一个儿子串。
代码:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N=5e5 + 5e3;
int a[N],son[N];
bool cmp(int a,int b){
return a>b;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--){
int n,cnt=0,ans=0;
cin>>n;
for(int i=0;i<=n+200;i++)son[i]=0;
for(int i=1;i<n;i++)cin>>a[i],son[a[i]]++;
for(int i=1;i<=n;i++)if(son[i])cnt++;
ans+=cnt+1;
sort(son+1,son+1+n,cmp);
for(int i=1;i<=cnt;i++){
son[i]-=cnt+2-i;
}//贪心先将大的注射
int lazy=0;
//进行循环模拟在剩余最大的一项中边进行传播边进行注射的操作
priority_queue<int> que;
for(int i=1;i<=cnt;i++)que.push(son[i]);
while(que.top()>lazy){
int tmp=que.top()-1;
que.pop();
lazy++;
que.push(tmp);
}
ans+=lazy;
cout<<ans<<endl;
}
}
D. GCD Guess
题意:猜测一个数字x,我们可以进行最多30次询问来得到该数字是多少,每次可以询问gcd(x+a,x+b)的值(交互题)
思路:首先对于1<=x<=1e9,且询问次数最多为次,那我们可以知道一定是按位进行询问;
另外我们还需要知道辗转相除法gcd(x+a,x+b)=gcd(x+b,(x+a)%(x+b)),这里我们假设a>b.
则=gcd(x+b,x+a-(x+b))=gcd(x+b,a-b);
假设我们已经知道x的前i-1位的数字是多少,这里我们设为last,我们需要求当前位数i的数字是多少;
则x-last有一个很好的性质即他前i-1为数字都为0了;
那x-last+2^i这个数有什么性质呢?
我们发现如果x的第i位为1的话,加上2^i,这一位就进位了,则当前最低位为1的地方是第i+1位
如果x的第i位为0的话,加上2^i,这一位就是1,当前最低位为1的地方是第i位。
这样子,我们就可以将两种情况分出来了,将x-last+2^i与2^(i+1)取gcd,如果这一位是2^(i+1),则这一位一定是1了
a我们已经知道为2^i-last了;
那b怎么知道呢,我们只能知道 2^(i+1)=x+b,但是这求不出b呀,那怎么办呢,这就要用到上面的辗转相除法的结论了,此时相当于2^(i+1)=a-b;
这样我们就知道b啦;
b=a-2^(i+1);
但是我们仔细一看又会发现错误,我们假设的前提可是a>b,但是这里却不满足条件欸;
那如果是b>a的话呢?
2^(i+1)=b-a;
b=2^(i+1)+a;
这样就满足条件啦;
所以我们只需要询问三十次即可得到x的每一位了;
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
int n;
cin>>n;
while(n--){
ll ans=0;
ll last=0;
for(int i=0;i<30;i++){
ll a=(1<<i)-ans;
ll b=(1<<(i+1))+a;
cout<<"? "<<a<<" "<<b<<endl;
ll x;
cin>>x;
if(x==(1<<(i+1)))ans+=(1<<i);
}
cout<<"! "<<ans<<endl;
}
}