CodeForces CF1846G 题解
CodeForces CF1846G 题解
标准答案是状压之后,转化成Dijkstra算法跑最短路。我这里提供一个不一样的思路。
题意简述
主人公得了病,有部分类型的症状。所有类型症状共有 \(n\) 种,用长为 \(n\) 的01串表示是否有那种症状。共有 \(m\) 种药,吃第 \(i\) 种药需要花费时间 \(t_i\), 能够治好症状 \(a_i\), 留下后遗症 \(b_i\), 其中 \(a_i\)、\(b_i\)均为长度为 \(n\) 的01串,表示每种症状是否治好或者后遗。
主人公每次只能吃一种药。求康复需要的最少时间。
保证输入不会自相矛盾,药物能治好某种症状就不会后遗。
多组测试。
题目分析
1. 最后吃什么?
实际上这个过程和“化学除杂”有些类似。我们考虑最后吃的药的特征,发现最后吃的药一定没有后遗症。简单的证明就是:我们考虑症状个数 \(cnt\),最终目标是 \(cnt = 0\),如果每种药物都有后遗症,那么即使能够治好全部症状,也至少会遗留下一种后遗症,于是 \(cnt \ge 1\),矛盾。我们暂且把这种药物成为“纯药”(无后遗症)。
2. 交换吃药顺序?
我们发现交换药物服用顺序是不行的(显然后吃“纯药”和先吃“纯药”,一个康复,一个可能不康复)。
3. 一种药物吃几次?
接下来我们尝试考虑一种药物吃几次。
假设一个药物吃两次及以上,为了方便表示,我们不妨交换每种症状的相对位置,使得对于这个药物而言,是“治疗症状、保持原状、后遗症”的格式。例如原来是:
\text{主人公症状} & \texttt{01011}\\
\text{药物的疗效} & \texttt{11010}\\
\text{药物后遗症} & \texttt{00100}\\
\end{array}
\]
交换症状相对位置之后(此处3、4列和4、5列对调)变成:
\text{主人公症状} & \texttt{01110} \\
\text{药物的疗效} & \texttt{11100}\\
\text{药物后遗症} & \texttt{00001}\\
\end{array}
\]
我们将药物的效果压缩成一个串来表示,治疗用 \(\texttt{-}\) 表示,保持不变用 \(\texttt{0}\) 表示, 后遗症用 \(\texttt{+}\) 表示,于是:
\text{药物的疗效} & \texttt{11100}\\
\text{药物后遗症} & \texttt{00001}\\
\text{药物效果} & \texttt{---0+}\\
\end{array}
\]
我们将不确定的位置用 \(\texttt{Q}\) 来占位表示。(下面表中的各部分串的长度仅为示意,实际上是某一特定的数值。)假如一个药物吃了两次及以上,肯定存在两次吃某一个药,于是有:
\text{项目} & \text{可治疗} & \text{不变} & \text{后遗症} \\
\text{用药前一状态} & \texttt{QQQ} & \texttt{QQQQ} & \texttt{QQ} \\
\text{药物效果} & \texttt{---} & \texttt{0000} & \texttt{++} \\
\text{一次用药后状态} & \texttt{000} & \texttt{QQQQ} & \texttt{11} \\
\text{中间若干用药} & \cdots & \cdots & \cdots \\
\text{二次用药后状态} & \texttt{000} & \texttt{QQQQ} & \texttt{11} \\
\end{array}
\]
我们发现在两次服药中间的步骤,所起到的效果(或者说吃它们的目的),是为了改变 \(Q\) 的值。因此我们发现,如果把第一次吃药这一步撤掉,我们的结果是:
\text{项目} & \text{可治疗} & \text{不变} & \text{后遗症} \\
\text{用药前一状态} & \texttt{QQQ} & \texttt{QQQQ} & \texttt{QQ} \\
\text{药物效果} & \texttt{---} & \texttt{0000} & \texttt{++} \\
\text{中间若干用药} & \cdots & \cdots & \cdots \\
\text{原二次用药后状态} & \texttt{000} & \texttt{QQQQ} & \texttt{11} \\
\end{array}
\]
效果没有改变。
因此一种药物吃一遍就足够了。也就是说,每种药只吃一次。
4. 从最后的药物出发
所以我们找到一个“纯药”之后,根据上面的结论,这个纯药应当在最后吃,而且只在最后吃(因为每种药只吃一次)。
我们观察一下:
\text{项目} & \text{可治疗} & \text{不变} & \text{后遗症} \\
\text{某状态} & \texttt{QQQ} & \texttt{QQQQ} & \texttt{QQ} \\
\text{中间若干用药} & \cdots & \cdots & \cdots \\
\text{纯药效果} & \texttt{---} & \texttt{0000} & \texttt{00} \\
\text{吃纯药后状态} & \texttt{000} & \texttt{QQQQ} & \texttt{QQ} \\
\end{array}
\]
我们发现,吃纯药后把“可治疗”症状全部归 \(\texttt{0}\),也就意味着,如果最后吃这个“纯药”,那么再考虑之前的药物时,不用考虑“可治疗”的那几个症状,因为最后都会被纯药一次性全治好。
因此,我们把纯药从所有药物中删除,所有的药物和主人公症状中,涉及到所删除纯药“可治疗”的症状全部抹去,就转化成了规模更小的问题。我们暂时称这些位置“被覆盖了”。 如表格所示:
\text{项目} & \text{不变} & \text{后遗症} \\
\text{某状态} & \texttt{QQQQ} & \texttt{QQ} \\
\text{中间其他若干用药} & \cdots & \cdots \\
\end{array}
\]
于是我们重复上述过程,在剩下的位置中,找剩下药物中的“纯药”(只考虑剩下的位置来判断)。
最终我们可以求得答案。
5. 具体实现的一些细节
具体实现中,我采用的是类似SPFA的算法(用优先队列,或者说是BFS也行),以及状态更新。我们令状态压缩的01串 \(S\) 表示每一个位置(症状)是否被覆盖。令 \(f_S\) 表示 \(S\) 状态下的最短时间。我们在更新的时候,除了更新 \(S\) 本身外,还要更新其“包含”状态的值(例如 \(\texttt{11001110}\) 中间包含 \(\texttt{10001010}\)):
\]
由于使用优先队列,所以每个状态只访问一次,对应的vis数组记录,判断重复。
其他的位运算等细节请见代码。
记得没病要特判。
代码
#include <bits/stdc++.h>
#define N (int)(12)
#define M (int)(1e3 + 5)
using namespace std;
typedef long long LL;
struct drag
{
LL t,e,se,idx;
}d[M];
LL f[1<<N];
bool vis[1<<N];
LL T;
LL n,m;
string to_str(int x);
struct state
{
LL e,t;
};
bool operator<(const state xx, const state yy)
{
return xx.t > yy.t;
priority_queue<state> q;
void dfs(LL e,LL p, LL t)
{
if(vis[e]) return;
if(e < (1<<p))
{
vis[e] = true;
f[e] = t;
return;
}
if(((e>>p)&1) == 1)
{
dfs(e^(1<<p),p+1,t);
}
dfs(e,p+1,t);
}
LL ansdfs(LL e,LL p)
{
if(e < (1<<p))
{
return f[e];
}
LL ans = 1e18;
if(((e>>p)&1) == 0)
{
ans = min(ans,ansdfs(e|(1<<p),p+1));
}
ans = min(ans,ansdfs(e,p+1));
return ans;
}
inline void setf(LL e,LL t)
{
dfs(e,0,t);
}
inline LL anti(LL x)
{
return (1<<n) - 1 - x;
}
bool check(LL e,LL se)
{
for(LL i = 0;i < n;i++)
{
if((((e>>i)&1) == 0) && (((se>>i)&1) == 1))
{
return false;
}
}
return true;
}
string to_str(int x)
{
string str = "";
for(int i = 0;i < n;i++)
str += ((x>>i)&1) + '0';
return str;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> T;
while(T--)
{
memset(vis,0,sizeof(vis));
memset(f,0x7f,sizeof(f));
cin >> n >> m;
string str;
cin >> str;
LL e0 = 0;
for(LL j = n - 1;j >= 0;j--)
e0 = (e0 << 1) | (str[j] - '0');
for(LL i = 1;i <= m;i++)
{
cin >> d[i].t;
d[i].idx = i;
cin >> str;
d[i].e = 0;
for(LL j = n - 1;j >= 0;j--)
d[i].e = (d[i].e << 1) | (str[j] - '0');
cin >> str;
d[i].se = 0;
for(LL j = n - 1;j >= 0;j--)
{
d[i].se = (d[i].se << 1) | (str[j] - '0');
}
}
if(e0 == 0)
{
cout << "0\n";
continue;
}
q.push({0,0});
while(!q.empty())
{
state top = q.top();
q.pop();
if(!vis[top.e])
{
setf(top.e,top.t);
for(int i = 1;i <= m;i++)
{
if(check(top.e,d[i].se))
{
LL ne = top.e | d[i].e;
if(!vis[ne])
{
q.push({ne,top.t + d[i].t});
}
}
}
}
}
LL ans = ansdfs(e0,0);
if(ans >= 1e9) cout << "-1\n";
else cout << ans << "\n";
}
return 0;
}
本人能力有限,欢迎大家来Hack!
CodeForces CF1846G 题解的更多相关文章
- codeforces#536题解
CodeForces#536 A. Lunar New Year and Cross Counting Description: Lunar New Year is approaching, and ...
- codeforces 1093 题解
12.18 update:补充了 $ F $ 题的题解 A 题: 题目保证一定有解,就可以考虑用 $ 2 $ 和 $ 3 $ 来凑出这个数 $ n $ 如果 $ n $ 是偶数,我们用 $ n / 2 ...
- Codeforces Numbers 题解
这题只需要会10转P进制就行了. PS:答案需要约分,可以直接用c++自带函数__gcd(x,y). 洛谷网址 Codeforces网址 Code(C++): #include<bits/std ...
- Codeforces 691E题解 DP+矩阵快速幂
题面 传送门:http://codeforces.com/problemset/problem/691/E E. Xor-sequences time limit per test3 seconds ...
- Codeforces 833B 题解(DP+线段树)
题面 传送门:http://codeforces.com/problemset/problem/833/B B. The Bakery time limit per test2.5 seconds m ...
- Codeforces 840C 题解(DP+组合数学)
题面 传送门:http://codeforces.com/problemset/problem/840/C C. On the Bench time limit per test2 seconds m ...
- Codeforces 515C 题解(贪心+数论)(思维题)
题面 传送门:http://codeforces.com/problemset/problem/515/C Drazil is playing a math game with Varda. Let’ ...
- Codeforces 475D 题解(二分查找+ST表)
题面: 传送门:http://codeforces.com/problemset/problem/475/D Given a sequence of integers a1, -, an and q ...
- CodeForces CF875C题解
题解 非常有意思的\(2-SAT\)的题. 听学长讲完之后感觉确实容易想到\(2-SAT\),顺理成章. 显然,对于两个串,对咱们来说有意义的显然是两个串中第一个不同的数字.那么,我们假设两个串分别是 ...
- CodeForces CF877D题解(BFS+STL-set)
解法\(1:\) 正常的\(bfs\)剪枝是\(\Theta(nm4k)\),这个时间复杂度是只加一个\(vis\)记录的剪枝的,只能保证每个点只进队一次,并不能做到其他的减少时间,所以理论上是过不了 ...
随机推荐
- 【python爬虫】bilibili每周必看页面视频图片爬取
此博客仅作为交流学习 对于使用bilibili上学习和娱乐的小伙伴们有时会看到视频博主发布的视频封面好看想要得到,但是苦于没有方法,这次我用python来爬取bilibili每周必看页面视频图片. 首 ...
- Notion 中文:客户端、网页端汉化方案
Notion 官方已经正式公布将会支持中文.不过距离正式发布中文版,可能还有一段时间. Notion 的汉化方案 目前,Notion 汉化方案有两种: 客户端汉化 此方法本质上也是在客户端中增加脚本, ...
- 2022-10-23:给你一个整数数组 nums 。如果 nums 的一个子集中, 所有元素的乘积可以表示为一个或多个 互不相同的质数 的乘积,那么我们称它为 好子集 。 比方说,如果 nums =
2022-10-23:给你一个整数数组 nums .如果 nums 的一个子集中, 所有元素的乘积可以表示为一个或多个 互不相同的质数 的乘积,那么我们称它为 好子集 . 比方说,如果 nums = ...
- 2020-10-06:java中垃圾回收器让工作线程停顿下来是怎么做的?
福大大答案2020-10-06: 简单回答:安全点,主动式中断. 中级回答:用户线程暂停,GC 线程要开始工作,但是要确保用户线程暂停的这行字节码指令是不会导致引用关系的变化.所以 JVM 会在字节码 ...
- 又一个开源便斩获 7k star 的新模型「GitHub 热点速览」
Star 并不能代表什么,但是绝对能表示一个项目的受欢迎程度.就像刚开源一周就有 7k+ star 的新模型,输入文本 / 图像就能获得 3D 对象.除了这个新模型,本周还有一款新的 Web 3D 渲 ...
- 【C#/.NET】使用ASP.NET Core对象池
Nuget Microsoft.Extensions.ObjectPool 使用对象池的好处 减少初始化/资源分配,提高性能.这一条与线程池同理,有些对象的初始化或资源分配耗时长,复用这些对象减少初始 ...
- MySQL中字符串查询效率大比拼
背景 最近有个同事对字符串加索引,加完后,发现多了个奇奇怪怪的数字执行的SQL如下: alter table string_index_test add index `idx_name` (`name ...
- Galaxy Project | 一些尝试与思考
很久都没有更新推文了,脑壳羞涩,快码不出字的节奏! 最近在尝试内部 Galaxy 一些新工具的开发和 Galaxy 核心版本的升级测试,发现一些问题,简单记录和聊一下吧. 一些尝试 对于在线的 web ...
- JS异步解决方案及优缺点
1. 回调函数 优点: 解决了同步的问题(只要有一个任务耗时长后面的任务都会等待,会拖延程序执行) 缺点: 回调地狱 不能用try catch捕获 不能用 return setTimeout(( ...
- 洛谷 P5979 [PA2014] Druzyny
简要题意 有 \(n\) 个人,把他们划分成尽可能多的区间,其中第 \(i\) 个人要求它所在的区间长度大于等于 \(c_i\),小于等于 \(d_i\),求最多的区间数量以及如此划分的方案数. 数据 ...