Codeforces Round #781 (Div. 2)
A
\(\circlearrowright\) 给出 \(n\),要求构造 \(4\) 个整数 \(a,b,c,d\),满足:\(a+b+c+d=n\) 且 \(\gcd(a,b)=\operatorname{lcm}(c,d)\)。
我教你构造一个 \(n-3,1,1,1\)。
My Code
void solve(){
int n;cin>>n;
cout<<n-3<<' '<<1<<' '<<1<<' '<<1<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;for(cin>>T;T--;) solve();
return 0;
}
B
\(\circlearrowright\) 给你一个序列,你可以复制任何一个序列或者交换任何两个序列中的任何两个元素。求最少操作次数使得某一个序列全部相等。
容易想到让序列中的数都等于出现次数最多的那个数。然后你发现你每次可以倍增它的数量,所以就是你需要有 \(\log_2\dfrac{n}{mx}\) 次用于复制序列,剩下的就是交换。细节上用倍增不要直接算。
My Code
const int MAXN=1e5+10;
map<int,int> mp;
void solve(){
int n,a,mx=0;cin>>n;
mp.clear();
rep(i,1,n){
cin>>a;mp[a]++;
mx=max(mx,mp[a]);
}int cur=0;
while(mx*(1ll<<cur)<n)
cur++;
cout<<n-mx+cur<<'\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;for(cin>>T;T--;) solve();
return 0;
}
C
\(\circlearrowright\) 给你一棵树,节点初始都是白色,每秒可以选择一个节点把它染黑。然后每秒对于一个节点 \(u\),如果它至少一个儿子是黑色的,那么它会自发地把剩下的白色儿子染黑。求把整棵树都染黑的最短时间是多少。
把节点按父亲分类,容易想到一个贪心,先染最多的,然后让它自己扩散,最后如果每一组都染过了,那就回来补刀。具体做就是排序从大到小,然后先每一组染一个,算出都染过之后每一组剩下有几个白点。然后你枚举一下用几秒可以把剩下的白点都染黑,用前缀和判一下就可以了。
My Code
const int MAXN=2e5+10;
int son[MAXN],cnt[MAXN];
void solve(){
int n;
cin>>n;
rep(i,0,n) son[i]=cnt[i]=0;
son[0]=1;
rep(i,2,n){
int p;cin>>p;
son[p]++;
}
sort(son,son+1+n,[&](int x,int y){return x>y;});
int stp=0;
rep(i,0,n)
if(son[i]==0){
stp=i-1;break;
}
rep(i,0,stp) son[i]-=min(son[i],stp+1-i);
int sum=0;
rep(i,0,stp) sum+=son[i];
if(!sum){
cout<<stp+1<<'\n';
return;
}
sort(son,son+1+n,[&](int x,int y){return x>y;});
int brk=0;
rep(i,0,n)
if(son[i]==0){
brk=i;break;
}
int lst=1;
per(i,brk-1,0){
rep(j,lst,son[i]){
cnt[j]=cnt[j-1]+i+1;
}lst=son[i]+1;
}
rep(i,0,son[0]){
if(sum-cnt[i]<=i){
cout<<i+stp+1<<'\n';
return;
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;for(cin>>T;T--;) solve();
return 0;
}
D
\(\circlearrowright\) 交互猜数,每次询问 \(\gcd(x+a,x+b)\),最多询问 \(30\) 次。
看到 \(30\),看到 \(2\times 10^9\) 就不难想到二进制。首先每次询问相当于是说,你可以知道 \(\gcd(x+b,a-b)\)(辗转相减),然后考虑从低到高逐位确定。我们考虑令 \(b\) 加到 \(x\) 上之后,恰好在当前考虑的第 \(i\) 位产生一个进位。然后如果第 \(i\) 位本来是 \(1\),那么接下来询问 \(x+b\) 和 \(2^{i+1}\) 的 \(\gcd\) 就是 \(2^{i+1}\),否则就是 \(2^i\)。然后就这样做 \(30\) 次就可以全部猜出来了。
My Code
int qry(int a,int b){
cout<<"? "<<a<<' '<<b<<endl;
int g;cin>>g;return g;
}
void solve(){
int x=0;
rep(i,1,30){
int b=(1ll<<(i-1))-x,v=(1ll<<i);
int g=qry(v+b,b);
if(g==v) x+=(1ll<<(i-1));
}cout<<"! "<<x<<endl;
}
signed main()
{
int T;for(cin>>T;T--;) solve();
return 0;
}
E
\(\circlearrowright\) 给你一个序列 \(a\),每次询问给出一个 \(l,r\),要求区间内按位或起来最小的一对数。求他们的或和。
这题的做法是,找区间内最小的 \(31\) 个数,然后暴力求这 \(31\) 个数两两或起来的最小值。我们可以归纳证明这个结论。结论:对于小于 \(2^k\) 的数,我们在 \(k+1\) 个最小的数中可以找出两个数或起来最小就是整段的最小。
首先,当 \(k=1\) 时,显然有 \(a_i\in {0,1}\),你掏出最小的两个数显然就是最小的。
接下来,我们考虑,如果 \(k\) 是成立的,那么 \(k+1\) 是否成立。假如说,所有数二进制的第 \(k+1\) 位都是 \(1\),那么最终或起来,答案的第 \(k+1\) 位肯定是 \(1\),那么我们只要管接下来的 \(k\) 位就可以了,也就是只需要 \(k+1\) 个数(而我们当前论证中可选 \(k+2\) 个数,肯定是够的)。如果有一个数不是 \(1\),那么我们考虑第 \(k+1\) 位一定还是 \(1\),但是我们选最小的数的时候一定会选择那个第 \(k\) 位不是 \(1\) 的,但它不一定最优,所以我们会需要多选一个,也就是 \(k+2\) 个。如果有多于两个数不是 \(1\),那么答案第 \(k+1\) 位肯定是 \(0\),于是我们直接选最小的 \(k+1\) 个数,肯定包含了答案。
My Code
const int MAXN=1e5+10;
struct info{
vector<int> mn;
void clear(){mn.clear();}
info friend operator+(info a,info b){
info ret;
int at=0,bt=0;
while(ret.mn.size()<31&&at<a.mn.size()&&bt<b.mn.size())
if(a.mn[at]<b.mn[bt]) ret.mn.pb(a.mn[at++]);
else ret.mn.pb(b.mn[bt++]);
while(ret.mn.size()<31&&at<a.mn.size()) ret.mn.pb(a.mn[at++]);
while(ret.mn.size()<31&&bt<b.mn.size()) ret.mn.pb(b.mn[bt++]);
return ret;
}void push(int x){mn.clear();mn.pb(x);}
};
struct Tree{int l,r;info mn;}tr[MAXN<<2];
#define ls i<<1
#define rs i<<1|1
int a[MAXN];
void pushup(int i){tr[i].mn=tr[ls].mn+tr[rs].mn;}
void build(int i,int l,int r){
tr[i].l=l;tr[i].r=r;if(l==r){tr[i].mn.push(a[l]);return;}
int mid=(l+r)>>1;build(ls,l,mid);build(rs,mid+1,r);pushup(i);
}info ask(int i,int l,int r){
if(tr[i].l==l&&tr[i].r==r) return tr[i].mn;
int mid=(tr[i].l+tr[i].r)>>1;
if(r<=mid) return ask(ls,l,r);
else if(l>mid) return ask(rs,l,r);
else return ask(ls,l,mid)+ask(rs,mid+1,r);
}
void solve(){
int n;cin>>n;
rep(i,1,n) cin>>a[i];
build(1,1,n);
int Q,l,r;cin>>Q;
while(Q--){
cin>>l>>r;
vector<int> cur=ask(1,l,r).mn;
int ans=INF;
rep(i,0,(int)cur.size()-1)
rep(j,i+1,(int)cur.size()-1)
ans=min(ans,cur[i]|cur[j]);
cout<<ans<<'\n';
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;for(cin>>T;T--;) solve();
return 0;
}