【代码超详解】CometOJ C1272 [JSOI2015]套娃(贪心 + multiset)

本文详细解析了JSOI2015竞赛中的C1272套娃问题,通过贪心算法和multiset数据结构,指导如何最小化不满意度。首先统计套娃的不满意度,然后按好看度排序,每次选择最合适的套娃进行嵌套,最后输出剩余的不满意度。

一、题目描述

在这里插入图片描述

二、算法分析说明与代码编写指导

本指导是根据 https://www.cnblogs.com/Khada-Jhin/p/10461734.html 改写并扩充的。
设每个套娃mi有三个属性O,I,B,分别代表套娃 i 的外径、内径和好看度。
在没有将任何套娃套入之前,先统计不满意度 d = ∑(mi.O × Bi),i = 1,2,……,N。
如果要让总的不满意度 d 尽可能小,且已知一个套娃内部不可以并排套入多个套娃,那么应当尽量把每个套娃都套入外径尽可能逼近其内径的套娃。易知把一个外径尽可能大的套娃套入后不会影响其它套娃的嵌套,所以可以采用贪心算法,即让每一次嵌套减少的不满意度最大。嵌套时要先从好看度高的套娃开始。
输入时只需要把套娃的外径放入多重集 s 中,输入完毕后先对套娃按好看度排序。
集合默认是升序排列的。从好看度高的套娃开始,在集合中用 lower_bound 成员函数查找第一个大于等于 当前正在尝试嵌套的套娃的内径 的外径,然后将迭代器递减 1 次,就得到了小于 当前正在尝试嵌套的套娃的内径 的最大外径。扣除相应的不满意度,并将这个找到的值删除,就完成了对本次嵌套过程的模拟。但是要注意,查找可能找到 *s.begin(),即正在尝试嵌套的套娃是所有套娃中外径最小的,此时不能将任何套娃嵌入这个套娃中,直接跳过。
对每个套娃都尝试处理完毕后,输出剩余的不满意度 d。

三、AC 代码

#include<cstdio>
#include<algorithm>
#include<set>
#pragma warning(disable:4996)
using namespace std;
struct matryoshka {
   
    unsigned long lon
### 实现原理 该代码使用 2-SAT(2-布尔可满足性问题)算法解决洛谷 P4171 [JSOI2010] 满汉全席问题。2-SAT 用于处理布尔变量的逻辑关系,每个变量有两种状态,且存在一系列条件,要求满足这些条件。在本题中,每种食材有满式和汉式两种加工方式,每个评审员有两个喜好,至少满足其中一个喜好,可将其转化为 2-SAT 问题的逻辑关系。同时使用 Tarjan 算法求强连通分量,用于判断 2-SAT 问题是否有解,如果一个变量和它的反变量在同一个强连通分量中,则无解。 ### 功能 该代码的主要功能是判断对于给定的食材数量、评审员数量以及每个评审员的喜好,是否存在一种食材加工方式的组合,使得每个评审员的至少一个喜好得到满足。如果存在这样的组合,输出 "GOOD",否则输出 "BAD"。 ### 代码逻辑 以下是对代码各部分的详细分析: ```cpp #include<bits/stdc++.h> using namespace std; const int maxn=205; int dfn[maxn]={0},low[maxn]={0},col[maxn]={0},n,m,d=0,c=0; bool instk[maxn]={0},f=0; stack<int>stk; vector<int>G[maxn]; ``` - 引入必要的头文件,定义常量 `maxn` 表示最大节点数。 - `dfn` 数组记录节点的时间戳,`low` 数组记录节点能追溯到的最早时间戳,`col` 数组记录节点所属的强连通分量编号。 - `n` 表示食材数量,`m` 表示评审员数量,`d` 用于记录时间戳,`c` 用于记录强连通分量的编号。 - `instk` 数组标记节点是否在栈中,`f` 标记是否存在无解情况。 - `stk` 是一个栈,用于 Tarjan 算法。 - `G` 是邻接表,用于存储图的边。 ```cpp void init(){ memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(instk,0,sizeof(instk)); while(!stk.empty())stk.pop(); for(int i=0;i<=n*2;i++)G[i].clear(); d=0;f=0; } ``` - `init` 函数用于初始化数组和栈,清空邻接表,重置时间戳和无解标记。 ```cpp int fei(int x){ return x>n?x-n:x+n; } ``` - `fei` 函数用于返回变量的反变量,若 `x` 大于 `n`,则返回 `x - n`,否则返回 `x + n`。 ```cpp void addedge(int u,int v){ G[u].push_back(v); } ``` - `addedge` 函数用于向图中添加一条从 `u` 到 `v` 的有向边。 ```cpp void tarjan(int u){ dfn[u]=low[u]=++d; instk[u]=1; stk.push(u); for(int i=0;i<G[u].size();i++){ int v=G[u][i]; if(instk[v])low[u]=min(low[u],low[v]); else if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } } if(dfn[u]==low[u]){ c++; while(!stk.empty()){ int t=stk.top();stk.pop(); instk[t]=0; col[t]=c; if(col[t]==col[fei(t)]){ f=1;return; } if(u==t)break; } } } ``` - `tarjan` 函数是 Tarjan 算法的核心,用于求强连通分量。 - 首先给节点 `u` 分配时间戳和 `low` 值,将其标记为在栈中并压入栈。 - 遍历 `u` 的所有邻接节点 `v`,若 `v` 在栈中,则更新 `low[u]`;若 `v` 未被访问过,则递归调用 `tarjan(v)` 并更新 `low[u]`。 - 当 `dfn[u] == low[u]` 时,说明找到了一个强连通分量,将栈中元素依次弹出,标记其所属的强连通分量编号。若一个变量和它的反变量在同一个强连通分量中,标记 `f` 为 1 表示无解。 ```cpp int main(){ int t,u,v; cin>>t; while(t--){ cin>>n>>m; init(); string s1,s2; for(int i=1;i<=n;i++)addedge(0,i); for(int i=0;i<m;i++){ cin>>s1>>s2; u=0,v=0; for(int j=1;j<s1.size();j++){ u*=10;u+=s1[j]-'0'; } for(int j=1;j<s2.size();j++){ v*=10;v+=s2[j]-'0'; } if(s1[0]=='m')u+=n; if(s2[0]=='m')v+=n; addedge(fei(u),v); addedge(fei(v),u); } tarjan(0); if(!f)cout<<"GOOD"<<endl; else cout<<"BAD"<<endl; } return 0; } ``` - `main` 函数是程序的入口。 - 读取测试数据的组数 `t`。 - 对于每组测试数据,读取食材数量 `n` 和评审员数量 `m`,调用 `init` 函数初始化。 - 向图中添加从 0 到每个食材节点的边。 - 读取每个评审员的两个喜好,将其转换为节点编号,根据逻辑关系添加边。 - 调用 `tarjan(0)` 进行强连通分量的求解。 - 根据 `f` 的值输出结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值