[笔记]均分纸牌问题

news/2025/2/28 15:56:05/文章来源:https://www.cnblogs.com/Sinktank/p/18607007

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/853432.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

掌握PageRank算法核心!你离Google优化高手只差一步!

0 前言 98年前的搜索引擎体验不好:返回结果质量不高:搜索结果不考虑网页质量,而通过时间顺序检索易被钻空:搜索引擎基于检索词检索,页面中检索词出现的频次越高,匹配度越高,这样就会出现网页作弊的情况。有些网页为了增加搜索引擎的排名,故意增加某个检索词频率当时Goo…

ApacheDirectoryStudio如何安装和使用?附安装包

前言 大家好,我是小徐啊。ldap数据库是我们Java开发中,经常会用到的一种数据库。这种数据库是树形结构的,和平常的mysql等数据库还不太一样。但目前对应连接ldap数据库的连接工具比较少,且功能也不强大。今天,小徐就来介绍下一款比较好的连接ldap数据库的连接功能,那就是…

服务器 数据库被攻击如何处理

最近系统有点卡,查看了一下系统事件,发现有人攻击服务器数据库。 以下是我的解决方案 1、修改密码位复杂的密码 2、修改默认数据库默认端口目前已解决下面的腾讯的小哥给的建议,总体差不多一个意思 1、服务器设置大写、小写、特殊字符、数字组成的12-16位的复杂随机密码 ,…

字符数组及应用

这两个等价 长度都为10。这两个等价 长度都为11。如果有:则:注意: 作者QQ4577105

PyQt5 使用 QPlainTextEdit/QTextBrowser 与 Logging 结合后显示日志信息

PyQt5 使用 QPlainTextEdit/QTextBrowser 与 Logging 结合后显示日志信息 本文演示 PyQt5 如何与 Python 的标准库 Logging结合,然后输出日志信息到如:QPlainTextEdit QTextBrowser上 代码结构 本文中全部代码全在test_QPlainTextEdit_Log.py这一个文件中编码,步骤中有变动的…

2024-2025-1 20241314 《计算机基础与程序设计》第十二周学习总结

2024-2025-1 20241314 《计算机基础与程序设计》第十二周学习总结 作业信息这个作业属于哪个课程 2024-2025-1-计算机基础与程序设计这个作业要求在哪里 2024-2025-1计算机基础与程序设计第十二周作业这个作业的目标 复习作业正文 正文教材学习内容总结引言与文件概述在《C语…

PyQt5 使用结合Logging 在 QPlainTextEdit/QTextBrowser 上显示日志信息

PyQt5 使用结合Logging 在 QPlainTextEdit/QTextBrowser 上显示日志信息 本文演示 PyQt5 如何与 Python 的标准库 Logging结合,然后输出日志信息到如:QPlainTextEdit QTextBrowser上 代码结构 本文中全部代码全在test_QPlainTextEdit_Log.py这一个文件中编码,步骤中有变动的…

uniapp+vue3+uViewPlus

1、uniapp创建项目2、 HuilderX菜单栏 工具->插件安装 -》前往插件市场安下载安装到对应的项目 导入的时候需要看广告 耐心看完 3、uview-plus在main.js中配置代码 import uviewPlus from @/uni_modules/uview-plusapp.use(uviewPlus) 4、在uni.scss中配置样式: @import…

NAS部署quark-auto-save,实现网盘资源自由

安装拉取镜像docker pull cp0204/quark-auto-save:latest一键运行容器docker run -d--name quark-auto-save-p 5005:5005-e TZ=Asia/Shanghai-v /volume1/docker/quark-auto-save:/app/config # 配置文件路径--restart unless-stoppedcp0204/quark-auto-save:latest系统配置首次…

索引与性能优化

title: 索引与性能优化 date: 2024/12/15 updated: 2024/12/15 author: cmdragon excerpt: 索引是数据库性能优化的重要工具,通过建立索引,可以加速数据的检索和查询操作,从而提高数据库的响应速度。虽然索引能显著改善数据访问性能,但不当的使用也可能导致性能下降。 ca…

Differential Transformer: 通过差分注意力机制提升大语言模型性能

Transformer模型已经成为大语言模型(LLMs)的标准架构,但研究表明这些模型在准确检索关键信息方面仍面临挑战。今天介绍一篇名叫Differential Transformer的论文,论文的作者观察到一个关键问题:传统Transformer模型倾向于过分关注不相关的上下文信息,这种"注意力噪声…

idea简单调试

1.行断点是一个小红原点 然后,在main方法中点击调试,程序运行时会在该点停顿,点击 恢复程序就会继续运行2.详细断点 | 源断点 shift+左键唤出断点是一个小黄圆点,并且有一些信息 若点击挂起再点完成,将会变为小红圆点 点击调试,控制台给出断点位置 3.方法断点 是一个小…