伊斯坦布尔的帮派Gangs of Istanbull

题目链接https://www.luogu.org/problem/P3064

数据范围:略。


题解

这个题其实分为两问,第一问是$YES$、$NO$和最大值,第二问是最小字典序方案。

整体思路肯定是,后$2\sim m$的帮派先自行抵消,最少能剩下多少奶牛,然后再用$1$去抵消。

先说第一问:

问题就相当于求$k$堆奶牛最少抵消成多少头。

这个最傻逼的做法就是维护一个大根堆,把$2\sim m$都扔进去。

然后每次取出人数最多的两个帮派,让它俩互相抵消一次,再扔回堆里,这是$O(nlogn)$的。

再来看第二问:

我们发现,如果按照第一问的思路,第二问根本就没法做。

因为第一问的过程我们根本就没办法掌控,但是它给了我们一些启发。

再画几组小数据我们发现,最少剩多少奶牛其实只和这$2\sim m$中的最大值有关。

这是显然的,那么我们假设这些奶牛的和为$sum$,最大值为$mx$,分两种情况讨论:

第一种:$mx > \frac{sum}{2}$。

这种就比较简单,因为所有的非最大值奶牛一定是要和最大值相抵消的。

那么我们把答案大小的$1$号放在最后,剩下的随便搞搞基就好,具体看代码。

第二种:$mx \le \frac{sum}{2}$。

这种的话,最少会剩下$sum \& 1$头,假设是$0$。

那么所有的$1$奶牛都得扔在后面,我们只需要考虑剩下的帮派怎么互相抵消就好。

考虑每次贪心。

假设现在已经决定了前$i - 1$头奶牛的顺序,剩下$now$头属于帮派$id$的奶牛,我们考虑$i$位置。

首先,取出来最小的有奶牛的帮派和最大的帮派两个。

如果最小的帮派放在了位置$i$,与$now$和$id$做了做抵消之后,剩下的奶牛仍然满足$Max \le \frac{Sum}{2}$,我们就放最小的。

显然如果最小的不行,不是最大值不是最小值的其他任何数都不行(除了$id$,可以做一下特判)。

如果都不行,那么这个位置只能是最大值,我们就把最大值所在的帮派的奶牛数$--$,然后考虑位置$i + 1$即可。

这时候,我们想一想怎么能拿出来最大值最小值呢?还要支持单点修改?那就维护一个线段树好了。

代码

#include <bits/stdc++.h>

#define N 1000010 

#define ls p << 1 

#define rs p << 1 | 1 

using namespace std;

char *p1, *p2, buf[100000];

#define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ )

int rd() {
int x = 0, f = 1;
char c = nc();
while (c < 48) {
if (c == '-')
f = -1;
c = nc();
}
while (c > 47) {
x = (((x << 2) + x) << 1) + (c ^ 48), c = nc();
}
return x * f;
} int a[N]; int sum[N << 2], mx[N << 2]; inline void pushup(int p) {
mx[p] = max(mx[ls], mx[rs]);
sum[p] = sum[ls] + sum[rs];
} void update(int x, int v, int l, int r, int p) {
if (l == r) {
sum[p] += v, mx[p] += v;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) {
update(x, v, l, mid, ls);
}
else {
update(x, v, mid + 1, r, rs);
}
pushup(p);
} int query_id(int l, int r, int p) {
if (l == r) {
return l;
}
int mid = (l + r) >> 1;
if (sum[ls]) {
return query_id(l, mid, ls);
}
else {
return query_id(mid + 1, r, rs);
}
} int query_mx(int l, int r, int p) {
if (l == r) {
return l;
}
int mid = (l + r) >> 1;
if (mx[ls] >= mx[rs]) {
return query_mx(l, mid, ls);
}
else {
return query_mx(mid + 1, r, rs);
}
} int main() {
// freopen("gangs.in", "r", stdin);
// freopen("gangs.out", "w", stdout);
int n = rd(), m = rd();
for (int i = 1; i <= m; i ++ ) {
a[i] = rd();
}
int Sum = n - a[1], mx = 0;
for (int i = 2; i <= m; i ++ ) {
mx = max(mx, a[i]);
}
int re;
int flag = 0;
if (mx <= Sum / 2) {
flag = 1;
re = Sum & 1;
if (re) {
flag = 2;
}
}
else {
int mdl = Sum - mx;
re = mx - mdl;
flag = 3;
}
if (re >= a[1]) {
puts("NO");
return 0;
} puts("YES");
printf("%d\n", a[1] - re);
if (flag == 1) {
// puts("Fuck");
for (int i = 2; i <= m; i ++ ) {
update(i, a[i], 1, m, 1);
}
int id = 0, now = 0;
for (int i = 1; i <= n - a[1]; i ++ ) {
int j = query_id(1, m, 1);
if (!id) {
printf("%d\n", j);
id = j, now = 1;
a[j] -- ;
update(j, -1, 1, m, 1);
continue;
}
if (id == j) {
printf("%d\n", j);
now ++ ;
a[j] -- ;
update(j, -1, 1, m, 1);
continue;
}
int mdlall = now + sum[1] - 2;
// puts("SSSShit");
int mdlmxid = query_mx(1, m, 1);
// cout << mdlall << ' ' << mdlmxid << endl ;
int mdlmx = a[mdlmxid];
if (mdlmxid == j) {
mdlmx -- ;
}
if (mdlmx <= mdlall / 2) {
printf("%d\n", j);
update(j, -1, 1, m, 1);
a[j] -- ;
now -- ;
if (!now) {
id = 0;
}
}
else {
printf("%d\n", mdlmxid);
update(mdlmxid, -1, 1, m, 1);
a[mdlmxid] -- ;
now -- ;
if (!now) {
id = 0;
}
}
}
for (int i = 1; i <= a[1]; i ++ ) {
printf("%d\n", 1);
}
}
else if (flag == 2) {
// puts("Fuck");
update(1, 1, 1, m, 1);
for (int i = 2; i <= m; i ++ ) {
update(i, a[i], 1, m, 1);
}
int id = 0, now = 0;
int all = n - a[1] + 1;
for (int i = 1; i <= all; i ++ ) {
int j = query_id(1, m, 1);
if (!id) {
printf("%d\n", j);
id = j, now = 1;
a[j] -- ;
update(j, -1, 1, m, 1);
continue;
}
if (id == j) {
printf("%d\n", j);
now ++ ;
a[j] -- ;
update(j, -1, 1, m, 1);
continue;
}
int mdlall = now + sum[1] - 2;
int mdlmxid = query_mx(1, m, 1);
int mdlmx = a[mdlmxid];
if (mdlmxid == j) {
mdlmx -- ;
}
if (mdlmx <= mdlall / 2) {
printf("%d\n", j);
update(j, -1, 1, m, 1);
a[j] -- ;
now -- ;
if (!now) {
id = 0;
}
}
else {
printf("%d\n", mdlmxid);
update(mdlmxid, -1, 1, m, 1);
a[mdlmxid] -- ;
now -- ;
if (!now) {
id = 0;
}
}
}
// puts("Shit");
// printf("%d\n", a[1]);
for (int i = 1; i <= a[1]; i ++ ) {
printf("%d\n", 1);
}
}
else {
int id = 2;
for (int i = 2; i <= m; i ++ ) {
if (a[i] > a[id]) {
id = i;
}
}
for (int i = 1; i <= re; i ++ ) {
puts("1");
}
for (int i = 1; i <= re; i ++ ) {
printf("%d\n", id);
}
for (int i = 2; i < id; i ++ ) {
for (int j = 1; j <= a[i]; j ++ ) {
printf("%d\n", i);
}
for (int j = 1; j <= a[i]; j ++ ) {
printf("%d\n", id);
}
}
int mdlsum = 0;
for (int i = id + 1; i <= m; i ++ ) {
mdlsum += a[i];
}
for (int i = 1; i <= mdlsum; i ++ ) {
printf("%d\n", id);
}
for (int i = id + 1; i <= m; i ++ ) {
for (int j = 1; j <= a[i]; j ++ ) {
printf("%d\n", i);
}
}
for (int i = 1; i <= a[1] - re; i ++ ) {
puts("1");
}
}
// fclose(stdin);
// fclose(stdout);
return 0;
}

小结:好题好题,但是细节有点点多。考试的时候没拿到$flag=2$的点,原因是$for$循环的问题。所以如果一个题有多种情况,最好每种情况都试几组小样例。

[LuoguP3064][USACO12DEC]伊斯坦布尔的帮派Gangs of Istanbull(加强版)_线段树_贪心的更多相关文章

  1. P3064 [USACO12DEC]伊斯坦布尔的帮派 (模拟)

    题目传送门 题意: 一片草地,每次可以只可以让一种牛占领,问你怎样安排牛的次序 最后剩下的是1号牛,并且输出其数量 思路: 看到n到100 ,所以可以(n^3)暴力,第一重遍历次序,第二枚举是哪只牛 ...

  2. 洛谷P3066 [USACO12DEC]逃跑的Barn (线段树合并)

    题目描述It's milking time at Farmer John's farm, but the cows have all run away! Farmer John needs to ro ...

  3. Usaco2012-2013 金组 题解 (暂缺Hill walk以及Figue eight)

    https://files.cnblogs.com/files/Winniechen/usaco2012-2013.pdf 做的不是很好,还请见谅! 如果有什么疑问,可以QQ上找我. QQ号:1967 ...

  4. bzoj AC倒序

    Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...

  5. ACM起步要点总结(转哈工大)

    首先,我想说的就是,我是一个很普通的ACMer,高中没有参加过任何计算机和数学竞赛的经历,也没有ben那样过人的天资,努力至今也未能取得什么成绩,我之所以写下这篇文章,只是希望给刚进大学或者刚进ACM ...

  6. NOIP2017 国庆郑州集训知识梳理汇总

    第一天 基础算法&&数学 day1难度测试 如果要用一个词来形容上午的测试,那真是体无完肤.  成绩: 题目 成绩 评价 T1 50 一般 T2 10 大失所望 T3 0 差 基础算法 ...

  7. [Swift]LeetCode879. 盈利计划 | Profitable Schemes

    There are G people in a gang, and a list of various crimes they could commit. The i-th crime generat ...

  8. my.资料收集_20170912

    1.终于摸索出平民单开赚钱方法了!![梦幻西游手游吧]_百度贴吧.html http://tieba.baidu.com/p/5323468885?see_lz=1 1.http://tieba.ba ...

  9. Description POJ1703

    Description The police office in Tadu City decides to say ends to the chaos, as launch actions to ro ...

随机推荐

  1. npm源管理

    1. 安装淘宝镜像 为了提高npm的安装速度,可以使用淘宝镜像. 使用淘宝镜像的方法有两种: 1. npm install -g cnpm --registry=https://registry.np ...

  2. 【线性代数】5-3:克莱姆法则,逆和体积(Cramer's Rule,Inverses,and Volumes)

    title: [线性代数]5-3:克莱姆法则,逆和体积(Cramer's Rule,Inverses,and Volumes) categories: Mathematic Linear Algebr ...

  3. c语言 宏

    #代表命令要被预处理器处理#define 定义的宏可以出现在程序的任意位置#define 定义之后的代码都可以使用这个宏 宏是字面量,不占用内存 单步编译预处理器,只进行文本替换,不进行语法检查:gc ...

  4. 汇编语言学习-Dos下的调试工具debug的使用教程

    1.常用的debug功能 (1)用Debug的R命令查看.改变CPU寄存器内容: (2)用Debug的D命令查看内存中的内容: (3)用Debug的E命令查看内存中的内容: (4)用Debug的U命令 ...

  5. servlet实现类似target="_top"功能

    通过网上很多解决方案,大部分都是重定向,或者页面跳转,但是我试了试都不能脱离原来框架,后来发现,可以直接通过form表单的target来实现从servlet跳转到frameset的指定框架,这就不用再 ...

  6. UVALive 3716 DNA Regions ——(扫描法)

    乍一看这个问题似乎是很复杂,但其实很好解决. 先处理出每个点到原点的距离和到x正半轴的角度(从x正半轴逆时针旋转的角度).然后以后者进行排序. 枚举每一个点到圆心的距离,作为半径,并找出其他到圆心距离 ...

  7. POJ 2486 Apple Tree ——(树型DP)

    题意是给出一棵树,每个点都有一个权值,从1开始,最多走k步,问能够经过的所有的点的权值和最大是多少(每个点的权值只能被累加一次). 考虑到一个点可以经过多次,设dp状态为dp[i][j][k],i表示 ...

  8. OpenFOAM 中的边界条件(一)【转载】

    链接:http://xiaopingqiu.github.io/2016/04/02/Boundary-conditions-in-OpenFOAM1/ 本系列解读 OpenFOAM 中边界条件的实现 ...

  9. idea 2018注册码(激活码)

    最近做一个项目,用idea 社区版的   但是缺少了好多功能 无奈只能用专业版的,但是需要注册激活  下面是我的注册方法 1.打开了idea  会提示让激活  选择Licensse server 2. ...

  10. 【spring源码分析】IOC容器初始化——查漏补缺(四)

    前言:在前几篇查漏补缺中,其实我们已经涉及到bean生命周期了,本篇内容进行详细分析. 首先看bean实例化过程: 分析: bean实例化开始后 注入对象属性后(前面IOC初始化十几篇文章). 检查激 ...