CF1665

CF1665

GCD vs LCM

简单构造 我们让 \(gcd(a,b)=1\)\(lcm(a,b)=1\) 即可 所以 \(a=c=d=0\)\(b=n-3\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long 
const int N = 3e5 + 5;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n;


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int T = read();
	while ( T -- )
	{
		n = read();
		cout << "1 " << n - 3 << " 1 1" << endl;
	}
	return 0;
}

Array Cloning Technique

可能是不太一样的做法(?)

我们稍微手推一下可以发现 一定要使得原数组中出现次数最多的一个数成为最终数组中的数(可以用 \(map\) 记录)

那么最优策略是先复制出来一组数 再将所有数合并到一起 如果还是不能覆盖全部数组 那么我们将这个合并之后的数组再复制出来一份继续进行上述操作即可

考虑模拟 我们设最多出现数的次数为 \(cnt\) 如果我们复制一次 我们可以额外弄出来的数为 \(cnt\) 复制两次为 \(cnt+cnt*2\) 以此类推

\(stp\) 为我们复制了几次 \(res\) 为能额外凑出来的数(如果这个数大于等于 \(n-cnt\) 说明已经合法了)

最后将复制次数和移动次数( \(n-cnt\) ) 累加即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long 
const int N = 3e5 + 5;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , a[N] , cnt;
map<int,int> mp;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int T = read();
	while ( T -- )
	{
		n = read();
		mp.clear() , cnt = 0;
		for ( int i = 1 ; i <= n ; i ++ ) mp[a[i]=read()] ++;
		for ( int i = 1 ; i <= n ; i ++ ) cnt = max ( mp[a[i]] , cnt );
		int res = 0 , stp = 0 , temp = cnt;
		while ( res < n - cnt )
		{
			++ stp;
			res += temp;
			temp <<= 1;
		}
		cout << stp + n - cnt << endl;
	}
	return 0;
}

Tree Infection

因为题目中说一个点只能传染给它的兄弟节点 也就是说树上每一层节点都是独立的

考虑我们第一轮感染一定是从最多的节点向最少的节点来感染 (所有儿子数量为 \(0\) 的节点不用管 因为这些节点本身是归它父亲管的 对于根节点 \(1\) 我们钦定 \(0\) 节点为它的父亲 而且儿子数量为 \(1\) )

那么考虑第二轮感染 我们显然不能一直感染最大的那个节点 例如有一个 \(1000\) 节点和一个 \(999\) 节点 我们一定要兼顾而不是只感染 \(1000\) 节点

所以我们开一个堆记录剩余的健康节点个数的最大值 每次拎出来这个最大值并注射这个节点即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long 
const int N = 2e5 + 5;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , cnt[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int T = read();
	while ( T -- )
	{
		n = read();
		fill ( cnt + 1 , cnt + n + 1 , 0 );
		for ( int i = 1 ; i < n ; i ++ ) cnt[read()] ++;
		cnt[0] = 1;
		sort ( cnt , cnt + n + 1 );
		for ( int i = 0 ; i <= n ; i ++ )
			if ( cnt[i] != 0 )
			{
				priority_queue<int> q;
				for ( int j = i ; j <= n ; j ++ ) q.push ( cnt[j] - ( j - i + 1 ) );
				int spread = 0 , ans = n - i + 1;
				while ( q.top() > spread )
				{	
					int x = q.top(); q.pop();
					q.push ( x - 1 ) , ++ spread , ++ ans;
				}
				cout << ans << endl;
				break;
			}
	}
	return 0;
}

GCD Guess

根据更相减损术 可以得到 \(gcd(x+a,x+b)=gcd(x+a,b-a)\)

因为最多询问 \(30\) 次 所以可以想到每次询问目标数的一个二进制位

我们从低位向高位猜 假设我们现在猜的是从右到左第 \(i\) 位(也就是 \(x\&1<<i-1\) 的值) 那么我们现在显然已经知道了右面 \(i-1\) 位 这些位数值的和记为 \(y\)

那么我们设 \(a=2^{i-1}-y\)\(b=2^i+2^{i-1}-y\) 那么我们相当于是询问 \(gcd(x+2^{i-1}-y,2^i)\) 也就是 如果 \(x\) 这一位加上 \(1<<i-1\) 有进位 那么显然这位为 \(1\) 否则为 \(0\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define mid (l+(r-l>>1))
#define pii pair<int,int>
#define lson ls,l,mid
#define rson rs,mid+1,r
#define fi first
#define se second
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int inf = INT_MAX;
const int N = 2e5 + 5;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , a[N] , q , s[N];

int query ( int x , int y )
{
	cout << "? " << x << ' ' << y << endl << flush;
	return read();
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int TT = read();
	while ( TT -- )
	{
		int x = 0;
		for ( int i = 1 ; i <= 30 ; i ++ )
		{
			int res = query ( ( 1 << i - 1 ) - x , ( 1 << i ) + ( 1 << i - 1 ) - x );
			if ( res & ( 1 << i ) ) x |= 1 << i - 1;
		}
		cout << "! " << x << endl << flush;
	}
	return 0;
}

MinimizOR

一个结论:区间内的最小代价为区间内的前 \(31\) 小数中的某两个

证明:采用数学归纳法 原命题等价于小于 \(2^k\) 的数中的或运算最小值出现在前 \(k+1\) 小数中的两个数之间

也就是说如果区间中每一个数都小于 \(2^k\) 那么每次询问只取区间前 \(k+1\) 小值 两两或起来一定是正确答案

对于 \(k=1\)\(2^1=2\) 则取前两小的数就是答案

那么对于 \(k>1\)

  1. 如果序列中最高位的数中 \(\ge2\)\(0\) 高位贪心 那么或起来 当前位一定选 \(0\) 最小值一定出现在 \(k-1\) 位的最小值 那么根据 \(k-1\) 的结论 一定出现在前 \(k\) 小中

  2. 如果有 \(1\)\(0\) 那么或起来当前位置一定为 \(1\) 那么最小值一定是在前 \(k\) 个最高位为 \(1\) 的最小值 和那一个最高位为 \(0\) 的值之间选取

    那么一定在前 \(k+1\) 中选取

  3. 如果没有 \(0\) 那么只能选 \(1\) 类似第一种情况 结果一定是 \(k-1\) 位中的最小值 那么根据 \(k-1\) 的结论 一定在前 \(k\) 小中

用线段树暴力提取区间前 \(31\) 小值并进行组合取 \(min\) 即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define mid (l+(r-l>>1))
#define pii pair<int,int>
#define lson ls,l,mid
#define rson rs,mid+1,r
#define fi first
#define se second
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int inf = INT_MAX;
const int N = 2e5 + 5;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , a[N] , q , s[N];

struct Tree
{
	pii t[N<<2];
	inl void up ( int p ) { t[p] = min ( t[ls] , t[rs] ); }
	void build ( int p , int l , int r )
	{
		if ( l == r ) return t[p] = { a[l] , l } , void();
		build ( lson ) , build ( rson ) , up(p);
	}
	void upd ( int p , int l , int r , int x , int val )
	{
		if ( l == r ) return t[p] = { val , l } , void();
		if ( x <= mid ) upd ( lson , x , val );
		else upd ( rson , x , val );
		up(p);
	}
	pii query ( int p , int l , int r , int x , int y )
	{
		if ( x <= l && r <= y ) return t[p];
		pii res = { inf , inf };
		if ( x <= mid ) res = min ( res , query ( lson , x , y ) );
		if ( mid + 1 <= y ) res = min ( res , query ( rson , x , y ) );
		return res;
	}
}T;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int TT = read();
	while ( TT -- )
	{
		n = read();
		for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
		T.build ( 1 , 1 , n );
		q = read();
		for ( int i = 1 ; i <= q ; i ++ )
		{
			int l = read() , r = read() , ans = inf , tot = min ( r - l + 1 , 31 );
			for ( int j = 1 ; j <= tot ; j ++ )
			{
				s[j] = T.query ( 1 , 1 , n , l , r ).se;
				T.upd ( 1 , 1 , n , s[j] , inf );
			}
			for ( int j = 1 ; j <= tot ; j ++ ) T.upd ( 1 , 1 , n , s[j] , a[s[j]] );
			for ( int j = 1 ; j <= tot ; j ++ ) 
				for ( int k = j + 1 ; k <= tot ; k ++ )
					ans = min ( ans , a[s[j]] | a[s[k]] );
			cout << ans << endl;
		}
	}
	return 0;
}
posted @ 2023-10-26 10:16  Echo_Long  阅读(636)  评论(0)    收藏  举报