搜索与搜索剪枝5

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

[SCOI2010]游戏

lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示。当他使用某种装备时,他只能使用该装备的某一个属性。并且每种装备最多只能使用一次。

游戏进行到最后,lxhgww遇到了终极boss,这个终极boss很奇怪,攻击他的装备所使用的属性值必须从1开始连续递增地攻击,才能对boss产生伤害。也就是说一开始的时候,lxhgww只能使用某个属性值为1的装备攻击boss,然后只能使用某个属性值为2的装备攻击boss,然后只能使用某个属性值为3的装备攻击boss……以此类推。现在lxhgww想知道他最多能连续攻击boss多少次?

可以将所有的属性当做点,所有的武器当做边,例如武器的属性分别为:1 3/ 2 4/ 3 5,则维护出来一个图,其中1 3 5连通,2 4连通。我们可以发现这样的规律:当选了一条边的其中一点,则另一点就不能通过这一条边选了。所以像是下图的情况,每一个连通块中只能选(各自的点数-1)个属性,而且这些属性还必须是连续的。所以思路就是先维护连通块,如果维护出来后有回路,那么说明其中的点都取到,否则不能,更新答案为更小的点的编号。比如下图,1 3 5更新得到的点为5,后2 4得到的点为4,则答案更新为4-1=3(为什么得到的数要比答案大一位:因为在更新的时候,无须顾忌节点大小,只要是当前遍历得到的最大值即可,减少代码量和出错率).

 具体实现方面,可以用搜索的方式从第一个没有被访问过的点开始访问,访问每个连通块中每一条边,就可以知道是不是可以全部选中其中的点了(当边数>=点数即可全部访问)。

细节问题:由于数据较大,空间可能不够开,这时vector就上场了。向量数组真香。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int inf = 0x3f3f3f, maxn=10010;
int n,m,maxx,vis[maxn];
vector<int>ed[maxn];
int dfs(int x,int fa){
	vis[x]=1;
	int flag=0;
	maxx=max(maxx,x);
	for(int i=0;i<ed[x].size();++i){
		int y=ed[x][i];
		if(fa==y) continue;
		if(vis[y]) {
			flag=1; continue;
		}
		if(dfs(y,x)) flag=1;
	}
	return flag;
}
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		ed[x].push_back(y); ed[y].push_back(x); 
		m=max(m,max(x,y));
	}
	memset(vis,0,sizeof(vis));
	int ans=m+1;
	for(int i=1;i<=m;++i){
		maxx=0;
		if(!vis[i]&&!dfs(i,0))
			ans=min(ans,maxx);
	} 
	printf("%d",ans-1);
	return 0;
}

 另外,用并查集也可以达到目的。只需要把需要的信息存在根节点即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<deque>//
#include<stack>
#include<vector>
#include<cstring>
using namespace std;
const int inf = 0x3f3f3f, maxn=10010;
int n,m,maxx[maxn],fa[maxn],b[maxn];
int find(int x){
	if(x==fa[x]) return x;
	return fa[x]=find(fa[x]);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=10000;++i)
		fa[i]=i, maxx[i]=i;
	memset(b,0,izeof(b));
	for(int i=0;i<n;++i){
		int x,y;
		scanf("%d%d",&x,&y); 
		m=max(m,max(x,y));
		int fx=find(x), fy=find(y);
		if(fx!=fy){
			fa[fx]=fy;
			maxx[fy]=max(maxx[fy],maxx[fx]);
			b[fy]=(b[fx]||b[fy]);
		}else{
			b[fy]=1;
		}
	}
	ans=10001;
	for(int i=1;i<=10000;++i){
		if(fa[i]==i){
			if(b[i]==0)
				ans=min(ans,maxx[i]);
		}
	}
	printf("%d",ans-1)
	return 0;
}

例7 1903 -- Jurassic Remains

从n(n<=30)个字符串中选取最多的串使得他们中每个字母出现的次数都是偶数次

这里加强了一波数据,如果每一个字符串都判断一波能不能选,则有2^30≈10^10,肯定不行。我们联想起之前先将数据分组处理,最后进行合并,这样直接变为两个2^15的时间,立刻化腐朽为神奇。不过光想很简单,实现就不好说了,此处借鉴大佬题解分析一波。POJ 1903 - Jurassic Remains 中途相遇法(枚举)_kk303的博客-CSDN博客

接下来就是强行把自己讲懂的showtime!一开始我们想,对头搜索不就是之前做过的八数码吗?者之间可能有什么巧妙的联系。没错,正是map的媒人作用!map通过它寄存数据的强大功能,可通过方便地查询其内部信息而成功让男女嘉宾牵手(bushi)。那么,如何让两头知道对方就是自己要找的那一个呢?这又是一个找内部关系的关键操作。奇偶性这种东西,完全可以抽象为二进制码呀,就是让每一位二进制码对应一个字母出现的是奇数次(二进制为1)还是偶数次(二进制为0),而这通过异或+移位操作就可以实现,要让所有字母出现偶数次,不就相当于奇偶性要一致吗?翻译成二进制就是,他们的二进制码要相同。综上所述,我们需要一个map记录每一个二进制所对应的选中的字符串个数,啊对了,最后还要输出选了哪几个来着,也就是说还要记录选中的字符串编号。那就用一个结构体封装一下吧QAQ

细节问题:注意,搜索公共部分的数据处理一定要放在所有代码之前!QAQ

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map> 
#include<vector>
#include<cstring>
using namespace std;
const int inf = 0x3f3f3f, maxn=10010;
int n;
string s[35];
struct node{
	int w[35], num;
	// w记录加入了那些字符串,num记录倒数第一个字符串是第几个字符串 
}ans;
map<int,node>mp;
void dfs1(int i,int x,node h){
// i:指向了第几个输入的字符串,x:目前各个字符的奇偶性(用 二进制 表示),h:当前节点中字符串信息 
	if(mp.find(x)==mp.end()||mp[x].num<h.num) mp[x]=h;// 
	if(i>n/2) return ;
	dfs1(i+1,x,h);
	for(int j=0;j<s[i].size();++j)
		x^=1<<(s[i][j]-'A');
	h.w[++h.num]=i;
	dfs1(i+1,x,h);
}
void dfs2(int i,int x,node h){
	if(mp.find(x)!=mp.end()&&mp[x].num+h.num>ans.num){// 
		ans=mp[x];
		for(int j=1;j<=h.num;++j)
			ans.w[++ans.num]=h.w[j];
	}
	if(i>n) return ;
	dfs2(i+1,x,h);
	h.w[++h.num]=i;
	for(int j=0;j<s[i].size();++j)
		x^=1<<(s[i][j]-'A');
	dfs2(i+1,x,h);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		cin>>s[i];
	node h;
	h.num=0;//指向w的末尾,即存放了多少字符串 
	dfs1(1,0,h); //第一次深搜 
	ans.num=0;
	dfs2(n/2+1,0,h);
	printf("%d\n",ans.num);
	for(int i=1;i<=ans.num;++i)
		printf("%d ",ans.w[i]);
	return 0;
}

Sramoc

Sramoc(K,M)表示用数字0 1 2……K-1组成的自然数中能被M整除的最小数。给定K,M,求Sramoc(K,M)。

如K=2,M=7时,Sramoc(K,M)=1001.

看到最小解,首先想到广搜,一位位生成直到找到目标值。但有可能在过程中生成一个位数极大的数,导致TLE。可能数极大导致longlong都存不下,但我们可以利用模的性质进行优化:(a+b)%p=(a%p+b%p)%p,若x%m=y,则(x*10+a)%m=(y*10+a)%m,只对余数进行操作就可以保证不会爆空间。另外,要知道最小的数肯定是从最短的数中生成出来的,所以只需要每个余数只需要保留一个数即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map> 
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
const int inf = 0x3f3f3f, maxn=10010;
int k,m;
struct ty{
	string s;
	int yu;
};
queue<ty> q;
int vis[10010];
string bfs(string s){
	ty tmp;
	tmp.s=s; tmp.yu=1;
	vis[1]=1;
	q.push(tmp);
	while(!q.empty()){
		tmp=q.front(); 
		if(tmp.yu==0) return tmp.s;
		q.pop();
		for(int i=0;i<k;++i){
			if(!vis[(tmp.yu*10+i)%m]){
				ty cur=tmp;
				cur.s+=i+'0';
				cur.yu=(tmp.yu*10+i)%m;
				vis[cur.yu]=1;
				q.push(cur);
			}
		}
		
	}
}
int main(){
	scanf("%d%d",&k,&m);
	cout<<bfs("1");
	return 0;
}

[CQOI2007]矩形 

给一个a*b矩形,由a*b个单位正方形组成。你需要沿着网格线把它分成非空的两部分,每部分所有格子连通,且至少有一个格子在原矩形的边界上。“连通”是指任两个格子都可以通过水平或者竖直路径连在一起。 求方案总数。例如3*2的矩形有15种方案。

只要算进去一次再出来一次的位数即可,注意,剪枝的点在于不能重复搜索线段上的某个点,只需要用一个标记数组即可。还有就是切入点的设置,只是把它标记一下,就像警告不许和这个走过的点重合是一样的。难道不会找重吗?实际上,不同的下刀点就代表了一种不同的方案,所以不用担心。

有一个不用memset的小技巧,就是每次标记不同的数字。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<map>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=10;
int n,m,f[maxn][maxn],ans,num;
ll a[5]= {0,1,0,-1,0};
ll b[5]= {0,0,1,0,-1};
void dfs(int x,int y){
	if(x==0||x==n||y==0||y==m) {
		ans++; return ;
	}
	for(int i=1;i<=4;++i){
		int xx=x+a[i],yy=y+b[i];
		if(f[xx][yy]==num) continue;
		f[xx][yy]=num;
		dfs(xx,yy);
		f[xx][yy]=0;
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<n;++i){//hang
		num++;
		f[i][0]=num;
		f[i][1]=num;
		dfs(i,1);
	}
	for(int i=1;i<m;++i){
		num++;
		f[0][i]=num;
		f[1][i]=num;
		dfs(1,i);
	}
	cout<<ans;
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

[SCOI2009]生日快乐

windy的生日到了,为了庆祝生日,他的朋友们帮他买了一个边长分别为 X 和 Y 的矩形蛋糕。现在包括windy ,一共有 N 个人来分这块大蛋糕,要求每个人必须获得相同面积的蛋糕。windy主刀,每一切只能平行于一块蛋糕 的一边(任意一边),并且必须把这块蛋糕切成两块。这样,要切成 N 块蛋糕,windy必须切 N-1 次。

为了使得每块蛋糕看起来漂亮,我们要求 N块蛋糕的长边与短边的比值的最大值最小。你能帮助windy求出这个比值么?

我承认自己傻,所以这种板子当然要背背才行!QAQ就是这种将限制条件作为形参传递,将答案作为返回值,中间还有先递归找出答案最后递归结束再汇总答案的这种板子!

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<map>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=10;

double dfs(double x,double y,int n){
	if(n==1) 
		return max(x,y)/min(x,y);
	double a=x/n, b=y/n,ans=1e9;
	for(int i=n>>1;i;--i){
		double h=max(dfs(a*i,y,i),dfs(x-a*i,y,n-i));
		double w=max(dfs(x,b*i,i),dfs(x,y-b*i,n-i));
		ans=min(ans,min(h,w));
	}
	return ans;
}
int main(){
	double x,y;
	int n;
	cin>>x>>y>>n;
	printf("%.6lf",dfs(x,y,n));
	return 0;
} 

登录—专业IT笔试面试备考平台_牛客网

Tree Decoration

农夫约翰正在装饰他的春分树(就像一棵圣诞树,但大约三个月后很受欢迎)。它可以建模为具有 N (1 <= N <= 100,000) 个元素的有根数学树,标记为 1...N,元素 1 作为树的根。每个树元素 e > 1 都有一个父元素 P_eP e ​ (1 <= P_eP e ​ <= N)。当然,元素 1 没有父元素(在输入中表示为“-1”),因为它是树的根。每个元素 i 都有一个对应的子树(可能大小为 1)植根于那里。FJ 想确保元素 i 对应的子树总共有至少 C_iC i (0 <= C_iC i ​ <= 10,000,000)个点缀在其成员中。他还想最小化他放置所有装饰品的总时间(在元素 i (1 <= T_iT i ​ <= 100) 处放置 K 个装饰品需要时间 K*T_iT i ​)。帮助 FJ 确定放置满足约束条件的装饰品所需的最短时间。请注意,此答案可能不适合 32 位整数,但适合带符号的 64 位整数。

一道不错的递归水题。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map> 
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=100010;
struct nd{
	ll p,c,t;
}e[maxn];
vector<int> v[maxn];
int n,p;
ll dfs(int x,int need){//节点编号,节点及其子树需要的装饰数量 
	ll ans=0;			//时间为0 
	if(v[x].size()==0){	//若为叶子结点 
		ans+=e[x].t*need;	//时间为花费*需要数 
		return ans;
	}
	ll minn=e[x].t,maxx=0;	//找到的最小花费,子树的装饰数量 
	for(int i=0;i<v[x].size();++i){	//找子树 
		int son=v[x][i];
		ans+=dfs(son, e[son].c);
		maxx+=e[son].c;
		minn=min(minn,e[son].t);
	}
	if(need>maxx){
		ans+=(need-maxx)*minn;
	}else{
		e[x].c=maxx;
	}
	e[x].t=minn;
	return ans;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>e[i].p>>e[i].c>>e[i].t;
		if(e[i].p==-1) p=i;
		else v[e[i].p].push_back(i);
	}
	cout<<dfs(p,e[p].c);//从根开始(根节点编号,根节点子树所需要的数量 
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

[NOIP2010]关押罪犯

S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c 的冲突事件。每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

这里的思路同食物链,用并查集即可维护。还用一种方法叫“敌人的敌人是朋友”,即使他们真的会打架,但由于产生冲突较小被安排到一起也算是相对而言的朋友了QAQ

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map> 
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=100005, num=20005;
struct nd{
	int a,b,c;
}e[maxn];
int n,m,fa[num<<1];
int find(int x){
	while(x!=fa[x]) x=fa[x]=fa[fa[x]];
	return x;
}
void merge(int x,int y){
	fa[find(x)]=find(y);
}
bool cmp(nd x,nd y){
	return x.c>y.c;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n*2;++i)
		fa[i]=i;
	for(int i=0;i<m;++i)
		scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
	sort(e,e+m,cmp);
	for(int i=0;i<m;++i){
		if(find(e[i].a)==find(e[i].b)||find(e[i].a+n)==find(e[i].b+n)){
			cout<<e[i].c;
			return 0;
		}else{
			merge(e[i].a,e[i].b+n);
			merge(e[i].a+n,e[i].b);
		}
	}
    cout<<0;
	return 0;
}

思路二:染色法判断二分图。思路就是将你要分开的两个点染上不同的颜色,如果在此过程中发现原本这个点要染成白的但它却在之前被染成了黑的,那就说明不能把这个点成功与之分离,要再次调整答案。

细节问题:提交之前一定要检查好数组大小与数据范围!头一次因为数组开小而TLE QAQ

#include<iostream>
#include<algorithm>
#include<queue>
#include<string>
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#define ll long long
using namespace std;
const int maxn=200005,num=20005;
int n,m,cnt;
int e[maxn],w[maxn],ne[maxn],h[maxn],color[num];
void add(int u,int v,int c) {
	ne[++cnt]=h[u];
	w[cnt]=c;
	e[cnt]=v;
	h[u]=cnt;
}
int dfs(int u,int c,int limit) {
	color[u]=c;
	for(int i=h[u]; i; i=ne[i]) {
		if(w[i]<=limit) continue;
		int j=e[i];
		if(color[j]) {
			if(color[j]==c) return 0;
		} else if(!dfs(j,3-c,limit)) return 0;
	}
	return 1;
}
int check(int limit) {
	memset(color,0,sizeof(color));
	for(int i=1; i<=n; ++i)
		if(!color[i])
			if(!dfs(i,1,limit)) return 0;
	return 1;
}
int main() {
	cin>>n>>m;
	for(int i=0; i<m; ++i) {
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	int l=0,r=1e9;
	while(l<=r) {
		int mid=(r-l)/2+l;
		if(check(mid)) r=mid-1;
		else l=mid+1;
	}
	cout<<r+1;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值