赛后——2.13 寒假模拟9


\(\text{T1}\) 打错变量挂 \(80\) 分祭(痛失 \(\text{rk}1\)

\(\text{T1}\) No Problem

题意

新学期到了,有一个 \(n\times m\) 的教室,老师来组织同学们互相认识一下。

每个人会和自己周围 \(3\times 3\) 方格中的其他人握手(即左上、上、右上、左、右、左下、下和右下的八个人,若没有人则不会握手),如果还有空位,老师会挑一个空位坐下时的总的握手次数最多。

求这个最多次数。

思路

首先求出所有同学周围同学数的一半,就是握手次数。同时记录下一个空位周围同学的最大值,二者之和就是答案。

代码

点击查看代码
inline int get(int x,int y){
	int res=0;
	for(int i=x-1;i<=x+1;i++){
		for(int j=y-1;j<=y+1;j++){
			if(i==x&&j==y) continue;
			if(i<1||i>n||j<1||j>m) continue;
			if(s[i][j]=='o') res++;
		}
	}
	return res; 
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		scanf("%s",s[i]+1);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){//SoyTony的殇
			int tmp=get(i,j);
			if(s[i][j]=='o') sum+=tmp;
			else maxx=max(maxx,tmp);
		}
	}
	printf("%d\n",sum/2+maxx);
	return 0;
} 

\(\text{T2}\) Str

题意

对于字符串 \(s\),定义一个变换 \(f(s)\) 表示,\(\forall 1\le k\le\lfloor|s|/2\rfloor\),将从后往前数第 \(k\) 个字符插入从前往后数第 \(k\) 个与第 \(k+1\) 个字符之间后得到的字符串例如,\(s=\text{abcdef}\),变换后的 \(s'=\text{afbecd}\)

现在给出变换过 \(k\) 次以后的字符串,求变换之前的字符串。

思路

首先把变换过 \(1\) 次字符串复原是 \(O(|s|)\) 的,而 \(k\)\(10^9\) 之多。但是因为变换是有循环节的(这个可以写个程序计算下),就能够得到对于每个 \(|s|\) 的循环节大小,将 \(k\) 取模,剩下的就交给暴力吧。

代码

点击查看代码
int main(){
	k=read();
	scanf("%s",s+1);
	n=strlen(s+1);
	k=k%md[n];
	for(int i=1;i<=k;i++){
		char tmp[1005];
		for(int j=1;j<=(n-1)/2+1;j++){
			tmp[j]=s[j*2-1];
			if(j*2<=n) tmp[n-j+1]=s[j*2];
		}
		for(int i=1;i<=n;i++){
			s[i]=tmp[i];
		}
	}
	for(int i=1;i<=n;i++){
		cout<

另外挂一个打表的代码。

点击查看代码
int main(){
	t=read();
	freopen("data.out","w",stdout);
	for(int i=1;i<=t;i++){
		for(int j=1;j<=i;j++){
			s[j]=a[j]=j;
		}
		int cnt=0;
		while(1){
			cnt++;
			for(int j=1;j<=i/2;j++){
				tmp[j*2-1]=a[j];
				tmp[j*2]=a[i-j+1];
			}
			if(i%2) tmp[i]=a[i/2+1];
			bool pd=0;
			for(int j=1;j<=i;j++){
				if(s[j]!=tmp[j]){
					pd=1;
					break;
				}
			}
			if(!pd){
				printf("%d,",cnt);
				break;
			}
			for(int j=1;j<=i;j++){
				a[j]=tmp[j];
			}
		}
	}
	return 0;
}

\(\text{T3}\) Not TSP

题意

一个城镇有 \(n\) 个地区,第 \(i\) 个地区和第 \(j\) 个的地区是 \(dis(i,j)\),即从 \(i\)\(j\) 与从 \(j\)\(i\) 都是一样的代价。

现在小 \(F\) 要恰好一次访问这些所有地区,为了降低难度,规定访问第 \(i\) 个地区的时候,\(1\dots i-1\) 这些地区要么全部去过要么全部未去过。

求恰好一次访问所有地区的最小代价。(起点终点任意)

思路

首先状压暴搜可以拿到 \(40\) 分,这里 \(n\le 20\)

然而,发现从任意一个点起步,都只能向编号小于它的点移动(下文描述为向左走),因为向任何一个大于它的点移动,都不满足“全部未走过”的条件。同时,这样的走法可以选择一个一个“扫”,也可以选择直接“跳”,而这样向左的目的地是 \(1\),而且当重新去到大于起点编号的点时,左面的点必须恰好访问一次。于是从 \(1\) 向右走的本质其实是,将向左走时“扫”过的点直接“跳”过,“跳”过的点中间部分一个一个“扫“过。

我们用 \(f(i,j)\) 来表示从 \(i\) 出发向左,到达 \(1\) 后向右到达 \(j\) 的最小代价,显然最终答案是 \(\min_{i=1}^{n-1}\{f(i,n)\}\)。发现其实 \([i+1,j]\) 的部分只能扫过,于是转移其实是由 \(f(i,i+1)\) 的得来的。接着,对于每个 \(f(i,i+1)\),我们用一个 \(j\) 将其断开,路径大致是:\(i+1\to j \to 1\to i\),于是 \([1,i+1]\) 的点就全部走完了,枚举这个 \(j\) 即可。

代码

点击查看代码
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			dis[i][j]=read();
		}	
	}
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+dis[i-1][i];
	}
	for(int i=0;i<=n;i++){
		dp[0][i]=sum[i];
		dis[0][i]=dis[1][i];
	}
	for(int i=2;i<=n;i++){
		for(int j=0;j

\(\text{T4}\) Game

题意

\(\text{Alice}\)\(\text{Bob}\) 在做游戏。

平面上有 \(n\) 个点 \(\text{Alice}\) 先手,首先选择画一条平行于 \(\text{x}\) 轴或 \(\text{y}\) 轴的一条直线,穿过前一条直线穿过的某个点。不能画与之前直线重合的直线。不能操作的人输,求最优策略下谁赢。

思路

发现把每个直线当成一个节点,每个点作为节点的连边,就转化成类似于二分图上移动棋子的博弈论做法。当这个图有完备匹配时后手必胜。

代码

点击查看代码
inline bool dfs(int u,int c){
	if(col[u]==c) return false;
	col[u]=c;
	for(auto v:G[u]){
		if(dfs(l[v],c)||!l[v]){
			l[v]=u,r[u]=v;
			return true;
		}
	}
	return false;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int xx=read(),yy=read();
		x.insert(xx),y.insert(yy);
		G[xx].push_back(yy);
	}
	for(int i=1;i<=n;i++){
		dfs(i,i);
	}
	for(auto i:x){
		if(!r[i]) return printf("Alice\n"),0;
	}
	for(auto i:y){
		if(!l[i]) return printf("Alice\n"),0;
	}
	printf("Bob\n");
	return 0;
}