首页 > 其他分享 >[笔记]均分纸牌问题

[笔记]均分纸牌问题

时间:2024-12-15 19:09:24浏览次数:4  
标签:limits 纸牌 箭头 int sum 笔记 均分 答案

Index

  • 链形均分纸牌
    • 每次仅可交换\(1\)张
    • 每次可交换多张
  • 环形均分纸牌
    • 每次仅可交换\(1\)张
    • 每次可交换多张

拓展性很强的贪心问题。或许能推广到树之类的结构上,或者拓展到方案计数问题之类,不过目前还没想好啦。

链形均分纸牌

每次仅可交换\(1\)张

最基础的例题是这样的:

有\(n\)个人坐成一排,第\(i\)个人初始持有\(a[i]\)张纸牌。定义一次操作如下:

  • 设\((u,v)\)是相邻的两人,让\(u\)给\(v\)一张纸牌。

请问要让每个人持有的纸牌数相同,最少进行多少次操作。

此题可以用贪心求解。

显然,有解\(\iff n|(\sum\limits_{i=1}^{n}a[i])\),令\(a\)的平均数为\(x\)。

令\(s\)为\(a\)的前缀和数组,答案即为\(\sum\limits_{i=1}^{n}|s[i]-i\times x|\)。
也可以将每个\(a[i]\)减去\(x\)再用,答案就是\(\sum\limits_{i=1}^{n}|s[i]|\)了。

这是因为第\(1\)个人的牌数只能通过第\(2\)个人调整成\(x\),显然两个相邻的人之间只能进行单向操作,否则答案一定不优。所以第\(1\)个人被调整成\(x\)后就相当于被删掉了,相应地,第\(2\)个人只能通过第\(3\)个人调整成\(x\)……答案就这样出来了。

固然,按上面的模拟方法,可能会出现负数张牌的情况。不过每个人最终都会被补成\(x\)张牌,所以通过更改交换顺序就可以保证中途不出现负数张牌(实际上感性理解并不难,下面是严谨一点的证明)。

更为具体地,我们不妨令\(a[i]\leftarrow (a[i]-x)\),令\(y\)表示此时的\(\min a[i]\)。

我们需要证明,最优方案下一定可以保证任何时刻\(a[i]\ge y\)。

对于\(a[1,i]\)和\(a[i+1,n]\)两个区间:

  • 如果\(s[i]=0\),那么两个区间不进行交换。
  • 如果\(s[i]>0\),那么一定左给右。
  • 如果\(s[i]<0\),那么一定右给左。

我们把这样的交换关系用箭头表示出来。

每个元素只能发牌给自己指向的元素,且只能收到指向自己元素的牌。

我们按上图的拓扑序发牌,按拓扑序遍历到的当前元素,一定将所有可能拿到的牌都拿到了,自然其值一定\(\ge y\)。对于每种图的形态,这样的构造都是可行的。

点击查看代码
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int n,a[N];
int solve(){
	int x=0,ans=0;
	for(int i=1;i<=n;i++) x+=a[i];
	if(x%n) return -1;
	x/=n;
	for(int i=1;i<=n;i++) a[i]+=a[i-1]-x;
	for(int i=1;i<=n;i++) ans+=abs(a[i]);
	return ans;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cout<<solve()<<"\n";
	return 0;
}

每次可交换多张

P1031 [NOIP2002 提高组] 均分纸牌

将上题的条件修改了一下。

和上面的图一样的分析方式,令\(a[i]\leftarrow (a[i]-x)\),答案即为箭头个数,也即和非\(0\)的前缀个数。

点击查看代码
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int n,a[N];
int solve(){
	int x=0,ans=0;
	for(int i=1;i<=n;i++) x+=a[i];
	if(x%n) return -1;
	x/=n;
	for(int i=1;i<=n;i++) a[i]+=a[i-1]-x;
	for(int i=1;i<=n;i++) ans+=(a[i]!=0);
	return ans;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cout<<solve()<<"\n";
	return 0;
}

环形均分纸牌

每次仅可交换\(1\)张

P2512 [HAOI2008] 糖果传递

所谓环形,就是规定第\(1\)个人和第\(n\)个人也可以互相发牌。

结论:一定存在一个解,满足它是最优的,且至少有两个相邻的人之间没有纸牌传递。

证明:

假设所有相邻两人之间都发生传递(自然,都是单向的),如下图。

用每个箭头传递的纸牌数\(k\)作为它的权值,如果它是红箭头则看作\(+k\),是蓝箭头则看作\(-k\)。那么容易知道,相邻的两个箭头的和是定值。

换句话说,只要确定一个箭头传递的纸牌数,其他箭头传递的纸牌数也就确定了。

我们从上图中选定一个红色箭头,让它的值\(+1\),考虑对答案的贡献。此时所有红色箭头的值都会\(+1\),所有蓝色箭头的值都会\(-1\),对答案的贡献设为\(+y\);如果让这个红色箭头的值\(-1\),对答案的贡献就是\(-y\)。

我们根据\(y\)的正负性选择\(+\)或\(-\),直到某个箭头变成\(0\),这样就存在相邻两人之间没有传递了,答案要么不变,要么减少。

有了这个结论之后,我们就可以枚举环在哪里断开,对于每条链计算答案,不过这样是\(O(n^2)\)的,考虑优化。

链从\(k\)和它之后的元素处断开,新链的前缀和\(s_k\)为:

\[\begin{aligned} s_k[1]&=s[k+1]-s[k]\\ s_k[2]&=s[k+2]-s[k]\\ &\dots\\ s_k[n-k]&=s[n]-s[k]\\ s_k[n-k+1]&=s[n]-s[k]+s[1]\\ &\dots\\ s_k[n]&=s[n]-s[k]+s[k] \end{aligned}\]

答案即为\(\sum\limits_{i=1}^n |s_k[i]-i\times x|\)。

设\(s'[i]\)为\(s[i]-i\times x\),则有\(s[i]=s'[i]+i\times x\),且\(s'[n]=0\),带入可得:

\[\begin{aligned} s_k[1]&=s'[k+1]-s'[k]+x\\ s_k[2]&=s'[k+2]-s'[k]+2x\\ &\dots\\ s_k[n-k]&=s'[n]-s'[k]+(n-k)x\\ s_k[n-k+1]&=-s'[k]+s'[1]+(n-k+1)x\\ &\dots\\ s_k[n]&=-s'[k]+s'[k]+nx \end{aligned}\]

答案即为\(\sum\limits_{i=1}^n |s_k[i]-i\times x|=\sum\limits_{i=1}^n|s'[i]-s'[k]|\)。

呼 这个转化似乎很难想的说……不过仔细想来,\(s'\)就是将\(a[i]\leftarrow (a[i]-x)\)后的\(s\)啊!如果想到这个步骤就好考虑了(其实上面这堆都是我用它倒退得来的)。

上面这个式子是一个典型的“货仓选址”问题,\(s[k]\)选在\(s[1\sim n]\)的中位数时答案最小。而取中位数可以用nth_element()做到\(O(n)\)。

总时间复杂度是\(O(n)\)。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 1000010
using namespace std;
int n,a[N],s[N];
int solve(){
	int x=0,ans=0;
	for(int i=1;i<=n;i++) x+=a[i];
	if(x%n) return -1;
	x/=n;
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]-x;
	nth_element(s+1,s+(n+1)/2,s+1+n);
	for(int i=1;i<=n;i++) ans+=abs(s[i]-s[(n+1)/2]);
	return ans;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cout<<solve()<<"\n";
	return 0;
}

附另一种推导过程,部分来自此题解 by Social_Zhao

设\(K[i]\)为\(i\)给他前一个人的牌数。

则有\(a[i-1]-K[i-1]+K[i]=x\),即\(K[i]=K[i-1]+x-a[i-1]\)。

这样我们就可以用\(K[1]\)表示出\(K[2\sim n]\),这里的结论和证明中提到的“只要确定一个箭头传递的纸牌数,其他箭头传递的纸牌数也就确定了”是相同的,更具体地:

\[\begin{cases} K[2]=K[1]+x-s[1]\\ K[3]=K[1]+2x-s[2]\\ \dots\\ K[n]=K[1]+3x-s[n] \end{cases}\]

答案即为\(\sum\limits_{i=1}^n |K[i]|=\sum\limits_{i=1}^n|K[1]+ix-s[i]|\),然后货仓选址即可,代码相同不放了。

两种推导过程有异曲同工之妙。

每次可交换多张

这种情况下,显然只有破环成链才可能取到最优解。

枚举断开位置仍然效率太低,考虑优化。

结合链\(2\)和环\(1\)的结论,可知答案是\(\sum\limits_{i=1}^n [s'[i]-s'[k]\neq 0]\)。为了让答案尽可能小,\(s'[k]\)应取\(s'\)的众数。

点击查看代码
#include<bits/stdc++.h>
#define N 1000010
using namespace std;
int n,a[N];
unordered_map<int,int> cnt;
int solve(){
	int x=0,ans=0;
	for(int i=1;i<=n;i++) x+=a[i];
	if(x%n) return -1;
	x/=n;
	for(int i=1;i<=n;i++){
		a[i]+=a[i-1]-x;
		cnt[a[i]]++;
	}
	for(auto i:cnt) ans=max(ans,i.second);
	return n-ans;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cout<<solve()<<"\n";
	return 0;
}

标签:limits,纸牌,箭头,int,sum,笔记,均分,答案
From: https://www.cnblogs.com/Sinktank/p/18607007

相关文章

  • 计算机网络课程笔记
    计算机网络课程该笔记于2024年12月15日15:14:02编写常用命令以及简写完整命令简写形式解释configureterminalconft进入全局配置模式enableenenableexitex退出当前模式hostnamehost重启设备interfaceint进入接口配置模式shutdownshut......
  • 笔记本电脑蓝屏 硬盘损坏数据恢复
    当笔记本电脑出现蓝屏故障,并且怀疑硬盘已损坏需要恢复数据时,可以参考以下步骤和建议:一、初步处理断开电源:在尝试任何数据恢复操作之前,首先要断开笔记本电脑的电源,以避免进一步的数据损坏或丢失。评估蓝屏原因:蓝屏可能是由驱动程序错误、系统文件损坏、硬件故障等多种原因引起的......
  • 【Java学习笔记】Set 接口实现类-HashSet
    一、HashSet的全面说明HashSet实现了Set接口HashSet实际上是HashMap,看下源码.(图)可存放null,只能有一个null无序且不重复无序:不保证存放元素的顺序和取出顺序一致不重复:不能有重复元素/对象二、案例说明(仔细认真看看)packagecom.hspedu.set_;importjava.util.Has......
  • 【Java笔记】LinkedList 底层结构
    一、LinkedList的全面说明LinkedList底层实现了双向链表和双端队列特点可以添加任意元素(元素可以重复),包括null线程不安全,没有实现同步二、LinkedList的底层操作机制三、LinkedList的增删改查案例publicclassLinkedListCRUD{publicstaticvoidmain(String[]......
  • 【Java学习笔记】Map 接口实现类-HashMap
    一、HashMap小结二、HashMap底层机制及源码剖析packagecom.hspedu.map_;importjava.util.HashMap;/***@author韩顺平*@version1.0*/@SuppressWarnings({"all"})publicclassHashMapSource1{publicstaticvoidmain(String[]args){HashMapmap......
  • 并发编程笔记三-ConditionObject源码深度解析
     一.ConditionObject概述        synchronized提供了wait和notify的方法实现线程在持有锁时,可以实现挂起,唤醒的操作。其实ReentrantLock也拥有这个功能,ReentrantLock提供了await和signal方法去实现类似wait和notify的功能。同样的,想执行await或者是signal就必须先持......
  • 汽车XCP标定学习笔记简略版
    1为什么需要标定?同一款车有不同的配置,功能越多价格自然就越高,比如客户可以选择是否购买智能辅助驾驶功能服务;汽车通常配置了不同的驾驶模式,比如经济模式,正常模式和运动模式,驾驶员可以根据自己的喜好和路况来选择。以上两个例子都与标定相关,一个通过标定可以实现一个功能的开......
  • 学习笔记070——Java中【泛型】和【枚举】
    文章目录1、泛型1.1、为什么要使用泛型?1.2、泛型的应用1.3、泛型通配符1.4、泛型上限和下限1.5、泛型接口2、枚举1、泛型Generics是指在定义类的时候不指定类中某个信息(属性/方法返回值)的具体数据类型,而是用一个标识符来替代,当外部实例化对象的时候再来指定具体的......
  • 《Django 5 By Example》阅读笔记:p543-p550
    《Django5ByExample》学习第19天,p543-p550总结,总计8页。一、技术总结1.fixtures(1)定义Afixtureisacollectionoffilesthatcontaintheserializedcontentsofthedatabase.(2)作用1)数据导入一般来说,我们是通过数据库工具(如:Navicat,DBeaver)进行数据导......
  • 深度学习入门笔记——神经网络的构建和使用
    神经网络的整体构建神经网络的基本骨架首先可以在Pytorch官网的PythonAPI中查看torch.nn的使用,如下所示。可以看到神经网络包括Container(基本骨架)、卷积层、池化层、Padding层、非线性激活等等。构建一个神经网络首先要先构建起基本骨架,也就是Containersnn.Moudle的使用这......