《随机化专题》小结

T1 ContestofRopePulling

image

image

相当于有 n 个重量为正的物品,m 个重量为负的物品,然后要去若干个物品使得总重量为 0 ,美丽度最大。

显然如果直接正着先取,然后再取负的做不了。

我们考虑如果能不连续一段取正或者连续一段取负,那么我们实际上第二维的值域不需要维护那么大。

这时候引入随机游走这么一个概念,假如你在一个 x 轴上,你每次有 0.5 概率向左走 1 ,有 0.5 概率向右走 1 ,那么你走了 n 步之后,很大概率在 nn 之间。

于是我们将 a 数组随意打乱,每次做背包,这样第二维很大概率不会超过 n×1000 如果超过,就很大概率不可能成为答案。

于是我们做背包就好了。

时间复杂度 O(Tn×1000×n)

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const LL inf=-1e18;
const int MAXN=1000*40*10;
int T,cnt,n,m,pp;
struct ddl {
	int a,b;
}a[MAXN];
LL f[MAXN],g[MAXN];
int main () {
	srand(time(0));
	scanf("%d",&T);
	while(T--) {
		cnt=0;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) {
			int u,v;
			scanf("%d%d",&u,&v);
			a[++cnt].a=u; a[cnt].b=v;
		}
		for(int i=1;i<=m;++i) {
			int u,v;
			scanf("%d%d",&u,&v);
			a[++cnt].a=-u; a[cnt].b=v;
		}
		int RR=sqrt(n)*1000;
		int p=RR; RR*=2;
		random_shuffle(a+1,a+1+cnt);
		
		for(int j=0;j<=RR;++j) f[j]=inf;
		f[p]=0;
		for(int j=1;j<=cnt;++j) {
			for(int q=0;q<=RR;++q) {
				g[q]=f[q];
			}
			for(int q=0;q<=RR;++q) {
				if(q-a[j].a>=0&&q-a[j].a<=RR) {
					f[q]=max(f[q],g[q-a[j].a]+a[j].b);
				}
			}
		}
		printf("%lld\n",f[p]);
	}
	return 0;
}

T2 GeometryProblem

image

image

显然题目保证有解,也就是说至少 n2 的点到一个点的距离相同。

每次我们随便选三个点出来,正确的概率是 18 ,多做几次就行了。

不过对于求三个点的圆心,是数学的东西,我不想管,直接抄代码。

点击查看代码
#include<bits/stdc++.h>
#define pi pair<DB,DB>
#define fi first
#define se second
#define eps 1e-9
typedef long long LL;
typedef double DB;

using namespace std;
const int MAXN=1e5+10;
double x,y,r;
int T; 
int n;
struct ddl {
	DB x,y;
}a[MAXN];
DB sq(DB x) {
	return x*x;
}
double dis(int p1,int p2) {
	return sqrt(sq(a[p1].x-a[p2].x)+sq(a[p1].y-a[p2].y));
}
double dis2(int E) {
	return sqrt(sq(a[E].x-x)+sq(a[E].y-y));
}
void cal(int E,int b,int c) {
	double a1=a[b].x-a[E].x, b1=a[b].y-a[E].y, c1=(a1*a1+b1*b1)/2;
	double a2=a[c].x-a[E].x, b2=a[c].y-a[E].y, c2=(a2*a2+b2*b2)/2;
	double d=a1*b2-a2*b1;
	x=a[E].x+(c1*b2-c2*b1)/d, y=a[E].y+(a1*c2-a2*c1)/d;
	r=dis2(E);
}
DB gt(DB x,DB y,DB xx,DB yy) {
	return sqrt(sq(x-xx)+sq(y-yy));
}
bool check(int p1,int p2,int p3) {
	cal(p1,p2,p3);
	int cnt=0;
	for(int i=1;i<=n;++i) {
		if(fabs(dis2(i)-r)<eps) ++cnt;
	}
	if(cnt*2>=n) {
		printf("%.6lf %.6lf %.6lf\n",x,y,r);
		return true;
	}
	return false;
}
int main () {
	srand(114514);
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;++i) {
			scanf("%lf%lf",&a[i].x,&a[i].y);
		}
		if(n<=2) {
			printf("%.6lf %.6lf %.6lf\n",a[1].x,a[1].y-1,1.0);
			continue;
		}
		if(n<=4) {
			double x=a[1].x+a[2].x,y=a[1].y+a[2].y;
			double R=dis(1,2);
			printf("%.6lf %.6lf %.6lf\n",x/2,y/2,R/2);
			continue;
		}
		while(1) {
			int p1=rand()%n+1,p2=rand()%n+1,p3=rand()%n+1;
			if(p1==p2||p2==p3||p1==p3) continue;
			if(check(p1,p2,p3)) break;
		}
	}
	return 0;
}

P7146 [THUPC2021 初赛] 独立

首先直接做的话显然是完全做不了的。然后这个东西一看就要 dp

但是如果你直接做有做不了,怎么办。

这时候我们想起来数据是随机生成的,手玩一下,可以发现,由于边很少,所以环的个数和大小都很少。

至多只有 7,8 个环这样子大概,而每个环的大小也不大。

对于没有环的 dp 是很好做的,所以我们就考虑对环单独考虑,然后再去把环变成树。

对于每条环上的边,暴力枚举他会不会两边的点都选,如果没有,就再枚举一下他两边哪个点不选。

上面这个东西要枚举子集,时间复杂度 O(3cnt)cnt 是环的个数。

然后每次要做一次 dp ,再乘一个 n

由于 cnt 很小,所以 O(3cntn) 可以通过。

点击查看代码
#include<bits/stdc++.h>
#define int long long
typedef long long LL;

using namespace std;
const LL inf=1e15;
const LL MAXN=2e5+10;
LL n,m,x,y,z,q=101,b=137,p=1e9+7;
LL a[MAXN];
map<LL,LL> mp[MAXN];
struct ddl {
	LL f,t,c;
}que[MAXN*2];
int cnt=1,h[MAXN];
void add(int f,int t,int c) {
	que[++cnt].f=h[f];
	que[cnt].t=t;
	que[cnt].c=c;
	h[f]=cnt;
}
LL xx,yy,zz;
void init() {
	scanf("%lld%lld%lld%lld%lld%lld",&n,&m,&xx,&yy,&a[0],&zz);
	for(int i=1;i<=n;++i) {
		a[i]=(q*a[i-1]+b)%p;
	}
	for(int i=1;i<=m;++i) {
		xx=(q*xx+b)%p;
		yy=(q*yy+b)%p;
		zz=(q*zz+b)%p;
		int x=xx%n+1,y=yy%n+1;
		if(!mp[x].count(y)&&x!=y) {
			mp[x][y]=1;
			mp[y][x]=1;
			add(x,y,zz);
			add(y,x,zz);
		}
	}
}
int dfn[MAXN],rt[MAXN];
bool g[MAXN];
struct circle {
	LL f,t,c;
}E[MAXN];
int tot,ban[MAXN*2],st[MAXN],top,zt[MAXN];
void dfs(int u,int fa) {
	dfn[u]=++cnt;
	if(u==fa) rt[u]=1;
	for(int i=h[u];i;i=que[i].f) {
		int t=que[i].t;
		if(t==fa) continue;
		if(!dfn[t]) {
			dfs(t,u);
			g[u]|=g[t];
		}
		else {
			if(dfn[t]<dfn[u]) {
				E[++tot]={t,u,que[i].c},ban[i]=1,ban[(i^1)]=1,g[u]=1;
			}
		}
	}
} 
LL dp[MAXN][2],root[MAXN],fgo,H[MAXN],qls;
bool zk[MAXN];
void DFS(int u,int fa) {
	zk[u]=1;
	for(int i=h[u];i;i=que[i].f) {
		int t=que[i].t;
		if(t==fa||ban[i]) continue;
		DFS(t,u);
		dp[u][0]+=max(dp[t][0],dp[t][1]);
		dp[u][1]+=max(dp[t][1]-que[i].c,dp[t][0]);
	}
}
void work() {
	cnt=0;
	for(int i=1;i<=n;++i) {
		if(!dfn[i]) {
			dfs(i,i);
		}
	}
	for(int i=1;i<=tot;++i) {
		st[++top]=E[i].f;
		st[++top]=E[i].t;
	}
	sort(st+1,st+1+top);
	top=unique(st+1,st+1+top)-st-1;//环上的点 
	int limit=(1<<top)-1;
	LL ans=0;
	for(int i=1;i<=n;++i) dp[i][0]=0,dp[i][1]=a[i];
	for(int i=1;i<=n;++i) {
		if(rt[i]&&!(g[i])) {
			DFS(i,i);
			ans+=max(dp[i][0],dp[i][1]);
		}
	}
	for(int i=1;i<=n;++i) {
		if(!zk[i]) {
			H[++qls]=i;
		}
	}
	for(int i=1;i<=n;++i) {
		if(!zk[i]&&rt[i]) {
			root[++fgo]=i;//环上点的根 
		}
	}
	limit=(1<<tot)-1;
	LL da=ans;
	for(int i=0;i<=limit;++i) {//强制某些环边贡献 
		LL ls_ans=ans;
		for(int j=1;j<=top;++j) zt[st[j]]=0;
		for(int j=1;j<=tot;++j) {
			if((i&(1<<(j-1)))) {
				ls_ans-=E[j].c;//枚举被造成贡献的根 
				zt[E[j].f]=zt[E[j].t]=1;//是否被强制选择 
			}
		}
		int lim=(i^limit);
		for(int j=lim;;j=((j-1)&lim)) {//枚举没被选择的环边 
			for(int q=1;q<=qls;++q) 
				dp[H[q]][1]=a[H[q]],dp[H[q]][0]=0;
			for(int q=1;q<=qls;++q) if(zt[H[q]]) 
				dp[H[q]][0]=-inf;
			for(int q=1;q<=tot;++q) {
				if(!((1<<(q-1))&lim)) continue;
				if((1<<(q-1))&j) {
					dp[E[q].t][1]=-inf;
				}
				else {
					dp[E[q].f][1]=-inf;
				}
			}
			LL ls_a=ls_ans;
			for(int q=1;q<=fgo;++q) {
				DFS(root[q],root[q]);
				ls_a+=max(dp[root[q]][0],dp[root[q]][1]);
			}
			da=max(da,ls_a);
			if(!j) break;
		}
	}
	printf("%lld\n",da);
}
signed main() {
	init();
	work();
	return 0;
}

T4 棋盘上的旅行

image

image

image

对于至少走 k 中颜色边的限制,如果我们暴力把每种颜色的状态有没有走过都记下来,显然是做不了的,但是实际上我们只需要知道有没有走过 k 条边,不需要知道那么多信息。

考虑每次给每种颜色再随意染一种颜色,值域为 1k ,然后我们再跑状压,就可以获得 O(nm2k) 的优秀做法。

然后我们每次正确的概率是 k!kk 最坏情况下错误概率是 0.993

不过我们多跑几次,跑个 450 次就可以将错误概率降低到 0.04 ,再多跑几次也不是不行。

然后就可以通过了,脸黑可以多跑几次。

PS: 114514 的种子被卡掉了,19260817 过了。

启示:

对于这题如果直接做是做不了的,因为你要记得信息太多了,于是我们考虑加强题目限制,将一部分颜色变成同一种颜色,这样我们在保证一部分正确率的情况下不用记录太多信息,只需要较少的信息量即可完成题目。

对于一些题目,如果原问题限制太弱,要太多信息,可以加强限制,减少信息的需要量。

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=30;
int T;
int n,m,k;
int a[MAXN][MAXN];
int f[MAXN][MAXN][(1<<7)];
int col[MAXN*MAXN],ans;
int dx[10+10]={0,0,1,-1};
int dy[10+10]={1,-1,0,0};
struct dl {
	int x,y,j;
};
queue<dl> q;
void zx() {
//	cout<<114514<<' ';
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) {
			for(int q=0;q<(1<<k);++q) {
 				f[i][j][q]=-1;
			}
		}
	}
	for(int i=1;i<=n*m;++i) col[i]=rand()%k+1;
	queue<dl> q;
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) {
			if(a[i][j]==-1) continue;
			int t=(1<<(col[a[i][j]]-1));
			if(a[i][j]==0) t=0;
			f[i][j][t]=0;
			q.push({i,j,t});
		}
	}
	while(!q.empty()) {
		dl ls=q.front();
		q.pop();
		for(int i=0;i<4;++i) {
			int xx=ls.x+dx[i];
			int yy=ls.y+dy[i];
			int t=ls.j;
			if(a[xx][yy]==-1) continue;
			if(col[a[xx][yy]]>0) t|=(1<<(col[a[xx][yy]]-1));
			if(xx<1||yy<1||xx>n||yy>m||f[xx][yy][t]>=0) continue;
			f[xx][yy][t]=f[ls.x][ls.y][ls.j]+1;
			q.push({xx,yy,t});
		}
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) {
			if(f[i][j][(1<<k)-1]==-1) continue;
			ans=min(ans,f[i][j][(1<<k)-1]);
		}
	}
}
int main() {
	srand(19260817);
	scanf("%d",&T);
	while(T--) {
		ans=114514;
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=n;++i) {
			for(int j=1;j<=m;++j) {
				scanf("%d",&a[i][j]);
			}
		}
		double sta=clock();
		for(int i=1;i<=450;++i) {
			zx();
		}
		if(ans==114514) {
			puts("-1");
		}
		else {
			printf("%d\n",ans);
		}
	}
	return 0;
}

T5 模范学长

我不会行列式,看这个题解,想看的看这个题解,看我看我

T6 度度熊算算数

image

image

image

首先考虑为一条链的情况。

直接做可以获得一个 O(n2k) 的做法,考虑到有决策单调性,可以优化到 O(nklogn)

但是由于是环,考虑破环成链,枚举开始点。

这样我们就可以获得 O(n2klogn) 的做法,但是这样还是无法通过此题。

我们考虑不要那么蠢所有点都枚举一次,因为当 k 很大时,我们随便枚举几个点,正确概率都很大,当 k 小时可以全部都做一次,就可以,不过可以考虑枚举 nk×G 次, G 是一个小常数。这样总时间复杂度就是 O(n2logn)

这样我们就可以大概率通过此题(我的 G 取的 5

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;
typedef double DB;

using namespace std;
const int M=1e7+10;
int prime[M],d[M],cnt,n,k;
bool sf[M];
DB lg[M];
void init() {
	sf[1]=1;
	for(int i=2;i<=M-10;++i) {
		if(!sf[i]) {
			prime[++cnt]=i;
			lg[i]=log(i); d[i]=i;
		}
		for(int j=1;j<=cnt;++j) {
			int t=i*prime[j];
			if(t>M-10) break;
			sf[t]=1; d[t]=prime[j];
			lg[t]=lg[i]+lg[prime[j]];
			if(i%prime[j]==0) break;
		}
	}
}
const int MAXN=2100,inf=-1e9;
int a[MAXN],s[MAXN],b[MAXN],tot,pre[MAXN][MAXN];
int q[MAXN],h,t,kk[MAXN],pp;
DB f[MAXN][MAXN];
map<int,int> mp;
double cal(int i,int j,int p) {
	return f[p-1][j]+lg[s[i]-s[j]];
}
int erfind(int x,int y,int i) {
	int l=y,r=n+1,mid;
	while(l+1<r) {
		mid=(l+r)/2;
		if(cal(mid,x,i)<=cal(mid,y,i)) r=mid;
		else l=mid;
	}
	return l;
}
double ans;
int sta,res;
void calc(int st) {
	tot=0;
	for(int i=st;i<=n;++i) b[++tot]=a[i];
	for(int i=1;i<st;++i) b[++tot]=a[i];
	for(int i=1;i<=n;++i) {
		s[i]=s[i-1]+b[i];
	}
	for(int i=0;i<=k;++i) 
		for(int j=1;j<=n;++j) f[i][j]=0;
	f[0][0]=0;
	for(int i=1;i<=k;++i) {
		q[h=t=1]=i-1;
		for(int j=0;j<=n;++j) kk[j]=0;
		for(int j=i;j<=n;++j) {
			while(h<t&&kk[h]<j) ++h;
			f[i][j]=cal(j,q[h],i); pre[i][j]=q[h];
			while(h<t&&kk[t-1]>=erfind(q[t],j,i)) --t;
			if(res&&i==2&&j==2) pp=1;
			kk[t]=erfind(q[t],j,i); pp=0;
			q[++t]=j; kk[t]=0;
		}
	}
	if(f[k][n]>ans) {
		ans=f[k][n];
		sta=st;
	}
}
int ind[MAXN],len;
int main () {
	srand(114514);
	init();
	while(scanf("%d%d",&n,&k)!=EOF) {
		for(int i=1;i<=n;++i) {
			scanf("%d",&a[i]);
			a[n+i]=a[i];
		}
		int K=n/k*5; ans=0; sta=0;
		for(int i=1;i<=K;++i) {
			int st=rand()%n+1;
			calc(st); 
		}
		res=1;
		calc(sta); res=0;
		int xx=k,yy=n; len=0;
		for(int i=k;i>=1;--i) {
			ind[++len]=s[yy]-s[pre[i][yy]];
			yy=pre[xx][yy];
			--xx;
		}
		mp.clear();
		for(int i=1;i<=len;++i) {
			int ls=ind[i];
			while(ls>1) {
				++mp[d[ls]];
				ls/=d[ls];
			}
		}
		for(auto t:mp) {
			printf("%d %d\n",t.first,t.second);
		}
	}
	return 0;
}
posted @   daduoli  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示