首页 > 编程语言 >随机化算法杂题

随机化算法杂题

时间:2022-11-16 15:59:52浏览次数:42  
标签:rand return 10 int double rep 随机化 算法 杂题

srO Charlie Orz

[CF1354G]Find a Gift

\(【题目描述】\)

这是一道交互题。你有一排 \(n(n\leq 10^3)\) 个箱子,编号为 \(1\rightarrow n\),每个箱子可能装有一个礼物或石头(保证礼物的数量 \(k\leq \lfloor \frac{n}{2}\rfloor\)),装有石头的箱子重量相等,且严格大于装有礼物的箱子,但装有礼物的箱子重量可能不相等。你可以进行不超过 \(50\) 次询问,每次询问给出两个箱子编号的不相交集合 \(A,B\),表示询问 \(A\) 中的箱子重量之和与 \(B\) 中的箱子重量之和的大小关系。现要求找出编号最小的装有礼物的箱子编号。

\(【考点】\)

随机化、二分、倍增

\(【做法】\)

一道挺有思维难度的题(对于我来讲交互题都这样

首先 \(50\) 次询问以及 \(n\leq 1000\) 可以想到 \(5\log\) 左右的做法,一般使用二分或倍增。可以发现,想要确定一个箱子的状态,必须用一个已知为石头的箱子去与之比较。

假设我们确定了 \(1\) 号箱子是石头,就可以用它和 \(2\) 号箱子比较,推算出 \(2\) 号箱子的情况,若还是石头,我们可以用 \([1,2]\) 箱子去推出 \([3,4]\) 箱子的情况……直到发现 \([1,p],[p+1,2p]\) 重量不相等,然后就可以将答案确定在 \([p+1,2p]\) 中。于是可以利用 \([1,p]\) 全是石头的性质,二分出第一个礼物的位置。除去确定 \(1\) 号箱子,一共需要 \(2\log\) 左右的次数。

回到一开始的问题,如何确定 \(1\) 号箱子是石头。题目中限定了 \(k\leq \lfloor \frac{n}{2}\rfloor\),即至少有一半为石头,此时大概还剩 \(3\log\) 次操作,可以直接Roll \(3\log\) 个箱子与 \(1\) 号作比较,若Roll出了一个石头就可以确定 \(1\) 号箱子的状态概率至少为 \(1-\frac{1}{2^{30}}\).

\(【代码】\)

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define go(i,u) for(int i=head[u];i;i=a[i].nxt)
#define f fflush(stdout)
using namespace std;
int n,k,t;
char s[10];
int query(int l1,int r1,int l2,int r2)
{
	printf("? %d %d\n",r1-l1+1,r2-l2+1);
	rep(i,l1,r1) printf("%d ",i);
	puts("");
	rep(i,l2,r2) printf("%d ",i);
	puts(""),f;
	scanf("%s",s+1);
	if(s[1]=='F') return 1;
	if(s[1]=='S') return 2;
	return 3;
}
int main()
{
	srand((int)time(0));
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&k);
		bool q=true;
		rep(i,1,20){//roll 20个箱子与1比较
			int r=rand()%(n-1)+2;
			if(query(1,1,r,r)==2){
				q=false;break;
			}
		}
		if(!q){
			printf("! 1\n"),f;
			continue;
		}
		int now=1,pos=1;
		while((now<<1)<=n){//倍增比较[1,now]与[now+1,now*2]
			if(query(1,now,now+1,now<<1)==1) break;
			now<<=1;
		}
		int l=now+1,r=min((now<<1),n);
		while(l<r){//二分找[now+1,now*2]的第一个1
			int mid=(l+r)>>1;
			if(query(l,mid,1,mid-l+1)==3) l=mid+1;
			else r=mid;
		}
		printf("! %d\n",l),f;
	}
	return 0;
}

[WC2018]通道

\(【题目描述】\)

给出 \(n(n\leq 10^5)\) 个点,点与点之间有 \(3\) 类连边,边都有长度,每类边分别构成一棵树。现要找出两个点 \(x,y\),使得他们在三棵树上的距离之和最大。

\(【考点】\)

边分治、虚树、随机化

\(【做法】\)

一眼随机化

可以模拟退火,但是时间复杂度可能不太能接受。

考虑一下一棵树的情况,就是两遍大法师找到直径两端点。那扩展到三棵树,同样以一个点为根,进行狂暴大法师,找到一个深度最深的点,以他为根,反复循环大法师,直到超时为止。然后借助模拟退火优化的小trick,换几个根多跑几遍,记录全局最优解。

\(【代码】\)

#include<bits/stdc++.h>
#define int long long
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define go(i,u,t) for(int i=head[t][u];i;i=a[t][i].nxt)
using namespace std;
const int N=5e5+50;

struct edge{
	int to,val,nxt;
}a[3][N<<1];
int head[3][N],dis[3][N],root=1,cnt[3],n,ans;
bool vis[N];
inline int read()
{
	int x=0;
	char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+ch-48;
		ch=getchar();
	}
	return x;
}
inline void print(int x)
{
	if(x>9) print(x/10);
	putchar((x%10)|48);
}
namespace kuangbaodfs{
	void dfs(int u,int fa,int t)
	{ 
		go(i,u,t){
			int v=a[t][i].to;
			if(v!=fa){
				dis[t][v]=dis[t][u]+a[t][i].val;
				dfs(v,u,t);
			}
		}
	}
	inline int Dis(int x){return dis[0][x]+dis[1][x]+dis[2][x];} 
	void solve()
	{
		int T=10;
		root=((int)rand()*(int)rand())%n+1;//注意这里rand()的范围只有32767
		while(T--){
			dis[0][root]=dis[1][root]=dis[2][root]=0;
			rep(t,0,2) dfs(root,0,t);
			int now=0;
			rep(i,1,n){
				if(Dis(now)<Dis(i)){
					now=i,ans=max(ans,Dis(i));
				}
			}
			root=now;
		}
	}
}
using namespace kuangbaodfs;
void add(int u,int v,int w,int t)
{
	++cnt[t];
	a[t][cnt[t]].to=v;
	a[t][cnt[t]].val=w;
	a[t][cnt[t]].nxt=head[t][u];
	head[t][u]=cnt[t];
}
signed main()
{
	double st=clock();
	n=read();
	int u,v,w;
	rep(t,0,2){
		rep(i,1,n-1){
			u=read(),v=read(),w=read();
			add(u,v,w,t);
			add(v,u,w,t);
		}
	}
	while((clock()-st)/CLOCKS_PER_SEC<3.5) solve();
	printf("%lld\n",ans);
	return 0;
}

[CF1114E]Arithmetic Progression

\(【题目描述】\)

这是一道交互题。交互库有一个长度为 \(n(n\leq 10^6)\) 的乱序等差数列 \(a(a_i\leq 10^9)\),最多可以进行 \(60\) 次以下询问:

  1. 给定一个 \(i\),询问 \(a_i\) 的值
  2. 给定一个 \(x\),询问是否存在 \(a_i>x\)

现要求出这个等差数列的首项和公差。

\(【考点】\)

数学(?

\(【做法】\)

首先可以通过二分,使用第二个操作 \(30\) 次找到末项。接着roll \(30\) 个数搞操作1,求出它们与末项的差的 \(\gcd\)

于是就做完了,感性理解一下发现成功率很高,严谨证明大概为 \(1-\frac{1}{10^9}\)

\(【代码】\)

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
using namespace std;
const int N=1e6+50;
int n,t=60;
bool vis[N];
inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
inline bool que(int x)
{
	printf("> %d\n",x);
	fflush(stdout);
	int p;
	scanf("%d",&p);
	return p;
}
int main()
{
	srand((int)time(0));
	scanf("%d",&n);
	int l=0,r=1e9;
	while(l<r){
		int mid=(l+r)>>1;
		t--;
		if(que(mid)) l=mid+1;
		else r=mid;
	}
	int g=0,x,cnt=0;
	rep(i,1,t){
		if(cnt==n) break;
		int r=((int)rand()*(int)rand())%n+1;//还是注意rand()的范围
		while(vis[r]) r=rand()%n+1;
		vis[r]=true,cnt++;
		printf("? %d\n",r);
		fflush(stdout);
		scanf("%d",&x);
		g=gcd(g,l-x); 
	}
	printf("! %d %d\n",l-(n-1)*g,g);
	return 0;
}

[CF1305F]Kuroni and the Punishment

\(【题目描述】\)

给定一个长为 \(n(n\leq 2\times 10^5)\) 的序列 \(a\),每次可以将一个数+1或-1,求使得所有数都不互质的最小操作次数。

\(【考点】\)

随机化

\(【做法】\)

首先一种合法方案就是将所有数变成偶数。因此操作次数的上限即为 \(n\),也就是说,有一半以上的数变化量不超过1。因此,我们在序列中随机roll \(50\) 个数,设当前数为 \(x\),将 \(x,x+1,x-1\) 的质因数全部存到一个集合里面,最终集合里面至多有 \(600\) 个质数,且必有一个是最终整个序列 \(gcd\) 的因数,因此直接算每个质数作为 \(gcd\) 的因数的情况下,序列中所有数的变化量之和,去最小即可。

\(【代码】\)

#include<bits/stdc++.h>
#define IT set<ll>::iterator
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)

using namespace std;
typedef long long ll;
const int N=2e5+50,M=2e6+50;
const ll inf=1e18;
int n,ed;
ll a[N],s[N];
bool vis[M];
set<ll> prime;//set自动去重
void work(ll x)
{
	if(x<=0) return ;
	for(ll i=2;i*i<=x;++i){
		if(x%i==0){
			if(!vis[i]) vis[i]=true,prime.insert(i);
			while(x%i==0) x/=i;
		}
	}
	if(x!=1) prime.insert(x);
}
inline ll Run(ll x)
{
	ll cnt=0;
	rep(i,1,n){
		ll t=a[i]/x*x;
		if(t) cnt+=min(a[i]-t,t+x-a[i]);
		else cnt+=t+x-a[i];
	}
	return cnt;
}
int main()
{
	srand((int)time(0));
	scanf("%d",&n);
	rep(i,1,n) scanf("%lld",&a[i]);
	rep(t,1,50){
		int p=((int)rand()*(int)rand())%n+1;
		work(a[p]),work(a[p]+1),work(a[p]-1);
	}
	ll ans=inf;
//	rep(i,1,ed) printf("%lld ",prime[i]);
//	puts("");
	for(IT it=prime.begin();it!=prime.end();++it){
//		printf("%lld\n",Run(prime[i]));
		ans=min(ans,Run(*it));
	}
	if(ans==inf) printf("%d\n",n);
	else printf("%lld\n",ans);
	return 0;
}

[JSOI2016]炸弹攻击

\(【题目描述】\)

二维平面上有 \(n(n\leq 10)\) 个圆和 \(m(m\leq 10^3)\) 个点,求一个圆心任意、半径不超过 \(R\) 的圆,使其不予任何给定的圆相交,且覆盖点的个数最多。

\(【考点】\)

计算几何、模拟退火

\(【做法】\)

一眼退火

如果直接拿覆盖点数作为估价函数,价值相等的点太多,不好搞,因此可以考虑设估价函数 \(f(x)=c-kr_0\),其中 \(c\) 表示覆盖点数,\(r_0\) 表示再增加多少半径能够覆盖到下一个点,\(k\) 是一个 \(>0\) 的常数。经过一番尝试,最终确定 \(k=14.14\) 时最优.

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define eps 1e-6
using namespace std;
const int N=1e3+50,M=15;
const double T=5e8,ed=1e-10,del=0.998,inf=1e9;
double x[M],y[M],r[M],p[N],q[N];
int n,m,ans,cnt;
double R,X,Y,val;
inline double Rand(){return rand()%100000/100000.0;}
inline double dis(double x,double y,double xx,double yy){
	return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}
double work()
{
	cnt=0;
	double nowr=inf;
	rep(i,1,n) nowr=min(nowr,dis(x[i],y[i],X,Y)-r[i]);
	nowr=max(nowr,0.0);
	nowr=min(R,nowr);
	double minr=inf;
	
	rep(i,1,m){
		double d=dis(p[i],q[i],X,Y);
		minr=min(minr,d-nowr);
		if(nowr-d>=-eps) ++cnt;
	}
	minr=max(minr,0.0);
	return -minr*14.14+(double)cnt;
}
void SA()
{
	X=0,Y=0;
	val=-inf;
	for(double t=T;t>=ed;t*=del){
		double tx=X,ty=Y;
		X=X+(rand()*2-RAND_MAX)*t;
		Y=Y+(rand()*2-RAND_MAX)*t;
//		printf("SS:%lf %lf\n",X,Y);
		double nw=work();
		if(nw>val){
//			printf("%d %lf %lf\n",cnt,X,Y);
			val=nw,ans=cnt;
		}
		else{
			if(exp((nw-val)/t)<=Rand()) X=tx,Y=ty;
		}
	}
}
int main()
{
    srand(114514);
	scanf("%d%d%lf",&n,&m,&R);
	rep(i,1,n) scanf("%lf%lf%lf",&x[i],&y[i],&r[i]);
	rep(i,1,m) scanf("%lf%lf",&p[i],&q[i]);
	double st=clock();
	while((clock()-st)/CLOCKS_PER_SEC<0.85) SA();
	printf("%d\n",ans); 
	return 0;
}

[CF1305F]Subway Pursuit

\(【题目描述】\)

你有一辆发怒/fn的火车,你要抓住他,每次可以选择一个区间 \([l,r]\),询问火车是否在这个区间内,若 \(l=r\) 且回答“是”,那么就抓住了火车,算成功,否则火车会往左或往右移动至多 \(k(k\leq 10)\) 步(火车移动范围为 \([1,10^{18}]\))。你需要在 \(4500\) 次询问内找到这辆火车。

\(【考点】\)

随机化、二分

\(【做法】\)

考虑对整个范围进行二分,每次算上可能移动的 \(k\) 步即可(例如二分出可能区间为 \([l,mid]\),则下次询问 \([l-k,mid+k]\)),当区间长度到达一个阈值 \(B\) 的时候,区间不再会递减,此时直接盲猜一个位置,然后放区间长度 \(+20\)

  1. 取 \(B=40\) 时,临近阈值的几次区间长度变化最坏情况为: \(60\rightarrow 50\rightarrow 45\rightarrow 43\rightarrow 42\rightarrow 41\),这样盲猜的次数只有 \(800\) 次左右。
  2. 取 \(B=50\) 时,临近阈值的几次区间长度变化最坏情况为:\(70\rightarrow 55\rightarrow 48\),大约可以猜 \(1400+\) 次,因此取 \(B=50\) 更优

\(【代码】\)

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)

using namespace std;
typedef long long ll;
ll n,k;
char s[100];
bool query(ll l,ll r)
{
	printf("%lld %lld\n",l,r);
	fflush(stdout);
	scanf("%s",s+1);
	if(s[1]=='Y') return true;
	return false;
}
int main()
{
	scanf("%lld%lld",&n,&k);
	ll l=1,r=n;
	while(1){
		while(r-l>50){
			ll mid=(l+r)>>1;
			if(query(l,mid)) r=mid;
			else l=mid+1;
			l=max(1ll,l-k);
			r=min(n,r+k);
		}
		ll pos=(ll)rand()*rand()*rand()*rand()%(r-l+1)+l;
		if(query(pos,pos)) break;
		l=max(1ll,l-k),r=min(n,r+k);
	}
	return 0;
}

[CF1061F]Lost Root

\(【题目描述】\)

这是一道交互题。交互库有一个 \(n(n\leq 1500)\) 个节点的满 \(k(k\leq n)\) 叉树,你需要找到这棵树的根,你可以进行以下操作至多 \(60n\) 次:给出三个点 \(a,b,c\),询问 \(b\) 是否在路径 \((a,b)\) 上。

\(【考点】\)

随机化

\(【做法】\)

满 \(k\) 叉树启发我们先找到叶子,然后根据根在两个非同一大子树内的叶子的中点,找到根。具体而言,可以先问出路径上的所有点,然后对于所有点诶个问与叶子的距离,共花费 \(n+h^2\) 次询问。

接下来问题变成了找叶子,我们先随便roll两个点 \(u,v\),然后枚举点 \(w\),若 \(v\) 在路径 \((u,w)\) 上,直接 \(v\leftarrow w\),最后 \(v\) 肯定是一个叶子,共花费 \(n\) 次询问,然后再用同样的方法找另一个叶子,只要叶子之间的距离为 \(2h-1\),也就是不在同一个大子树上,就可以直接找到根了。这样一次花费共 \(2n\) 次询问,成功的概率至少为 \(\frac{1}{2}\),随便跑个 \(19\) 次左右即可,成功率为 \(1-\frac{1}{2^{19}}\)

\(【代码】\)

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define go(i,u) for(int i=head[u];i;i=a[i].nxt)
using namespace std;
const int N=1e3+5e2+50;
int n,k,h;
int query(int x,int p,int y)
{
	printf("? %d %d %d\n",x,p,y);
	fflush(stdout);
	char s[10];
	scanf("%s",s+1);
	return s[1]=='Y';
}
int get_dis(int x,int y)//全局查找两点距离
{
	int cnt=2;
	rep(i,1,n){
		if(i!=x&&i!=y) cnt+=query(x,i,y);
	}
	return cnt;
}
inline int find_leaf()
{
	int u=rand()%n+1,v=rand()%n+1,w;
	v=rand()%n+1;
	rep(i,1,n){
		w=rand()%n+1;
		w=rand()%n+1;
		if(query(u,v,w)) v=w;
	}
	return v;	
}
int s[N],ed;
int get_dis_plus(int x,int y)//知道链上的节点后查找两点距离
{
	int cnt=2;
	rep(i,1,ed){
		if(s[i]!=x&&s[i]!=y) cnt+=query(x,s[i],y);
	}
	return cnt;
}
int get_ans(int x,int y)
{
	ed=0;
	rep(i,1,n){
		if(i!=x&&i!=y&&query(x,i,y)) s[++ed]=i;
	}
	rep(i,1,ed){
		if(get_dis_plus(x,s[i])==h) return s[i];
	}
	return 114514;
}
int main()
{
	srand((int)time(0));
	scanf("%d%d",&n,&k);
	h=log(n)/log(k)+1;
	rep(i,1,19){
		int A=find_leaf(),B=find_leaf();
		if(A!=B&&get_dis(A,B)==2*h-1){
			printf("! %d\n",get_ans(A,B));
			fflush(stdout);
			return 0;
		}
	}
	int r=rand()%n+1;
	printf("! %d\n",r); 
	return 0;
}

标签:rand,return,10,int,double,rep,随机化,算法,杂题
From: https://www.cnblogs.com/Unlimited-Chan/p/16896152.html

相关文章

  • 代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素。
    今日任务数组理论基础,704.二分查找,27.移除元素1数组理论基础文章链接:https://programmercarl.com/%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html......
  • 看scan line 算法有感
      永远要做一点困难的事情,生活中太一帆风顺了很容易自满。在安逸的环境下变得越来越平庸,温水煮青蛙,时间一点点流逝,却没有获得进步。不要待在舒适区!不要待在舒适区!不......
  • 实验四:神经网络算法实验
    实验四:神经网络算法实验【实验目的】理解神经网络原理,掌握神经网络前向推理和后向传播方法;掌握神经网络模型的编程实现方法。【实验内容】1.1981年生物学家格若根(W......
  • 代码随想录第三十五天 | 贪心算法
     第三十五天,继续贪心 860.柠檬水找零 classSolution{publicbooleanlemonadeChange(int[]bills){intn=bills.length;if(bills[0......
  • CPU乱序执行基础 —— Tomasulo算法及执行过程
    IBM360/91浮点单元最先实现Tomasulo算法从而允许乱序执行。360体系只有4个双精度浮点寄存器,限制了编译器调度的有效性。而且,IBM360/91的访存和浮点延迟都很长,如果顺序执......
  • 网易严选搜索推荐实践之:“全能选手”召回表征算法实践
    今天给大家带来网易严选人工智能部算法专家潘胜一先生所做的分享《网易严选搜索推荐实践之:“全能选手”召回表征算法实践.pdf》。潘先生所在的团队主要负责搜索推荐,搜索推荐......
  • Java实现5种负载均衡算法
    Java实现5种负载均衡算法1.轮询算法importcom.google.common.collect.Lists;importjava.util.List;importjava.util.concurrent.atomic.AtomicInteger;/***......
  • LCA 之 Tarjan(离线)算法
    \(LCA\)之\(Tarjan\)(离线)算法什么是最近公共祖先?在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先......
  • 网易严选搜索推荐实践之:“全能选手”召回表征算法实践.pdf(附下载链接)...
    今天给大家带来网易严选人工智能部算法专家潘胜一先生所做的分享《网易严选搜索推荐实践之:“全能选手”召回表征算法实践.pdf》。潘先生所在的团队主要负责搜索推荐,搜索推荐......
  • 算法基础:离散化及模板详解
    ⭐写在前面的话:本系列文章旨在复习算法刷题中常用的基础算法与数据结构,配以详细的图例解释,总结相应的代码模板,同时结合例题以达到最佳的学习效果。本专栏面向算法零基础但有......