一轮集训DAY9测试结题报告及问题反思

结题报告

论蒟蒻的我考场下来改了一点点就40->300的故事

T1DNA sequence

考场上多打了个-1然后直接爆零

首先考虑直接爆搜,由于搜索树无限,使用迭代加深

那么,考虑进行剪枝:

  1. 设当前字符串给匹配串缺少的字符数量的最大值为\(res\),则显然最少还需要\(res\)次操作才能够完成
  2. 对于每一次字符的加入操作,如果匹配的总字符数不大于不加入的,那就不如不加
string a[15],ans;
int t,n,m,dep,tag,Ans,len[15],kkk;
char s[4]={'A','C','G','T'};
int cnt[150];
inline int find(string &a,string &b){
	//cout<<a<<" "<<b<<endl;
	int len1=a.size(),len2=b.size();
	int l=0,r=0,cnt=0;
	for(int i=0;i<len1;i++){
		if(a[i]==b[l]){
			l++;
		}
	}
	return len2-l;
}
inline int check(string &s){
	int siz=s.size(),res=0,sum=0;
	for(int i=1;i<=n;i++){
		int x=find(s,a[i]);
		kkk=max(kkk,x);
		res+=x;
	}
	return res;
}
struct node{
	string ch;
	int res;
	bool operator<(const node b){
		return res<b.res;
	}
};
void dfs(int now){
	kkk=0;
	//cout<<now<<endl;
	if(tag)return ;
	int sum=check(ans);
	if(sum==0){
		tag=1;Ans=ans.size();return ;
	}
	if(now+kkk>=dep)return ;
	string k=ans;
	for(int i=0;i<4;i++){
	//	cout<<ans<<" ";
		ans+=s[i];
		int res=check(ans);
		if(res<sum)dfs(now+1);
		ans=k;
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		dep=0;
		tag=Ans=0;
		while(!tag){
			ans="";
			dfs(0);++dep;
		}
		cout<<Ans<<endl;
	}
}

T2

双向BFS,考场上放在最后来做,然后就TMD被T3T4坑了没时间写

首先,题目显然是双向BFS可做(卡IDA* 和A* )。
对于两个空格的处理,就直接记录下来,分别对两个空格进行扩展即可。
对于十字星地图的处理,直接将不能扩展到的位置打上标记,然后照常扩展即可。
HASH的话我直接用了map和string(虽然效率低,但此题足够了)
然后就是双向BFS的板子了
追求效率的话,可以选用更优秀的HASH方式,然后对于空格和空格换位置记得特判掉。
还有就是可以类比八数码写估价函数,就成了双向A*算法(这玩意虽然跑得飞起但调试很痛苦)。
话说为啥这个鬼数据,\(T=1\)啊都。

int n,m;
map<string,int>dis[2],vis[2];
struct node{
	string val;
	int tag,a[6][6];
};
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int t[6][6]={
{0,0,0,0,0,0},
{0,12,12,0,12,12},
{0,12,1,2,3,12},
{0,4,5,6,7,8},
{0,12,9,10,11,12},
{0,12,12,0,12,12},
};
int s[6][6]={
{0,0,0,0,0,0},
{0,12,12,0,12,12},
{0,12,1,2,3,12},
{0,4,5,6,7,8},
{0,12,9,10,11,12},
{0,12,12,0,12,12},
};
bool check(int x,int y){
	if(min(x,y)<1||max(x,y)>5||t[x][y]==12)return false;
	return true;
}
char get(int x){
	return x+'0';
}
string get(int a[6][6]){
	string ans="";
	for(int i=1;i<=5;i++){
		for(int j=1;j<=5;j++){
			int k=a[i][j];
			if(k==0)ans+="0";
			else if(k<=9&&k>=1)ans+=get(k);
			else if(k==10)ans+="10";
			else if(k==11)ans+="11";
			else ans+="12";			 
		}
	}
	return ans;
}
node mk(int a[6][6],int tag){
	node ans;ans.val=get(a),ans.tag=tag;
	for(int i=1;i<=5;i++){
		for(int j=1;j<=5;j++)ans.a[i][j]=a[i][j];
	}
	return ans;
}
void print(int a[6][6]){
	for(int i=1;i<=5;i++){
		for(int j=1;j<=5;j++)cout<<a[i][j]<<" ";
		cout<<endl;
	}
}
int bfs(){
	queue<node>q;
	q.push(mk(s,0));q.push(mk(t,1));
	dis[0][get(s)]=dis[1][get(t)]=0;
	vis[0][get(s)]=vis[1][get(t)]=1;
	if(get(s)==get(t))return 0;
	while(!q.empty()){
		node u=q.front();q.pop();
		node f=u;
		int cnt=0,sx,sy,tx,ty;
		for(int i=1;i<=5;i++){
			for(int j=1;j<=5;j++){
				if(u.a[i][j]==0){
					if(!cnt){
						++cnt;sx=i,sy=j;
					}
					else tx=i,ty=j;
				}
			}
		}
		for(int i=0;i<4;i++){
			int nx=sx+dx[i],ny=sy+dy[i];
			int mx=tx+dx[i],my=ty+dy[i];
			node v=f;
			if(check(nx,ny)&&v.a[nx][ny]!=0&&v.a[nx][ny]!=12){
				swap(v.a[nx][ny],v.a[sx][sy]);
				v.val=get(v.a);
				if(vis[v.tag^1][v.val]==1){
					return dis[v.tag^1][v.val]+dis[u.tag][u.val]+1;
				}
				if(vis[v.tag][v.val]!=1){
					vis[v.tag][v.val]=1;
					int d=0;
					d=dis[v.tag][v.val]=dis[u.tag][u.val]+1;
					if(d<=10)q.push(v);
				}
			}
			v=f;
			if(check(mx,my)&&v.a[mx][my]!=0&&v.a[mx][my]!=12){
				swap(v.a[mx][my],v.a[tx][ty]);
				v.val=get(v.a);
				if(vis[v.tag^1][v.val]==1){
					return dis[v.tag^1][v.val]+dis[u.tag][u.val]+1;
				}
				if(vis[v.tag][v.val]==1)continue;
				vis[v.tag][v.val]=1;
				int d=0;
				d=dis[v.tag][v.val]=dis[u.tag][u.val]+1;
				if(d<=10)q.push(v);
			}
		}
	}
	return -1;
}
void init(){
	vis[0].clear(),vis[1].clear();
	dis[0].clear(),dis[1].clear();
	cin>>s[1][3];
	cin>>s[2][2]>>s[2][3]>>s[2][4];
	cin>>s[3][1]>>s[3][2]>>s[3][3];
	cin>>s[3][4]>>s[3][5];
	cin>>s[4][2]>>s[4][3]>>s[4][4];
	cin>>s[5][3];
	return ;
}
int main(){
	int T;ios::sync_with_stdio(false);
	cin>>T;
	while(T--){
		init();
		int ans=bfs();
		if(ans==-1||ans>22)puts("No solution!");
		else cout<<min(20,ans)<<"\n";
	}
	return 0;
}

T3无题I(untitled)

IDA*大水题
我真的是服了,考场上慌了成了SB,估价函数返回比较的时候本来乘了4,但后面又删掉了还没发觉,直接爆蛋了
首先四个方向移动的函数就不用说了,简单到爆。

void move1(int x){//将第x行左移 
	for(int i=4;i;i--)a[x][i+1]=a[x][i];
	a[x][1]=a[x][5]; 
}
void move2(int x){//将第x行右移 
	for(int i=1;i<=4;i++)a[x][i-1]=a[x][i];
	a[x][4]=a[x][0];	
}
void move3(int x){//将第x列上移 
	for(int i=4;i;i--)a[i+1][x]=a[i][x];
	a[1][x]=a[5][x]; 
}
void move4(int x){//将第x列下移 
	for(int i=1;i<=4;i++)a[i-1][x]=a[i][x];
	a[4][x]=a[0][x];	
}
void move(int x,int y){
	if(y==1)move1(x);
	if(y==2)move2(x);
	if(y==3)move3(x);
	if(y==4)move4(x);
}

然后就是估价函数的设计:显然,每一次最多使每行/每列减少一个不一样的数,这样我们就可以统计出使得所有行/列归位的最小次数(即对每一行的数的种数-1)。显然一次可以更改4个数,所以需要除以4进行比较(保证精度进行乘四)(考场忘了乘四神笔错误直接爆蛋)

int check(){
//	print();
	int res=0,sum=0,cnt[10];
	for(int i=1;i<=4;i++){
		memset(cnt,0,sizeof cnt);
		for(int j=1;j<=4;j++){
			if(!cnt[a[i][j]]){
				res++;
				cnt[a[i][j]]=1;
			}
		}
		res--;
	}
	sum=res,res=0;
	for(int i=1;i<=4;i++){
		memset(cnt,0,sizeof cnt);
		for(int j=1;j<=4;j++){
			if(!cnt[a[j][i]]){
				res++;cnt[a[j][i]]=1;
			}
		}
		res--;
	}
	return min(sum,res);
}
//DFS中:
  int h=check();
	if(h==0){
	//	print();
		Ans=now,tag=1;return ;
	}
	if(now*4+h>dp*4)return ;

剩下的代码就套板子了,别告诉我你不会。

T4铁盘整理

赛时忘了离散化变成SB

首先,为了简化问题,将序列先离散化。

这样问题就变成了:给定一个\(1\sim n\)的排列,只允许将以一开头的子序列进行反转,问排好序的最少次数。

看到数据范围,考虑使用IDA*进行求解。
考虑一次反转不可以改变什么。显然一次反转,相对而言,反转\([1,x]\)\([2,x-1]\)的数都只是前面的数和后面的数进行交换,这启发我们将每个数的正确前后位进行计数。每一次反转,最多使得2个位置变得正确。

所以我们需要除以二。但在统计的时候这样统计即可:只用统计后面就是。

int h(){
	int res=0;
	for(int i=1;i<=n;i++)res+=(abs(a[i]-a[i+1])!=1);
	return res;
}

但这样会挂,为什么——1,2,3,……nn,n-1,…1都会导致这个为0。这个解决很简单,强制最后一位不能变即可,也即:a[n+1]=n+1

剪枝

  1. 不重复反转
  2. 不转长度为1的区间
  3. 卡卡常数
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[505],n,m,dep,b[505];
int h(){
	int res=0;
	for(int i=1;i<=n;i++)res+=(abs(a[i]-a[i+1])!=1);
	return res;
}
bool dfs(int now,int lst){
	int f=h();
	if(f==0)return true;
	if(f+now>dep)return false;
	for(int i=2;i<=n;i++){
		if(i==lst)continue;
		reverse(a+1,a+i+1);
		if(dfs(now+1,i))return true;
		reverse(a+1,a+i+1);
	}
	return false;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n; 
	for(int i=1;i<=n;i++)cin>>b[i];
	for(int i=1;i<=n;i++)a[i]=b[i];
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+n+1,a[i])-b;
	a[n+1]=n+1;
	dep=0;
	while(!dfs(0,0)){
		++dep;
	}
	cout<<dep;
	return 0;
}

MD其实难度排序应该是:

\(T1<T3\le T4<T2\)
从中我们可以看出来,对于估价函数的设计的一个套路:

考虑每一个部分(一般为一个数)在进行某次操作后(作为一般项)哪些地方不会改变,再考虑特殊地方会变化多少,以此设计估价
注意,不会改变的不仅仅指位置不变,也可以是某个整体不变。

总结反思

MD,考场上每道题都反智,直接挂掉200+,真的服了。

一些挂分的点:

  1. 不要被同桌所影响——同桌hjl爆切T1T2T3,把我弄得心态爆炸
  2. 切忌胡乱优化代码——这会导致奇奇怪怪的错误并把代码弄得很难调试
  3. 考虑需要详细周全——不然就会出现各种神笔错误
  4. 多组数据记得清空——否则WA的不要不要的
  5. 注意\(\pm 1\)边界情况——直接100->0
  6. 勿轻言放弃每道题——简单的T2直接放弃,直接丢掉\(100pts\)
  7. 起码先要打暴力分——能拿一分是一分
  8. 慌慌张张毁掉考试——心态稳住

一些学到的\(trick\)

  1. 估价函数可以从整体考虑,也可以考虑个体再进行求和,甚至可以考虑个体的前后关系与在整个数列的关系
  2. map<string,int>vis[2],dis[2]大法好
  3. 代码实现食用模块化编程
  4. 双向A*->跑得飞起
  5. 必要且可行时可以牺牲运行时间换取代码可读性与可调试性
  6. 手玩是个好东西
  7. 剪枝的时候可以考虑当前策略是否做出了贡献,没有直接continue.
  8. 务必注意常数开销
posted @ 2023-03-05 07:40  spdarkle  阅读(13)  评论(0编辑  收藏  举报