搜索,折半搜索训练记录 2025.1

「NOIP2009」靶形数独

https://www.gxyzoj.com/d/hzoj/p/P437

暴力枚举每一个空位的方法显然会T,可以发现,其实每一位的方案数有限,所以可以从可能性最少的点开始枚举

此时,填入一个数就会给后面的点增加限制,从而提高效率

因此,按照可选的情况数预处理填数队列即可,注意,选出点(x,y)后,要假设这个点已经填了

点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
int a[10][10],cnt,cntx[10],cnty[10],cnta[10],id[10][10];
struct node{
	int x,y;
}s[100];
bool vis[10][10];
int dx[10]={0,1,1,1,4,4,4,7,7,7};
int dy[10]={0,1,4,7,1,4,7,1,4,7};
int b[10][10],ans=-1;
int c[9][9]={
{6,6,6,6,6,6,6,6,6},
{6,7,7,7,7,7,7,7,6},
{6,7,8,8,8,8,8,7,6},
{6,7,8,9,9,9,8,7,6},
{6,7,8,9,10,9,8,7,6},
{6,7,8,9,9,9,8,7,6},
{6,7,8,8,8,8,8,7,6},
{6,7,7,7,7,7,7,7,6},
{6,6,6,6,6,6,6,6,6},
};
int clac()
{
	int res=0;
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			res+=b[i][j]*c[i-1][j-1];
//			printf("%d ",b[i][j]);
		}
//		printf("\n");
	}
//	printf("%d\n",res);
	return res;
}
bool check(int x,int y,int val)
{
//	for(int i=1;i<=9;i++)
//	{
//		for(int j=1;j<=9;j++)
//		{
//			printf("%d ",b[i][j]);
//		}
//		printf("\n");
//	}
	for(int i=1;i<=9;i++)
	{
		if(i!=y&&b[x][i]==val) return 0;
	}
	for(int i=1;i<=9;i++)
	{
		if(i!=x&&b[i][y]==val) return 0;
	}
	for(int i=dx[id[x][y]];i<dx[id[x][y]]+3;i++)
	{
		for(int j=dy[id[x][y]];j<dy[id[x][y]]+3;j++)
		{
			if((i!=x||j!=y)&&b[i][j]==val) return 0;
		}
	}
	return 1;
}
void dfs(int now)
{
	if(now==cnt+1)
	{
		ans=max(ans,clac());
		return;
	}
	int x=s[now].x,y=s[now].y;
//	printf("%d %d %d %d\n",now,x,y,b[s[now-1].x][s[now-1].y]);
	for(int i=1;i<=9;i++)
	{
		if(check(x,y,i))
		{
			b[x][y]=i;
			dfs(now+1);
			b[x][y]=0;
		}
	}
}
int main()
{
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			scanf("%d",&a[i][j]);
			int x=(i-1)/3,y=(j+2)/3;
			id[i][j]=x*3+y;
			b[i][j]=a[i][j];
			if(!a[i][j]) cnt++;
			else cntx[i]++,cnty[j]++,cnta[id[i][j]]++;
		}
	}
	for(int k=1;k<=cnt;k++)
	{
		int mx=0;
		for(int i=1;i<=9;i++)
		{
			for(int j=1;j<=9;j++)
			{
				if(!vis[i][j]&&!a[i][j])
				{
					if(mx<cntx[i]+cnty[j]+cnta[id[i][j]])
					{
						mx=cntx[i]+cnty[j]+cnta[id[i][j]];
						s[k].x=i,s[k].y=j;
					}
				}
			}
		}
		cntx[s[k].x]++,cnty[s[k].y]++;
		cnta[id[s[k].x][s[k].y]]++;
		vis[s[k].x][s[k].y]=1;
//		printf("%d %d\n",s[k].x,s[k].y);
	}
//	printf("%d\n",cnt);
	dfs(1);
	printf("%d",ans);
	return 0;
}

[CQOI2013] 新数独

https://www.gxyzoj.com/d/hzoj/p/4367

离谱做法,数独直接搜共约1021

先搜索每一宫的所有可能性,因为限制很多,所以情况很少,储存到vector中

然后将他们拼接起来,判断是否满足条件即可

代码:

点击查看代码
#include<cstdio>
#include<string>
#include<iostream>
#include<vector>
using namespace std;
int row[10][10],lin[10][10],cnt1,cnt2;
int a[5][5],b[5][5],c[10][10],d[10],cnt[10];
struct node{
	int a[4][4];
};
vector<node> v[10];
bool check()
{
	for(int i=1;i<=9;i++)
	{
		int x=(i-1)/3+1,y=(i-1)%3+1;
		c[x][y]=d[i];
	}
	for(int i=1;i<=3;i++)
	{
		for(int j=1;j<=2;j++)
		{
			if(c[i][j]>c[i][j+1]&&a[i][j]) return 0;
			if(c[i][j]<c[i][j+1]&&!a[i][j]) return 0;
		}
	}
	for(int i=1;i<=2;i++)
	{
		for(int j=1;j<=3;j++)
		{
			if(c[i][j]>c[i+1][j]&&!b[i][j]) return 0;
			if(c[i][j]<c[i+1][j]&&b[i][j]) return 0;
		}
	}
	return 1;
}
bool vis[10];
void dfs(int now,int id)
{
	if(now==10)
	{
		if(check())
		{
			node tmp;
			for(int i=1;i<=9;i++)
			{
				int x=(i-1)/3+1,y=(i-1)%3+1;
				tmp.a[x][y]=d[i];
			}
			cnt[id]++;
			v[id].push_back(tmp);
		}
		return;
	}
	for(int i=1;i<=9;i++)
	{
		if(!vis[i])
		{
			vis[i]=1,d[now]=i;
			dfs(now+1,id);
			vis[i]=0,d[now]=0;
		}
	}
}
int dx[10]={0,1,1,1,4,4,4,7,7,7};
int dy[10]={0,1,4,7,1,4,7,1,4,7};
bool check1(int x,int y,int val)
{
	for(int i=1;i<=9;i++)
	{
		if(i!=y&&c[x][i]==val) return 0;
	}
	for(int i=1;i<=9;i++)
	{
		if(i!=x&&c[i][y]==val) return 0;
	}
	return 1;
}
bool dfs1(int now)
{
	if(now==10)
	{
		for(int i=1;i<=9;i++)
		{
			for(int j=1;j<=9;j++)
			{
				printf("%d ",c[i][j]);
			}
			printf("\n");
		}
		return 1;
	}
	bool fl=0,fl1;
	for(int i=0;i<cnt[now];i++)
	{
		for(int j=1;j<=3;j++)
		{
			for(int k=1;k<=3;k++)
			{
				fl1=check1(j+dx[now]-1,k+dy[now]-1,v[now][i].a[j][k]);
				if(!fl1) break;
			}
			if(!fl1) break;
		}
		if(fl1)
		{
			for(int j=1;j<=3;j++)
			{
				for(int k=1;k<=3;k++)
				{
					c[j+dx[now]-1][k+dy[now]-1]=v[now][i].a[j][k];
				}
			}
			if(dfs1(now+1))
			{
				fl=1;
				break;
			}
		}
		for(int j=1;j<=3;j++)
		{
			for(int k=1;k<=3;k++)
			{
				c[j+dx[now]-1][k+dy[now]-1]=0;
			}
		}
	}
	return fl;
}
int pd[20]={0,1,0,1,0,1,1,0,1,0,1,1,0,1,0,1};
int main()
{
	for(int i=1;i<=15;i++)
	{
		string s;
		getline(cin,s);
		int fl=0;
		if(pd[i])
		{
			cnt1++;
			for(int j=1,k=0;j<=6,k<s.size();k++)
			{
				if(s[k]=='<') row[cnt1][j]=1,j++;
				if(s[k]=='>') j++;
			}
		}
		else
		{
			cnt2++;
			for(int j=1,k=0;j<=9,k<s.size();k++)
			{
				if(s[k]=='v') lin[cnt2][j]=1,j++;
				if(s[k]=='^') j++;
			}
		}
	}
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			c[i][j]=0;
		}
	}
	for(int i=1;i<=9;i++)
	{
		int x=(i-1)/3,y=(i-1)%3+1;
		int tx=3*x+1,ty=2*y-1;
		for(int j=tx;j<tx+3;j++)
		{
			for(int k=ty;k<ty+2;k++)
			{
				a[j-tx+1][k-ty+1]=row[j][k];
			}
		}
		tx=2*x+1,ty=3*y-2;
		for(int j=tx;j<tx+2;j++)
		{
			for(int k=ty;k<ty+3;k++)
			{
				b[j-tx+1][k-ty+1]=lin[j][k];
			}
		}
		dfs(1,i);
	}
	bool fl=dfs1(1);
	return 0;
}

Anya and Cubes

https://www.gxyzoj.com/d/hzoj/p/CF525E

折半搜索+抽象卡常

每个数有三种情况:原数,阶乘或不选,326,显然折半搜索

将和丢进map中,按阶乘的个数先分一下类,统计时加上所有i+xk的值

注意,直接加会T,统计之前加一个判断if(mp[i].count(m-sum)),不判断会增加很多无用信息

点击查看代码
#include<cstdio>
#include<map>
#define ll long long
using namespace std;
int n,k,a[30];
ll fac[25],m;
map<ll,int> mp[30];
int get(int x)
{
	int res=0;
	while(x)
	{
		if(x%3==2) res++;
		x/=3;
	}
	return res;
}
int qpow(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1) res*=x;
		x*=x;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d%lld",&n,&k,&m);
	fac[0]=1;
	for(int i=1;i<20;i++)
	{
		fac[i]=fac[i-1]*i;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	int tmp=n/2;
	int tmp1=qpow(3,tmp),tmp2=qpow(3,n-tmp);
	for(int s=0;s<tmp1;s++)
	{
		ll sum=0;
		int st=s;
		for(int i=1;i<=tmp;i++)
		{
			int x=st%3;
			if(x==1) sum+=a[i];
			if(x==2)
			{
				if(a[i]<19) sum+=fac[a[i]];
				else
				{
					sum=-1;
					break;
				}
			}
			st/=3;
		}
//		printf("%d %d\n",s,sum);
		if(sum!=-1&&sum<=m)
		mp[get(s)][sum]++;
	}
	ll ans=0;
	for(int s=0;s<tmp2;s++)
	{
		ll sum=0;
		int st=s;
		for(int i=tmp+1;i<=n;i++)
		{
			int x=st%3;
			if(x==1) sum+=a[i];
			if(x==2)
			{
				if(a[i]<19) sum+=fac[a[i]];
				else
				{
					sum=-1;
					break;
				}
			}
			st/=3;
		}
		if(sum!=-1&&sum<=m)
		{
			int x=k-get(s);
			for(int i=0;i<=x;i++)
			{
				if(mp[i].count(m-sum))
				{
					ans+=mp[i][m-sum];
				}
			}
		}
	}
	printf("%lld",ans);
	return 0;
}

Two Fairs

https://www.gxyzoj.com/d/hzoj/p/4363

因为要所有路径都经过a,b,那么从b出发,不经过a就走不到其中一个点,从a出发同理

所以从a开始bfs,且不将b入队,统计除b以外未经过的点数,b同理,不经a

最后两者相乘就是答案,注意long long

点击查看代码
#include<cstdio>
#include<queue>
using namespace std;
int n,m,T,a,b,head[200005],edgenum;
struct edge{
	int to,nxt;
}e[1000005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int vis[200005];
queue<int> q;
int bfs(int s,int t)
{
	for(int i=1;i<=n;i++) vis[i]=0;
	while(!q.empty()) q.pop();
	q.push(s);
	vis[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(vis[v]||v==t) continue;
			vis[v]=1;
			q.push(v);
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		if(!vis[i]&&i!=t) cnt++;
	}
	return cnt;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d%d",&n,&m,&a,&b);
		for(int i=1;i<=n;i++) head[i]=0;
		edgenum=0;
		for(int i=1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add_edge(u,v);
			add_edge(v,u);
		}
		printf("%lld\n",1ll*bfs(a,b)*bfs(b,a));
	}
	return 0;
}

Expression

https://www.gxyzoj.com/d/hzoj/p/4364

很有意思的一道题

因为每个数至多会加6位,所以肯定不能暴力搜索

可以考虑每一位的贡献,记x的个位为x,进位记为jw

则当a+b+c+jw0(mod10),则直接看下一位

否则分三种情况,即在a,b或c种加一位,使其满足条件

注意,当只有进位的时候,在c中加一个1即可,否则死循环

点击查看代码
#include<cstdio>
#include<iostream>
#include<string>
using namespace std;
string s;
int ta[30],tb[30],tc[30],minlen=1e9,ansa[30],ansb[30],ansc[30];
void dfs(int now,int a,int b,int c,int jw)
{
//	printf("%d %d %d %d %d\n",now,a,b,c,jw);
	if(!a&&!b&&!c&&!jw)
	{
//		printf("1");
		int cnt=0;
		for(int i=29;i>0;i--)
		{
			if(ta[i]!=0)
			{
				cnt+=i;
				break;
			}
		}
		for(int i=29;i>0;i--)
		{
			if(tb[i]!=0)
			{
				cnt+=i;
				break;
			}
		}
		for(int i=29;i>0;i--)
		{
			if(tc[i]!=0)
			{
				cnt+=i;
				break;
			}
		}
//		printf("%d %d\n",cnt,minlen);
		if(cnt<minlen)
		{
			minlen=cnt;
			for(int i=1;i<30;i++)
			{
				ansa[i]=ta[i],ansb[i]=tb[i],ansc[i]=tc[i];
			}
		}
		return;
	}
	int da=a%10,db=b%10,dc=c%10;
	if(!a&&!b&&!c&&jw)
	{
		tc[now]=jw;
		dfs(now+1,0,0,0,0);
	}
	else if((jw+da+db-dc)%10==0)
	{
		ta[now]=da,tb[now]=db,tc[now]=dc;
		dfs(now+1,a/10,b/10,c/10,(jw+da+db-dc)/10);
	}
	else
	{
		if(a||b)
		{
			ta[now]=da,tb[now]=db,tc[now]=(jw+da+db)%10;
			dfs(now+1,a/10,b/10,c,(jw+da+db)/10);
		}
		if(dc>=db+jw&&(c||b))
		{
			ta[now]=dc-db-jw,tb[now]=db,tc[now]=dc;
			dfs(now+1,a,b/10,c/10,0);
		}
		else if(c||b)
		{
			ta[now]=dc+10-db-jw,tb[now]=db,tc[now]=dc;
			dfs(now+1,a,b/10,c/10,1);
		}
		if(dc>=da+jw&&(c||a))
		{
			ta[now]=da,tb[now]=dc-da-jw,tc[now]=dc;
			dfs(now+1,a/10,b,c/10,0);
		}
		else if(c||a)
		{
			ta[now]=da,tb[now]=dc-da-jw+10,tc[now]=dc;
			dfs(now+1,a/10,b,c/10,1);
		}
	}
	ta[now]=tb[now]=tc[now]=0;
}
int a,b,c;
int main()
{
	cin>>s;
	int fl=0;
	for(int i=0;i<s.size();i++)
	{
		if(s[i]=='+') fl=1;
		else if(s[i]=='=') fl=2;
		else
		{
			if(fl==0) a=a*10+s[i]-'0';
			if(fl==1) b=b*10+s[i]-'0';
			if(fl==2) c=c*10+s[i]-'0';
		}
	}
	dfs(1,a,b,c,0);
	fl=0;
	for(int i=29;i>0;i--)
	{
		if(ansa[i]!=0||fl)
		{
			printf("%d",ansa[i]);
			fl=1;
		}
	}
	fl=0;
	printf("+");
	for(int i=29;i>0;i--)
	{
		if(ansb[i]!=0||fl)
		{
			printf("%d",ansb[i]);
			fl=1;
		}
	}
	printf("=");
	fl=0;
	for(int i=29;i>0;i--)
	{
		if(ansc[i]!=0||fl)
		{
			printf("%d",ansc[i]);
			fl=1;
		}
	}
	return 0;
}

Distinct Paths

https://www.gxyzoj.com/d/hzoj/p/CF293B

可以发现,当n+m>k时,一定无解,所以最坏情况下,只需要搜30个格子

但是这样暴力依然会T,考虑剪枝:

  1. 如果当前到终点的距离小于可支配的颜色数,直接无解

  2. 如果一些转台时等价的,可以只搜其中一个

关于什么时等价状态,记cnti表示i用了cnti

可以先统计固定的颜色的数量,然后在dfs过程中,如果当前点有k个cnt值为0,则它们是等价的

点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
const int mod=1e9+7;
int n,m,k,f[15][15],g[1025],a[15][15];
int cnt[20];
int get(int x)
{
	int res=0;
	while(x)
	{
		if(x&1) res++;
		x>>=1;
	}
	return res;
}
ll dfs(int now)
{
	if(now==n*m+1)
	{
		return 1;
	}
	int x=(now-1)/m+1,y=(now-1)%m+1;
//	printf("%d %d %d\n",a[x][y],x,y);
	int s=f[x][y-1]|f[x-1][y];
//	printf("%d %d\n",now,s);
	int st=((1<<k)-1)^s;
	ll res=0,tmp=-1;
	if(g[st]+x+y<=n+m) return 0;
//	printf("%d ",now);
	for(int i=1;i<=k;i++)
	{
		if(s&(1<<(i-1))) continue;
		if(!a[x][y]||a[x][y]==i)
		{
//			printf("%d %d\n",now,i);
			f[x][y]=s|(1<<(i-1));
			cnt[i]++;
			if(cnt[i]==1)
			{
				if(tmp==-1)
				{
					tmp=dfs(now+1);
				}
				res+=tmp;
			}
			else res+=dfs(now+1);
			res%=mod;
			cnt[i]--;
		}
	}
//	printf("%d %d\n",now,res);
	return res;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	if(n+m-1>k)
	{
		printf("0");
		return 0;
	}
	for(int i=1;i<(1<<k);i++)
	{
		g[i]=get(i);
//		printf("%d ",g[i]);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			cnt[a[i][j]]++;
		}
	}
	ll ans=dfs(1);
	printf("%d",ans);
	return 0;
}

Bear and Square Grid

https://www.gxyzoj.com/d/hzoj/p/4366

显然,如果去掉一个方阵,那么与其中的X相邻的联通块就会合并

所以最简单的,枚举每个方框的位置,然后暴力看有多少X并将与这些X相邻的联通块的大小相加

但是这样并不优,因为每次的枚举和重复运算很多

可以先计算开始行在同一行的第一个,然后直接在这个基础上操作

对于每个联通块,统计当前范围共有多少面与他相邻,记为cnt,如:

...
.X.

假设此时在(2,2),则应+3

如果在添加点a前,联通块b的相邻数cntb为0,则sum=sum+sizb

同理,减去后为0,则sum=sumsizb

每一次加上第i列,减去第i-m列即可,记得还要加上X的个数

ans就是所有sum的最大值,预处理所有联通块即可

点击查看代码
#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,a[505][505],id[505][505],tot,siz[250004];
struct node{
	int x,y;
};
queue<node> q;
int dx[4]={-1,0,0,1};
int dy[4]={0,-1,1,0};
void bfs(int x,int y)
{
	tot++;
	q.push((node){x,y});
	id[x][y]=tot;
	int sum=1;
	while(!q.empty())
	{
		node u=q.front();
		q.pop();
		for(int i=0;i<4;i++)
		{
			int tx=u.x+dx[i],ty=u.y+dy[i];
			if(!tx||!ty||tx>n||ty>n) continue;
			if(a[tx][ty]||id[tx][ty]) continue;
			id[tx][ty]=tot,sum++;
			q.push((node){tx,ty});
		}
	}
	siz[tot]=sum;
}
int cnt[250005];
int main()
{
	scanf("%d%d",&n,&m);
	bool fl=0;
	for(int i=1;i<=n;i++)
	{
		string s;
		cin>>s;
		for(int j=1;j<=n;j++)
		{
			if(s[j-1]=='X') a[i][j]=1,fl=1;
		}
	}
	if(!fl)
	{
		printf("%d",n*n);
		return 0;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(!a[i][j]&&!id[i][j])
			{
				bfs(i,j);
//				printf("%d %d %d\n",i,j,siz[tot]);
			}
//			printf("%d ",id[i][j]);
		}
//		printf("\n");
	}
	int ans=0;
	for(int i=1;i<=n-m+1;i++)
	{
		for(int j=1;j<=tot;j++) cnt[j]=0;
		int sum=0;
		for(int j=i;j<=i+m-1;j++)
		{
			for(int k=1;k<=m;k++)
			{
				if(!a[j][k]) continue;
				sum++; 
				for(int t=0;t<4;t++)
				{
					int tx=j+dx[t],ty=k+dy[t];
					if(!tx||!ty||tx>n||ty>n) continue;
					if(id[tx][ty])
					{
						cnt[id[tx][ty]]++;
						if(cnt[id[tx][ty]]==1)
						{
							sum+=siz[id[tx][ty]];
						}
					}
				}
			}
		}
		ans=max(ans,sum);
//		printf("%d %d\n",i,sum);
		for(int k=1+m;k<=n;k++)
		{
			for(int j=i;j<=i+m-1;j++)
			{
				if(!a[j][k-m]) continue;
				sum--; 
				for(int t=0;t<4;t++)
				{
					int tx=j+dx[t],ty=k-m+dy[t];
					if(!tx||!ty||tx>n||ty>n) continue;
					if(id[tx][ty])
					{
//						if(i==3)
//						printf("%d %d %d\n",k,tx,ty);
						cnt[id[tx][ty]]--;
						if(!cnt[id[tx][ty]])
						sum-=siz[id[tx][ty]];
					}
				}
			}
			for(int j=i;j<=i+m-1;j++)
			{
				if(!a[j][k]) continue;
				sum++; 
				for(int t=0;t<4;t++)
				{
					int tx=j+dx[t],ty=k+dy[t];
					if(!tx||!ty||tx>n||ty>n) continue;
					if(id[tx][ty])
					{
						cnt[id[tx][ty]]++;
						if(cnt[id[tx][ty]]==1)
						sum+=siz[id[tx][ty]];
					}
				}
			}
			ans=max(ans,sum);
//			printf("%d %d %d\n",i,k,sum);
		}
	}
	printf("%d",ans);
	return 0;
}

[SDOI2011] 迷宫探险

https://www.gxyzoj.com/d/hzoj/p/P783

抽象题

先考虑要记录什么信息,首先是位置和血量,因为每个陷阱的状态走过后是已知,因此还要统计所有陷阱是未知/有害/无害

因为是按照最优策略,考虑dp

但是根据题意,可以向4个方向走,那么有可能成环,为保证状态的单向改变,每次尽量找有害或未知的格子走

而当当前格子可以不经过任何有害或未知格子到达一个终点时,直接返回1

接下来看有害或未知格子如何搜

如果要走有害的格子,则血量必须大于1,此时走它必然扣血

如果是未知格子,如果血量大于1,则有害无害都能走,所以按照概率计算即可

如果等于1,就只能走无害,此时,只能用这个点是无害的概率与对应结果的概率之积

而对于状态为s,i无害的概率,可以预处理完成

注意,因为输入的只有当前一定有害或无害,所以要递归出部分未知时的概率

点击查看代码
#include<cstdio>
#include<string>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n,m,k,h,a[35][35],sx,sy,p1[10],s[40];
int p[500],st1;
bool pd(int x)
{
	int res=0;
	while(x)
	{
		if(!(x%3)) return 0;
		x/=3,res++;
	}
	if(res<k) return 0;
	return 1;
}
int get(int x,int y)
{
	if(p[x]>=0) return p[x];
	for(int i=y;;i++)
	{
		if((x/p1[i])%3) continue;
		p[x]=get(x+p1[i],i+1)+get(x+p1[i]*2,i+1);
		return p[x];
	}
}
double f[35][35][250][6],p2[35][250];
bool vis[35][35][250][6],v1[35][35][250];
struct node{
	int x,y;
};
vector<node> v[35][35][250];
int dx[4]={-1,0,0,1};
int dy[4]={0,-1,1,0};
int tag[35][35],tot;
inline void dfs2(int x,int y)
{
	for(int i=0;i<4;i++)
	{
		int tx=x+dx[i],ty=y+dy[i];
		if(tx&&ty&&tx<=n&&ty<=m&&tag[tx][ty]!=tot)
		{
			tag[tx][ty]=tot;
			if(a[tx][ty]==-2)
			{
				dfs2(tx,ty);
			}
			else if(a[tx][ty]==-3)
			{
				for(int j=1;j<=h;j++)
				{
					vis[x][y][st1][j]=vis[sx][sy][st1][j]=1;
					f[x][y][st1][j]=f[sx][sy][st1][j]=1;
				}
				return;
			}
			else
			{
				if(a[tx][ty]>=0&&a[tx][ty]<k)
				{
					if((st1/p1[a[tx][ty]])%3==1)
					{
						dfs2(tx,ty);
					}
					else
					{
						v[sx][sy][st1].push_back((node){tx,ty});
					}
				}
			}
		}
		if(vis[sx][sy][st1][1])
		{
			for(int j=1;j<=h;j++)
			{
				vis[x][y][st1][j]=1;
				f[x][y][st1][j]=1;
			}
			return;
		}
	}
}
inline double dfs(int x,int y,int st,int h1)
{
	if(vis[x][y][st][h1])
	{
		return f[x][y][st][h1];
	}
//	printf("%d %d %d %d\n",x,y,st,h1);
	if(!v1[x][y][st])
	{
		v1[x][y][st]=1;
		sx=x,sy=y,st1=st;
		tag[x][y]=++tot;
		dfs2(x,y);
		if(vis[x][y][st][h1]) return 1;
	}
	vis[x][y][st][h1]=1;
	double res=0;
	for(int i=0;i<v[x][y][st].size();i++)
	{
		int tx=v[x][y][st][i].x,ty=v[x][y][st][i].y;
		int col=a[tx][ty];
		if((st/p1[col])%3==2)
		{
			if(h1>1)
			{
				res=max(res,dfs(tx,ty,st,h1-1));
			}
		}
		else
		{
			if(h1==1)
			{
				res=max(res,dfs(tx,ty,st+p1[col],h1)*p2[st][col]);
			}
			else
			{
				res=max(res,dfs(tx,ty,st+p1[col],h1)*p2[st][col]+dfs(tx,ty,st+p1[col]*2,h1-1)*(1-p2[st][col]));
			}
		}
		if(res==1)
		{
			f[x][y][st][h1]=1;
			return 1;
		}
	}
	return f[x][y][st][h1]=res;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&h);
	p1[0]=1;
	for(int i=1;i<=k;i++)
	{
		p1[i]=p1[i-1]*3;
	}
	for(int i=1;i<=n;i++)
	{
		string s1;
		cin>>s1;
		for(int j=1;j<=m;j++)
		{
			if(s1[j-1]=='#') a[i][j]=-1;
			else if(s1[j-1]=='.') a[i][j]=-2;
			else if(s1[j-1]=='@') a[i][j]=-3;
			else if(s1[j-1]=='$')
			{
				a[i][j]=-2,sx=i,sy=j;
			}
			else a[i][j]=s1[j-1]-'A';
		}
	}
	int cnts=0;
	for(int i=0;i<p1[k];i++)
	{
		p[i]=-1;
		if(pd(i)) s[++cnts]=i;
	}
//	printf("%d ",cnts);
	for(int i=1;i<=cnts;i++)
	{
		scanf("%d",&p[s[i]]);
	}
	for(int i=0;i<p1[k];i+=3)
	{
		int tmp=get(i,0);
	}
	for(int i=0;i<p1[k];i++)
	{
		for(int j=0;j<k;j++)
		{
			if(!((i/p1[j])%3))
			p2[i][j]=p[i+p1[j]]*1.0/((p[i+p1[j]]+p[i+p1[j]*2])*1.0);
		}
	}
//	for(int i=0;i<p1[k];i++)
//	{
//		printf("%d ",p[i]);
//	}
//	printf("1");
	printf("%.3lf",dfs(sx,sy,0,h));
	return 0;
}

Lizard Era: Beginning

https://www.gxyzoj.com/d/hzoj/p/CF585D

因为每一位三种状态,25位,共325

显然折半搜索,0表示LM,1表示LW,2表示MW

假设前半段sumlsumm=x,则后半段summsuml=x则L和M就相等,其他两组同理

由等量代换,维护两组差分,就能保证相等

因为所有输入的绝对值107,所以最终差分值必然小于109

所以可以将差分值压缩成一个long long然后扔进map即可

注意,在前半段往map扔时,若压缩值相等,选L大的

点击查看代码
#include<cstdio>
#include<map>
#define ll long long
using namespace std;
int n,a[30],b[30],c[30],fl,x,tmp1,tmp2;
ll p=1e9;
struct node{
	int st,val;
};
map<ll,node> mp;
int qpow(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1) res=res*x;
		x*=x;
		y>>=1;
	}
	return res;
}
void print(int s1,int s2)
{
	fl=1;
	for(int i=1;i<=x;i++)
	{
		if(s1%3==0) printf("LM\n");
		if(s1%3==1) printf("LW\n");
		if(s1%3==2) printf("MW\n");
		s1/=3;
	}
	for(int i=x+1;i<=n;i++)
	{
		if(s2%3==0) printf("LM\n");
		if(s2%3==1) printf("LW\n");
		if(s2%3==2) printf("MW\n");
		s2/=3;
	}
}
int mx=-1e9,s1,s2;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a[i],&b[i],&c[i]);
	}
	x=n/2;
	tmp1=qpow(3,x),tmp2=qpow(3,n-x);
	for(int s=0;s<tmp1;s++)
	{
		int l=0,m=0,w=0,st=s;
		for(int i=1;i<=x;i++)
		{
			if(st%3==0) l+=a[i],m+=b[i];
			if(st%3==1) l+=a[i],w+=c[i];
			if(st%3==2) m+=b[i],w+=c[i];
			st/=3;
		}
		ll sta=(l-m)*p+(l-w);
//		printf("%d %d %d %d %lld\n",s,l,m,w,sta);
		if((mp.count(sta)&&mp[sta].val<l)||!mp.count(sta))
		{
			mp[sta]=(node){s,l};
		}
	}
	for(int s=0;s<tmp2;s++)
	{
		int l=0,m=0,w=0,st=s;
		for(int i=x+1;i<=n;i++)
		{
			if(st%3==0) l+=a[i],m+=b[i];
			if(st%3==1) l+=a[i],w+=c[i];
			if(st%3==2) m+=b[i],w+=c[i];
			st/=3;
		}
		ll sta=(m-l)*p+(w-l);
//		printf("%d %d %d %d %lld\n",s,l,m,w,sta);
		if(mp.count(sta))
		{
			int tmp=l+mp[sta].val;
			if(tmp>mx)
			{
				mx=tmp,s1=mp[sta].st,s2=s;
			}
			fl=1;
		}
	}
	if(!fl) printf("Impossible");
	else print(s1,s2);
	return 0;
}

Make Them Similar

https://www.gxyzoj.com/d/hzoj/p/4371

2301显然不能直接枚举,可以从215处折半,用vector储存相邻两数前/后15位异或x后1个数的差

一个储存前-后,另一个储存后-前,相同为相等则满足条件

点击查看代码
#include<cstdio>
#include<map>
#include<vector>
using namespace std;
int n,a[105],tmp=1<<15;
int get(int x)
{
	int res=0;
	while(x)
	{
		if(x&1) res++;
		x>>=1;
	}
	return res;
}
map<vector<int>,int> mp;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int s=0;s<tmp;s++)
	{
		vector<int> x;
		x.clear();
		for(int i=2;i<=n;i++)
		{
			int t1=get((a[i]&(tmp-1))^s);
			int t2=get((a[i-1]&(tmp-1))^s);
			x.push_back(t2-t1);
		}
		if(!mp.count(x)) mp[x]=s;
	}
	for(int s=0;s<tmp;s++)
	{
		vector<int> x;
		x.clear();
		for(int i=2;i<=n;i++)
		{
			int t1=get((a[i]&((1<<30)-tmp))^(s<<15));
			int t2=get((a[i-1]&((1<<30)-tmp))^(s<<15));
			x.push_back(t1-t2);
		}
		if(mp.count(x))
		{
			printf("%d\n",(s<<15)+mp[x]);
			return 0;
		}
	}
	printf("-1");
	return 0;
}

Prime Gift

16个数,每个数约15种情况,显然折半搜索,将所有的数分成两段,储存所有可以组成的数

分别排序后,二分求第k个即可,具体就是二分值域,统计有多少个乘积小于等于mid

但是还没有结束,如果你是从中间断开,则CF会给你一个TLE

别问我是怎么知道的

因为小的数情况多,大的情况少,check的复杂度和两个dfs的复杂度的主要组成都是两组的数量之和

因为整个序列能组成的数的数量一定,由均值不等式,差越小越优

所以可以按模2的余数去选,减少两组的数量之和

点击查看代码
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,a[20],a1[10],a2[10],n1,n2,cnt1,cnt2;
ll p1[10][100],p2[10][100];
ll k,p=1e18,v1[7400005],v2[7400005];
void dfs1(int now,ll val)
{
	if(now==n1+1)
	{
		v1[++cnt1]=val;
		return;
	}
	dfs1(now+1,val);
	for(int i=1;;i++)
	{
		if(p/p1[now][i]<val) break;
		dfs1(now+1,val*p1[now][i]);
	}
}
void dfs2(int now,ll val)
{
	if(now==n2+1)
	{
		v2[++cnt2]=val;
		return;
	}
	dfs2(now+1,val);
	for(int i=1;;i++)
	{
		if(p/p2[now][i]<val) break;
		dfs2(now+1,val*p2[now][i]);
	}
}
bool check(ll x)
{
	ll cnt=0;
	for(int i=1,j=cnt2;i<=cnt1&&j>0;i++)
	{
		if(v1[i]>x) break;
		while(v1[i]>x/v2[j])
		{
			j--;
		}
		cnt+=j;
	}
//	printf("%lld %lld\n",x,cnt);
	if(cnt>=k) return 1;
	return 0;
}
int main()
{
	scanf("%d",&n);
	n1=(n+1)/2,n2=n-n1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if(i%2) a1[(i+1)/2]=a[i];
		else a2[i/2]=a[i];
	}
	for(int i=1;i<=n1;i++)
	{
		p1[i][0]=1;
		for(int j=1;;j++)
		{
			if(p/a1[i]<p1[i][j-1])
			{
				p1[i][j]=p+1;
				break;
			}
			else p1[i][j]=p1[i][j-1]*a1[i];
		}
	}
	for(int i=1;i<=n2;i++)
	{
		p2[i][0]=1;
		for(int j=1;;j++)
		{
			if(p/a2[i]<p2[i][j-1])
			{
				p2[i][j]=p+1;
				break;
			}
			else p2[i][j]=p2[i][j-1]*a2[i];
		}
	}
	scanf("%lld",&k);
	dfs1(1,1);
	dfs2(1,1);
	sort(v1+1,v1+cnt1+1);
	sort(v2+1,v2+cnt2+1);
//	printf("%d %d\n",cnt1,cnt2);
	ll l=1,r=p;
	while(l<r)
	{
		ll mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%lld",l);
	return 0;
}
posted @   wangsiqi2010916  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示