2026.10.2sol

2026.10.2sol

题面

T1

考虑对于一个数\(3^{i-1}\),随便选比它小的数都小于他,故只选比它小的数有\(2^i-1\)种选法,故\(3^i\) 排名为\(2^i\),所以第\(k\)小的数为\(k\)的二进制数在三进制下的大小。

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
signed main(){
	freopen("catfish.in","r",stdin);
	freopen("catfish.out","w",stdout);
	scanf("%lld",&n);
	while(n--){
		scanf("%lld",&k);int ans = 0;
		for(int i = 0 , p = 1; i<=30 ; i++,p*=3)
			if((k>>i)&1) ans+=p;
		printf("%lld\n",ans);
	}
	return 0;
}

T2

求一个矩阵中最大子矩阵,满足仅在对角线为1,其他为0。考虑对于副对角线,将每一行翻转过来就成了主对角线,故只讨论主对角线。设\(dp_{i,j}\)为在第\(i\)行第\(j\)列为右下角最大的子矩阵长度。转移显然有\(dp_{i,j}=\left\{ \begin{array}{l} 0(a_{i,j}=0) \\ min(dp_{i-1,j-1},p,q)+1 (a_{i,j}=1) \end{array} \right.\)其中\(p,q\)分别表示\(a_{i,j}\)左侧和上端有多少个连续的\(0\),显然是可以在枚举时顺带求出的。
当然也有双指针做法,即枚举每一条斜线,去维护当前最大子矩阵,其实本质相同。

code

#include <bits/stdc++.h>
using namespace std;
const int N = 2515;
short dp[N][N],q[N],ans,n,m;
bool a[N][N];
void func(){
	memset(q,0,sizeof(q));
	for(int i = 1 ; i<=n ; i++){
		short p = 0;
		for(int j = 1 ; j<=m ; j++){
			if(!a[i][j]) ++p,++q[j],dp[i][j]=0;
			else dp[i][j]=min({dp[i-1][j-1],p,q[j]})+1,p=0,q[j]=0;
			ans=max(ans,dp[i][j]);
		}
	}
}
int main(){
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);
	scanf("%hd%hd",&n,&m);
	for(int i = 1 ; i<=n ; i++){
		for(int j = 1 ; j<=m ; j++){
			char ch=getchar();
			while(ch!='0'&&ch!='1') ch=getchar();
			a[i][j]=ch-'0';
		}
	}
	func();
	for(int i = 1 ; i<=n ; i++) reverse(a[i]+1,a[i]+m+1);
	func();
	printf("%hd",ans);
	return 0;
}

T3

容易发现其给的父子关系显然是等价于无向的,即整棵树可看作一颗无根树。显然枚举每个点作为信息源鱼即为无根树的根,计算所花费的时间。

使用树形\(dp\),设\(dp_{i}\)代表着\(0\)时刻从\(i\)开始传播,传递完\(i\)这棵子树所花费的时间。因为每一个单位时间只能传一个节点,则按儿子节点的\(dp\)值从大到小的顺序去给每一个儿子节点传播信息一定是不劣的。故\(dp_i=max(dp_j+s_j)\),其中\(s_j\)代表着它是子节点中\(dp\)值第\(s_j\)大的。

code

#include <bits/stdc++.h>
using namespace std;
const int N = 1015;
int n,dp[N],s[N];
vector <int> e[N];
void dfs(int u,int fa){
	dp[u]=0;
	vector <int> tmp;
	for(auto v : e[u]){
		if(v==fa) continue;
		dfs(v,u);tmp.emplace_back(dp[v]);
	}
	sort(tmp.begin(),tmp.end(),greater<int>());
	for(int i = 0 ; i<(int)tmp.size() ; i++) dp[u]=max(dp[u],tmp[i]+i+1);
	return ;
}
int main(){
	freopen("badnews.in","r",stdin);
	freopen("badnews.out","w",stdout);
	while(scanf("%d",&n)!=EOF){
		for(int i = 2 , fa; i<=n ; i++){
			scanf("%d",&fa);
			e[fa].emplace_back(i),e[i].emplace_back(fa);
		}
		int ans = 1e9;
		for(int i = 1 ; i<=n ; i++){
			dfs(i,0);
			ans=min(ans,s[i]=dp[i]);
		}
		printf("%d\n",ans+1);
		for(int i = 1 ; i<=n ; i++){
			if(s[i]==ans) printf("%d ",i);
		}
		printf("\n");
		for(int i = 1 ; i<=n ; i++) e[i].clear();
	}
	return 0;
}

T4

首先是一个错误的\(dp\),设\(dp_{x_1,y_1,x_2,y_2}\)表示考虑左上角为\((x_1,y_1)\),右下角为\((x_2,y_2)\),这个巨型的答案,假设其一定是可以被一刀切,则可以枚举被切的那一行或列,转移是容易的,可以直接看代码。但问题是不一定是一刀切,可能更优,于是根据官方题解有真实答案与\(dp\)所求出来的上界很接近,可以以\(dp\)所求出上界来剪枝\(dfs\)。复杂度未知,标程还是\(AI\)写的什么猎奇题。

因为题目太那啥了,所以只把赛时的\(dp\)搬上来了,但其思路和\(dp\)求出上界的\(trick\)还是值得学习的。

code

#include <bits/stdc++.h>
using namespace std;
const int N = 35;
int n,m,f[N][N][N][N],s[N][N];
int calc(int i,int j,int x,int y){
	return s[x][y]-s[i-1][y]-s[x][j-1]+s[i-1][j-1];
}
int main(){
	freopen("baffles.in","r",stdin);
	freopen("baffles.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i = 1 ; i<=n ; i++){
		for(int j = 1 ; j<=m ; j++){
			scanf("%d",&s[j][i]);
		}
	}
	for(int i = 1 ; i<=m ; i++)
		for(int j = 1 ; j<=n ; j++)
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
	memset(f,0x3f,sizeof(f));
	for(int l1 = 1 ; l1<=m ; l1++){
		for(int l2 = 1 ; l2<=n ; l2++){
			for(int i = 1 ; i+l1-1<=m ; i++){
				for(int j = 1 ; j+l2-1<=n ; j++){
					int x = i+l1-1,y=j+l2-1;
					if(calc(i,j,x,y)==1){
						f[i][j][x][y]=0;
						continue;
					}
					for(int k = i ; k<x ; k++){
							f[i][j][x][y]=min(f[i][j][x][y],f[i][j][k][y]+f[k+1][j][x][y]+y-j+1);
					}
					for(int k = j ; k<y ; k++){
							f[i][j][x][y]=min(f[i][j][x][y],f[i][j][x][k]+f[i][k+1][x][y]+x-i+1);
					}
				}
			}
		}
	}
	printf("%d",f[1][1][m][n]);
	return 0;
}
posted @ 2026-10-02 21:36  lrj3247  阅读(11)  评论(0)    收藏  举报