做题随笔:P10453

Solution

一些闲话

写之前看过大家的题解,觉得都写的很好,所以本蒟蒻这篇也不大抱希望,就当做完题写个随笔?

题意

原题链接

给定 \(N\times M\) 矩阵与 \(T\) 个特殊点,每次操作可以将相邻点进行交换,且每行、每列首尾也算作相邻,求使每行特殊点数相同,每列相同,每行相同且每列相同的可行性与最少操作步数。

分析

可行性判断

首先是可行性判断:以行的判断为例,因为交换没有限制,所以只要满足可以每行数量相同即可,也就是 $N $ 整除 \(T\) 即可。列和整个矩阵的判断同理。

最少步数求解

求解时,应该想到行和列的交换是独立的。

如样例1:

.   *
.   *
*   *
//*为关键点

\((2,1)\)\((1,1)\) 交换前后,第一行都有且仅有一个关键点,而第2列点数减1,第1列加1。即列交换不影响行点数。这是显然的。

其实这就是这个题最关键的一步了。想到这里,由于行和列交换独立,可以分开考虑交换,而且可以整体考虑交换。例如交换行时,考虑整列地交换,从而问题变成了:

给定正整数 \(N\) 以及数列 \(a_N\),每次可以以代价 \(k\),使相邻两数一个 \(+k\) 一个 \(-k\)\(a_1\)\(a_N\) 也算作相邻)要使 \(a_N\) 每项相同,求最小代价和。

对于这个问题,先考虑 \(a_1\)\(a_N\) 不算作相邻的情况(链上):考虑直接贪心:每一项减后不可能再加,加后不可能再减,代价与选定数的顺序无关,所以对每个数可以直接一步到位,先计算目标值 \(ave=\frac{\sum_{i = 1}^n a_i}{N}\),然后直接从 \(N=1\) 开始向右扫一遍,大于 \(ave\) 的自减右加,小于同理,所以 \(ans=\sum_{i = 1}^n abs(a_i-ave)\),写个前缀和轻松解决。

那么接下来就是 \(a_1\)\(a_N\) 也算作相邻的了(环上)。首先,环上问题一定能破坏成链上的环节问题,因为环具有封闭性,除了涉及到断点的操作,任何操作都可以在链上等效替代。以本题为例:如果把 \(a_1\)\(a_N\) 断开,对于非断点, \(a_1\)\(a_2\) 操作没有受到任何影响;而对于断点, \(a_1\)\(a_N\) 操作,其实和 \(a_1\)\(a_2\) 操作后, \(a_2\)\(a_3\) 操作,一直到到 \(a_N\) 从结果上是一样的,只是代价有差异,差异取决于 \(a_1\)\(a_N\) 原本的值。因此破坏时要遍历每一组可能的间断点,进行 \(N\) 次链上问题,对代价取最小值。

很好,已经 \(O(n^ 2)\) 了,但是\(1\le N,M\le 100000\) 显然不行,再考虑一下优化——

现在我们把 \(a_i\)\(a_{i+1}\) 断开,并以 \(a_{i+1}\) 为链的起点, \(a_i\) 为终点,即:\(a_{i+1},a_{i+1+1},a_{i+1+2},\dots,a_N,a_1,a_2,\dots,a_i\)

设原有以 \(a_1\) 开头的链为原数组计算的前缀和数组 \(S_N\),则新数组 \(G_N\) 为:

G[i+1]=S[i+1]-S[i];//现在它是第一项
G[i+2]=(S[i+2]-S[i-1])+G[i-1]=S[i+2]-S[i-1]
......
G[1]=S[1]+S[N]-S[i]
G[2]=S[2]+S[N]-S[i]
......
//若S[N]=0,即计算S前,a先通减了ave,有:
G[x]=S[x]-S[x-1]+G[x-1]=S[x]-S[i]

最后一式直观的话容易看出,证明的话递推即可。于是新前缀和的每一项就是原数组全减 \(S_i\),继续使用上述链上问题的思路(或者直接代公式),有 \(ans=min(\sum_{i = 1}^n abs(S_i-S_k)),k \in [1,N]\)

典型的初中数学题!于是直接使 \(S_k\)\(a_N\) 的中位数(排个序取 \(S_{\frac{N}{2}}\)),问题得解。

以上即为 P2512 糖果传递解题思路。

实现

读入矩阵,同时记录每行每列点数。先判断可行性,再按行、列进行求最小。若行列都成立的,分别计算求和即可。

Code

#include <iostream>
#include <cstdio>
#include <cctype>
#include <algorithm>

using namespace std;

typedef long long ll;//不开long long 见祖宗

ll fr() {
	ll x=0; char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) {
		x=(x<<3)+(x<<1)+(c^48);
		c=getchar();
	}
	return x;
}//经典快读

const ll maxn=1e5+10;
ll r[maxn],c[maxn],n,m,t,x,y;

ll row(ll x,ll *a) {
	ll M = t/x,ret = 0;//求均
	ll dr[maxn];
	dr[0]=0;
	for(register int i = 1; i <= x; i++) {
		a[i]-=M;//先减
		dr[i]=dr[i-1]+a[i];
	}
	sort(dr+1,dr+1+x);
	int k=(1+x)/2;//中位数
	for(register int i = 1; i <= x; i++) {
		ret+=abs(dr[i]-dr[k]);
	}
	return ret;
}

int main() {
	n=fr();m=fr();t=fr();
	for(register int i = 1; i <= t; i++) {
		x=fr();y=fr();
		r[x]++;c[y]++;//计数转糖果传递
	}
	if(!(t%n)&&!(t%m)) printf("both %lld\n",row(n,r)+row(m,c));
	else if(!(t%n)) printf("row %lld\n",row(n,r));
	else if(!(t%m)) printf("column %lld\n",row(m,c));
	else {
		printf("impossible\n");
	}
	return 0;
}

后话

感觉有用,还请点个赞吧!

posted @   Tenil  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示