20260716 小测总结

省流:运气局,t5 乱搞获得 95pts 但是挂了 40+ 有点气愤。这告诉我们要认真检查。

T1 数折形

原题链接

题目大意

给出一个 $ n \times n $ 的二维网格,每个格子上写有 ' o ' 和 ' x ',我们要在其中选出满足以下条件的三个格子:

  • 这 3 个格子互不相同。

  • 这 3 个格子上都写有 o。

  • 这 3 个格子中,恰好有 2 个在同一行。

  • 这 3 个格子中,恰好有 2 个在同一列。

求一共有多少个选择。

solution

对于每个三元组,正如题目所说,实际上就是一个折形。

观察折形的条件,发现这本质就是选出一个 o ,然后在其所在行选择一个 o ,再在其所在列再选一个 o 。

分析到这样就好办了。我们直接预处理出每一行和每一列 o 的个数,然后遍历整个网格,当我们遇到一个 o 的时候,答案就加上所在行和列的 o 的个数的积,记得减 1 因为自身不算。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005;
int n,cnth[N],cntl[N],ans;//cnth=行 o 的个数,cntl=列 o 的个数
char a[N][N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
			if(a[i][j]=='o')cnth[i]++,cntl[j]++;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j]=='o'){
				ans+=(cnth[i]-1)*(cntl[j]-1);
			}
		}
	}
	cout<<ans;
	return 0;
}

T2 冰激淋

原题链接

题目大意

给出一个 $ n \times n $ 的二维网格,每个格子上写有 ' . ' 和 ' # ',每个 ' # ' 组成的连通块代表一个冰淇淋。求面积最大的冰淇淋(面积最大则周长更小者更优)。

值得注意的是,这里的周长并非外面一圈而是所有 ' # ' 的边缘没有被覆盖的都要算上。

solution

普通的DFS。面积很好算每当搜到一个 ' # ' 的时候就加一,至于周长可以在从 ' # ' 往周围扩散的时候判断一下,如果周围是跃出边界或者空地就加入答案。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
int n,L,zdS,dx[4]={0,1,0,-1},dy[4]={1,0,-1,0},len,S;
char a[N][N];
bool vis[N][N];
void dfs(int x,int y){
	if(vis[x][y])return;
	vis[x][y]=1;
	S++;
	len+=4;
	for(int i=0;i<4;i++){
		int tx=x+dx[i],ty=y+dy[i];
		if(tx>=1&&tx<=n&&ty>=1&&ty<=n&&a[tx][ty]=='#')len--,dfs(tx,ty);
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j]=='#'&&!vis[i][j]){
				len=0;
				S=0;
				dfs(i,j);
				if(S>zdS){
					zdS=S;
					L=len; 
				}
				else if(S==zdS&&len<L)L=len;
			}
		}
	}
	cout<<zdS<<' '<<L;
	return 0;
}

T3 数组的孤独度

原题链接

题目大意

给定一个序列,求最小的 k 使得所有长度为 k 的子序列的按位或值相等。

solution

这题的思路我感觉不太好解释,只能尽力了。

看见按位或习惯性拆位思考。

把每个数都拆开补全长度最后排成一个矩阵,发现如果一列全都是 0 那么不影响答案,如果不是的话那么我们就要计算每两个 1 之间的距离,最大的距离 +1 就是答案。

另外最左边的 1 到头和最右边的 1 到尾也是要计算的。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int t;
int n,a[N];
vector<vector<int> >bit;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n;
		bit.clear();
		bit.resize(n+1);
		for(int i=1;i<=n;i++)cin>>a[i];
		int len=0;
		for(int i=1;i<=n;i++){
			if(a[i]==0){bit[i].push_back(0);continue;}
			int w=a[i],cnt=0;
			while(w){
				bit[i].push_back(w%2);
				w/=2;
				cnt++;
			}
			len=max(len,cnt);
		}
		for(int i=1;i<=n;i++){//补齐
			while(bit[i].size()<len)bit[i].push_back(0);
		}
		int ans=0;
		for(int i=0;i<len;i++){
			int flag=0,zd=0;//计算左边直接设置flag=0然后正常计算即可,如果flag=0说明
			for(int j=1;j<=n;j++){
				if(bit[j][i]==1)zd=max(zd,j-flag),flag=j;
			}
			if(flag)zd=max(zd,n-flag+1)/*特判最后一个1到右边*/,ans=max(ans,zd);
		}
		cout<<max(ans,1ll)<<'\n';
	}
	return 0;
}

T4 安全路径

原题找不到,看到的好心人可以帮我找找

题目大意

给出一个树,定义安全节点为以 1 为根时以该节点为根的子树的大小(包括该节点)为偶数的节点,求总共有多少条路径使得路径上所有点都是安全节点。

另外,路径是有序的,也就是说 $ x \rightarrow y $ 和 $ y \rightarrow x $ 是两条路径。

solution

切完t3过来看这题直接被吓哭了,没想到这么简单,反复确认了好几遍才敢写下做法。

先 DFS 一遍求出所有的 $ sz[u] $,确认哪些节点是安全节点哪些不是。

然后对于所有的不安全节点我们直接把它断开,然后就会剩下一些连通分量,每个连通分量我们求出它的大小然后套个组合公式就可以了。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=500005;
int n,sz[N],cnt,ans;
bool iss[N],vis[N];
vector<vector<int> >g;
inline void dfs(int now,int fa){
	sz[now]=1;
	for(int i=0;i<g[now].size();i++){
		int to=g[now][i];
		if(to==fa)continue;
		dfs(to,now);
		sz[now]+=sz[to];
	}
}
inline void dfss(int now){
	if(!iss[now]||vis[now])return;
	cnt++;
	vis[now]=1;
	for(int i=0;i<g[now].size();i++)dfss(g[now][i]);
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	g.resize(n+1);
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++)if(sz[i]%2==0)iss[i]=1;
	for(int i=1;i<=n;i++){
		if(iss[i]&&!vis[i]){
			cnt=0;
			dfss(i);
			ans+=cnt*(cnt-1);
		}
	}
	cout<<ans;
	return 0;
}

T5 星港续航

赛时乱搞获得 95pts,赛后改了一下获得 100pts 但是过不了洛谷数据拉完了。先咕着。

T6 移动球球

原题链接

题目大意

给出一些黑球和白球的排列,黑球和白球都有 $ n $ 个,每个球上面都有编号,你可以进行一次操作并使得两个相邻的球位置交换。

求让所有黑球从左到右编号从小到大排列的同时,让所有白球从左到右编号从大到小排列的最小交换次数。

solution

赛时没想出来,赛后醍醐灌顶。。。

考虑 DP 。我们令 $ dp[i][j] $ 表示将前 $ i $ 个按顺序排列的同时将前 $ j $ 个黑球按顺序排列的最小交换次数, $ dp[0][0] = 0 $, $ dp[n][n] $ 即为答案。

我们做一个预处理:设置两个数组, $ cstw[i][j] $ 和 $ cstb[i][j] $ ,表示从 $ dp[i-1][j] $ 与 $ dp[i][j-1] $ 转移到 $ dp[i][j] $ 时所需要的交换次数(即多放一个白球/黑球需要的交换次数)。

从计算白球入手。我们可以思考一下,当第 $ i $ 个白球要成功让编号翻转过来,它要翻越哪些障碍?

首先是比它大的白球。也就是说所有位置小于 $ i $ 并且编号大于 $ i $ 的白球(说白了就逆序对)。

其次是一些阻挡在其前面的黑球。这个不用管编号直接看它的位置即可。

顺便讲一句,为了便于实现我们设立两个保存位置的数组: $ posw[id] $ 和 $ posb[id] $ ,分别表示编号为 $ id $ 的不同颜色的球的原本的位置。

然后我们看回正题。哦不不不,如果每次计算 $ cst $ 的时候都扫一遍数组求白球逆序对和黑球的数量这无疑是超时的。

所以我们考虑一手递推。先考虑白球的 $ cst $ 的计算。我们先计算出所有的 $ cstw[i][0] $ 。然后思考如果 $ cstw[i][0] \rightarrow cstw[i][1] $ 的话会发生什么变化。

先直接继承一手。但是我们发现假如说 $ j $ 这个黑球前面挡住了这个白球并且被算进了答案,而且这个时候它已经被放好了,现在不会影响答案,这个时候再把其加上就会让答案错误。

所以我们判断前面是否有没有挡住,如果挡住了减减即可。

黑球同理。当然不是因为散文与懒得写了。

预处理做完了直接 DP 即可。这个状态转移大抵是不用讲的,唯一要注意的就是要记得初始化然后 $ i $ 和 $ j $ 要从 $ 0 $ 开始。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005;
int n,dp[N][N],cstw[N][N],cstb[N][N],posw[N],posb[N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=2*n;i++){
        char col;
        int pos;
        cin>>col>>pos;
        if(col=='W')posw[pos]=i;
        else posb[pos]=i;
    }
    for(int i=0;i<n;i++){//注意看i和j的范围。因为我们计算的是i+1被放进来的情况所以i=n的情况不会被用。下面计算cstb的时候j<n也是这个道理
        for(int j=0;j<=n;j++){
            if(j==0){
                cstw[i][0]=0;
                for(int k=i+1;k<=n;k++)if(posw[k]<posw[i+1])cstw[i][0]++;//逆序对必须换
                for(int k=1;k<=n;k++)if(posb[k]<posw[i+1])cstw[i][0]++;//挡我路了滚开
            }
            else{//注意我们的cstw计算的是i+1这个白球被放进来的时候要加的交换次数所以下面比较的时候写的是i+1,赛后在这里卡了好久
                cstw[i][j]=cstw[i][j-1];//从这个黑球还没放继承
                if(posb[j]<posw[i+1])cstw[i][j]--;//前面计算过了,去掉
            }
        }
    }
    for(int i=0;i<=n;i++){
        for(int j=0;j<n;j++){
            if(i==0){
                cstb[0][j]=0;
                for(int k=1;k<=n;k++)if(posw[k]<posb[j+1])cstb[0][j]++;
                for(int k=j+1;k<=n;k++)if(posb[k]<posb[j+1])cstb[0][j]++;
            }
            else{
                cstb[i][j]=cstb[i-1][j];
                if(posw[i]<posb[j+1])cstb[i][j]--;
            }
        }
    }
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){
            if(i==0&&j==0)continue;//两个都是0的话肯定是0,不管
            dp[i][j]=LLONG_MAX;//初始化
            if(i)dp[i][j]=min(dp[i][j],dp[i-1][j]+cstw[i-1][j]);//判断一下放置越界
            if(j)dp[i][j]=min(dp[i][j],dp[i][j-1]+cstb[i][j-1]);
        }
    }
    cout<<dp[n][n];
	return 0;
}
posted @ 2026-07-26 17:25  fireclouds_11  阅读(3)  评论(0)    收藏  举报