首师大附中集训第一天专题测试

专题测试

      今天早上的题都是搜索。

      第一题:给出n个开关,一个开关对应一个灯,灯与灯之间有连边,当摁下一个开关时,对应的灯和相邻的灯都会改变状态,问至少摁下多少开关,才可以把所有灯打开。

      这道题很容易就可以想到一个结论:一个开关只会被打开一次,打开两次没有影响。

      就可以直接爆搜拿到60分。满分也很简单,我们折半搜索,把第一次搜索的结果存在一个map里面,总共只会有2^{\frac{n}{2}}种情况。然后在搜索第二次搜索的结果取反放进第一次搜索的结果里面找,然后用和的最小值更新答案就好了。

#include
#include
#include
#include
#include
using namespace std;

int n,m;
struct edge{
	int y,next;
}s[1300];
int first[40],len;
map a;
long long tar;
int ans=1e9+7;
int data;

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
	s[++len]=(edge){x,first[y]};first[y]=len;
}

void dfs(int x,long long coef,int tot){
	if(x==data+1){
		a[coef]=tot;
		return ;
	}
	long long last=coef;
	coef^=((long long)1<<(x-1));
	for(int i=first[x];i!=0;i=s[i].next) coef^=((long long)1<<(s[i].y-1));
	dfs(x+1,coef,tot+1);coef=last;
	dfs(x+1,coef,tot);
}

void get_ans(int x,long long coef,int tot){
	if(x==n+1){
		if(tar==coef) ans=min(ans,tot);
		else if(a[tar^coef]) ans=min(ans,a[tar^coef]+tot);
		return ;
	}
	long long last=coef;
	coef^=((long long)1<<(x-1));
	for(int i=first[x];i!=0;i=s[i].next) coef^=((long long)1<<(s[i].y-1));
	get_ans(x+1,coef,tot+1);coef=last;
	get_ans(x+1,coef,tot);
}

int main(){
	freopen("light.in","r",stdin);
	freopen("light.out","w",stdout);
	scanf("%d %d",&n,&m);data=n/2;tar=((long long)1<

      

      第二题:给出一个长度为n的排列,每次可以把1到i翻转,问最少用多少此操作可以把出事排列变为1到n。

      这题我只会暴力,可以把状态记录下来,然后bfs,可以很容易知道一个排列在字典序的第几位。那么就直接做就可以拿到比暴力分稍多一点的40分。

      然而正解很神仙,首先是迭代加深,关键就加一个可行性剪枝,预估当前状态到答案还要多少步,如果用当前步数+至少需要的步数>规定步数,那么直接return ; 那么这个预估怎么做呢,可以发现,当我们在翻转1到i时,相邻两位之差的改变只有一对。

       可以发现,1到n这个排列相邻两位差没有不为1的。所以我们可以把预估步数直接设为相邻两位之差不为1的对数。

       但是n到1这个排列它相邻两位之差不为1的对数也没有。所以我们可以加上第0位与第1位之差。

       这样可能导致最后一次翻转会改变2对,所以我们把限制放宽就好了。

#include
#include
#include
#include
using namespace std;

int n;
int a[25];
bool we=false;

void dfs(int x,int mmax,int*a,int ty){
	if(x+ty-2>mmax) return ;
	if(x==mmax+1){
		if(ty==0) we=true;
		return ;
	}
	for(int i=2;i<=n;i++){
		ty-=(abs(a[1]-a[0])!=1);if(i!=n) ty-=(abs(a[i]-a[i+1])!=1);
		for(int j=1;j<=i/2;j++) swap(a[j],a[i-j+1]);
		ty+=(abs(a[1]-a[0])!=1);if(i!=n) ty+=(abs(a[i]-a[i+1])!=1);
		dfs(x+1,mmax,a,ty);
		if(we) return ;
		ty-=(abs(a[1]-a[0])!=1);if(i!=n) ty-=(abs(a[i]-a[i+1])!=1);
		for(int j=1;j<=i/2;j++) swap(a[j],a[i-j+1]);
		ty+=(abs(a[1]-a[0])!=1);if(i!=n) ty+=(abs(a[i]-a[i+1])!=1);
	}
}

int main(){
	scanf("%d",&n);int tot=0;
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		tot+=(abs(a[i]-a[i-1])!=1);
	}
	int tar=1;
	while(1){
		dfs(1,tar,a,tot);
		if(we) {printf("%d",tar);return 0;}
		tar++;
	}
}

      

      第三题:有m个武器,n个炸弹,第i个武器可以攻击当且仅当前i-1个武器被消灭。给出m个武器与n个炸弹的坐标,一个炸弹可以消灭周围平面距离不超过k且正在攻击的武器,武器的切换用时1秒钟,一个炸弹连续攻击5分钟。现在,要你安排n个炸弹的顺序,问你用最少多少个炸弹可以消灭全部的武器。

      很容易就可以发现一个炸弹可以攻击周围的连续炸弹,假若当前在攻击的炸弹为i,i~T都在炸弹的攻击范围内,那么下一个攻击的武器就一定是T+1。这个十分的明显。

       利用这个我们可以给这张图建边,边权就是用的炸弹。然后我们可以忽略每个炸弹的限制,从m+1这个点走反边跑一遍bfs。那么我们就知道在搜索时答案的下界,就可以进行最优性剪枝了。

       但是不知道为什么,改了之后后面5个点还是过不了。

#include
#include
#include
#include
#include
#include
using namespace std;

int m,n,k;
struct node{
	int x,y;
}a[110],b[110];
struct pre{
	int x,y,c;
	bool operator<(const pre a)const{
		return y-x=ans) return ;
	if(now==m+1){
		ans=x-1;
		printf("%d\n",ans);
		return ;
	}
	for(int i=first[now];i!=0;i=s[i].next){
		if(tf[s[i].c]) continue;
		tf[s[i].c]=true;
		dfs(x+1,s[i].y);
		tf[s[i].c]=false;
	}
}

int main(){
	scanf("%d %d %d",&m,&n,&k);	
	for(int i=1;i<=m;i++) scanf("%d %d",&a[i].x,&a[i].y);
	for(int i=1;i<=n;i++) scanf("%d %d",&b[i].x,&b[i].y);
	for(int i=1;i<=n;i++){
		memset(tf,false,sizeof(tf));
		for(int j=1;j<=m;j++) if(get_dis(b[i],a[j])+eps<=k) tf[j]=true;
		int tot=0;
		for(int j=1;j<=m+1;j++){
			if(!tf[j]){
				for(int k=j-tot;k<=j-1;k++) p[++t]=(pre){k,j,i},inss(j,k);
				tot=0;continue;
			}
			tot++;
		}
	}
	sort(p+1,p+1+t);
	for(int i=1;i<=t;i++) ins(p[i].x,p[i].y,p[i].c); 
	bfs();
	memset(tf,false,sizeof(tf));
	dfs(1,1);
	printf("%d\n",ans);
}

 

你可能感兴趣的:(首师大附中集训第一天专题测试)