[算法竞赛进阶指南] 七夕祭 (贪心+前缀和+中位数+排序)

题目

七夕节因牛郎织女的传说而被扣上了「情人节」的帽子。

于是TYVJ今年举办了一次线下七夕祭。

Vani同学今年成功邀请到了cl同学陪他来共度七夕,于是他们决定去TYVJ七夕祭游玩。

TYVJ七夕祭和11区的夏祭的形式很像。

矩形的祭典会场由N排M列共计N×M个摊点组成。

虽然摊点种类繁多,不过cl只对其中的一部分摊点感兴趣,比如章鱼烧、苹果糖、棉花糖、射的屋……什么的。

Vani预先联系了七夕祭的负责人zhq,希望能够通过恰当地布置会场,使得各行中cl感兴趣的摊点数一样多,并且各列中cl感兴趣的摊点数也一样多。

不过zhq告诉Vani,摊点已经随意布置完毕了,如果想满足cl的要求,唯一的调整方式就是交换两个相邻的摊点。

两个摊点相邻,当且仅当他们处在同一行或者同一列的相邻位置上。

由于zhq率领的TYVJ开发小组成功地扭曲了空间,每一行或每一列的第一个位置和最后一个位置也算作相邻。

现在Vani想知道他的两个要求最多能满足多少个。

在此前提下,至少需要交换多少次摊点。

输入格式:

第一行包含三个整数N和M和T,T表示cl对多少个摊点感兴趣。

接下来T行,每行两个整数x, y,表示cl对处在第x行第y列的摊点感兴趣。

输出格式:

首先输出一个字符串。

如果能满足Vani的全部两个要求,输出both;

如果通过调整只能使得各行中cl感兴趣的摊点数一样多,输出row;

如果只能使各列中cl感兴趣的摊点数一样多,输出column;

如果均不能满足,输出impossible。

如果输出的字符串不是impossible, 接下来输出最小交换次数,与字符串之间用一个空格隔开。

数据范围

1≤N,M≤100000 ,
0≤T≤min(N∗M,100000),
1≤x≤N,
1≤y≤M

输入样例
2 3 4
1 3
2 1
2 2
2 3
输出样例
row 1
分析
  • 这个题目比较像均分纸牌问题和货仓选址问题的结合

  • 均分纸牌问题说的是:
    一共有i个人站成一行,每个人都有a[i]个纸牌数,每次我们可以将一个人的一张纸牌给他相邻的一个人,求经过多少次我们可以将他们的纸牌数分成一样。

    均分纸牌的解法是:
    将每个人的纸牌数都减去平均值,然后利用前缀和从第一个开始,如果他的前缀和等于0,答案数不变,否则加上他的前缀和的绝对值。因为第一个如果不够肯定只能问第二个拿,这样推理第二个也是一样,因此这种方案肯定是对的。
    那么假设sum为前缀和。

   ans = sum[1] + sum[2] + ... + sum[n];
  • 当不能平均分配时无解,即总和对n取余不等于0

  • 环形均分纸牌指的是 第一个和最后一个也是相邻的,既形成一个圆。
    环形均分纸牌的解法于它类似:
    环形中肯定有一个k点,k于k+1点不会交换纸牌,然后把k它假设为1,k+1假设为n,然后利用均分纸牌的方案,求解即可。但是这样时间复杂度很高,k要从1遍历到n,那我们有没有什么可以迅速求解出k点的方法呢?
    肯定是有的,假设sum[i]为以1为起点的前缀和,num[i]为以k点为起点的前缀和
    我们分析k+1点可知k+1点的前缀和可以写成 num[k+1]=sum[k+1]-sum[k];
    那么

   num[k+1]=sum[k+1]-sum[k];
   num[k+2]=sum[k+2]-sum[k];
   ......
   num[1]=sum[1]+sum[n]-sum[k];
   num[2]=sum[2]+sum[n]-sum[k];
   ......
  • 因为是均分,所以sum[n]肯定是等于0的,那么就变成了求解问题就变成了
    求:
   ans = |sum[1]-sum[k]| + |sum[2]-sum[k]| + ... + |sum[n]-sum[k]|;
  • 很容易看出,我们把这个公式看成每一个点到k的点距离的和,那我们就用到了货仓选址问题,那么答案就是当sum数组排序后,他的中位数就是离所有点的距离之和最小的。
    所以sum排序后。
   k=(n+1)>>1;
  • 问题就解决了。

  • 我们再回到这个问题。题目中要求每一行中或每一列中都包含相等数目的小摊,因为只能交换相邻的两个位置,交换行就不会影响列,交换列就不会影响行,所以我们可以将行列分开单独处理。分别都是一个环形的均分纸牌问题。
    如果行列都有解就把步数相加。

代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define filr(i,a,b) for(ll i=a;i<=b;i++)
#define fild(i,a,b) for(ll i=a;i>=b;i--)
const int N=1e5+5;
ll r[N],c[N];

ll calc(ll arr[],ll num,ll sum){
	ll temp=sum/num,i;
	filr(i,1,num) arr[i]+=arr[i-1]-temp;
	sort(arr+1,arr+num+1);
	ll ans=0,kk=arr[(num+1)/2];
	filr(i,1,num) ans+=abs(arr[i]-kk);
	return ans;
}

int main()
{
	ll n,m,t,x,y,sumx=0,sumy=0,i;
	cin>>n>>m>>t;
	filr(i,1,t){
		cin>>x>>y;
		r[x]++;
		c[y]++;
	}
	
	filr(i,1,n) sumx+=r[i];
	filr(i,1,m) sumy+=c[i];
	
	ll is_x=sumx%n,is_y=sumy%m;
	if(!is_x&&!is_y) cout<<"both "<<calc(r,n,sumx)+calc(c,m,sumy)<<endl;
	else if(!is_x&&is_y) cout<<"row "<<calc(r,n,sumx)<<endl;
	else if(is_x&&!is_y) cout<<"column "<<calc(c,m,sumy)<<endl;
	else cout<<"impossible"<<endl;
	return 0;
} 
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值