abc310d <dfs暴搜-分组方案数 / bitmask表示集合+dp>
1.abc306e <mutiset的使用>2.abc053d <简单贪心>3.abc042d <组合数>4.abc043d5.abc045d6.abc044d <根号分治>7.abc046d8.abc047d9.abc048d <博弈>10.abc049d <并查集>11.abc050d <???>12.abc051 <多源最短路>13.abc052d14.abc054d <dp, 背包>15.abc055d <枚举>16.abc056d <贪心 / 二分+DP+bitset>17.abc057d <贪心+组合计数>18.abc058d <公式化简>19.abc059d <博弈, 打表找规律>20.abc060d <dp, 背包>21.abc061d <单源最短路, spfa, 判断负环>22.abc062d <优先队列>23.abc063d <二分答案>24.abc064d <贪心/前缀和>25.abc309f <线段树 + 离散化 + 双指针>26.abc309e <dfs>27.abc065d <贪心+最小生成树> [lambda表达式]28.abc066d <组合>29.abc067d <博弈 + dfs>30.abc068d <思维 + 构造>31.abc069d <构造>32.abc070d <简单树上dfs>33.abc071d <递推>34.abc072d <贪心>35.abc073d <Floyed + 枚举排列>36.abc074d <Floyed 消除传递边>37.abc075d <暴力枚举 / 枚举+离散化+二维前缀和>38.abc076d <dp / 贪心>39.abc077d <思维 + 最短路 (将构造数字过程视为最短路)>40.abc078d <博弈>41.abc079d <Floyed>42.abc080d <区间重叠>43.abc081d <思维 构造>44.abc082d <bitset 状压dp>45.abc083d <思维 贪心>46.abc084d <素数筛 前缀和>47.abc085d <贪心>48.abc086d <二维前缀和 同余>49.abc087d <并查集 维护距离信息>50.abc088 <bfs 最短路>51.abc089 <前缀和>
52.abc310d <dfs暴搜-分组方案数 / bitmask表示集合+dp>
53.abc310e <公式递推(dp?)>54.abc310f <dp + bitmask>55.abc090d <枚举计数>56.abc312c <二分答案>57.abc312d <dp, 括号匹配方案数>58.abc312e <暴力>59.abc320f <dp >60.abc092d<构造,思维>61.abc094d<组合数>62.abc095d<思维>63.abc096d<素数筛,整除>64.abc097d<并查集,排列>65.abc098d<双指针,异或>66.abc099d<dfs,枚举排列方案>67.abc100d<枚举>68.abc101d<打表,数学>题目
D - Peaceful Teams
参考:
https://www.cnblogs.com/legendstane/p/freee-programming-contest-2023-atcoder-beginner-contest-abc-310-solution.html
https://blog.csdn.net/Muelsyse_/article/details/131747083
思路
方法1 dfs暴搜
- 由于数据范围极小, 所以直接暴力即可
- dfs的顺序依据: dfs每层将一个player进行分组选择
- 如何保证不重复枚举: 从小到大分配player, 新建小组时, 每组的第一个人一定是当前未分配组别的player中序号最小者, 这使得小组1~t中每个小组中player序号为升序, 且t个小组中的第一名player的序号也为升序;
- dfs每个player的选择: 选择加入现有的cnt个小组中的一个; 或新建第cnt+1个小组, 成为这个小组的第一个人;
方法2 bitmask枚举状态 + dp
- 注意到数据范围 N<10, 这样的范围即可暴力枚举
- 而这样的数据范围, 很适合通过二进制位来枚举状态; 且二进制位很适合表示集合状态(某个元素集合中有1 or 没有0);
- 注意: 这里也存在如何保证不重复枚举的问题(因为组间是没有顺序的), 这里保证了每个新的小组中, 包含未被分组的player中序号最小者;
总结
- 数据范围
- 分组方案的dfs方式
- bitmask表示集合, 进行分组枚举
- 如何保证分组方案不重复
代码
1. dfs 代码
// https://atcoder.jp/contests/abc310/tasks/abc310_d
// dfs暴搜 分组方案数
// dfs 依次考虑每个人 u=1~n 分到哪个组
// 当前共有cnt个组, 每个人可选择分到 1~cnt组 或者新建一个组
// 避免重复的方式: 从1到n的顺序考虑每个人, 如果建立新组, 也只能顺序建立第cnt+1个小组(并加入这个小组)
// 这样能够使得最终的t个小组的每组的第一个人(建立这个小组的人)的序号为升序,
// 且每组人数不为空;
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
const int N = 12;
bool st[N][N];
vector<int> team[N];
int cnt;
int n, t, m;
int ans;
// 考虑编号为u的player的分组情况
void dfs(int u)
{
if (cnt > t)
return; // 分组超过t无效
if (u > n) // 当u=1~n都分配完毕
{
if (cnt == t) // 恰好分了t组
{
bool flag = true; // 检查组内是否有冲突
for (int i = 1; i <= t; i++)
for (auto x : team[i])
for (auto y : team[i])
if (st[x][y]) flag = false;
if (flag) ans++;
}
return;
}
for (int i = 1; i <= cnt; i++) // 将第u个人分到已有的 1~cnt 组
{
team[i].push_back(u);
dfs(u + 1);
team[i].pop_back();
}
// 第u人新建一组(第cnt+1组)
team[++cnt].push_back(u);
dfs(u + 1);
team[cnt--].pop_back();
}
void solv()
{
cin >> n >> t >> m;
while (m--)
{
int a, b;
cin >> a >> b;
st[a][b] = st[b][a] = true;
}
dfs(1);
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solv();
}
return 0;
}
2. bitmask表示集合 dp 代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
LL n, t, m, bad[20][20], badm[1100], dp[20][1100];
int main()
{
cin >> n >> t >> m;
for (int i = 0; i < m; i++)
{
int x, y;
cin >> x >> y;
bad[x - 1][y - 1] = bad[y - 1][x - 1] = 1; // 标记非法组合 (序号从0开始)
}
for (int i = 0; i < 1 << n; i++) // 遍历集合所有状态, 这里视为同一个小组中的组合
for (int j = 0; j < n; j++) // j, k 检查状态 i 中是否存在非法组合
for (int k = 0; k < n; k++)
if (j != k && (i & (1 << j)) && (i & (1 << k)) && bad[j][k]) // 标记非法状态为 1
badm[i] = 1;
dp[0][0] = 1; // 0 组, 总状态为 0, 方案为 1
for (int i = 0; i < t; i++) // 已有i组, 计算当i+1组建立时, 各状态下的方案时
for (int j = 0; j < 1 << n; j++) // 第i组状态
if (dp[i][j] && j != ((1 << n) - 1)) // 前i组, 总状态为j, 方案数不为0, 且总状态中人数未满
{
int tmp;
for (int k = 0; k < n; k++) // 找到在状态j种, 未分配的player的最小编号对应的bitmask -> tmp
if (!(j & (1 << k)))
{
tmp = 1 << k;
break;
}
for (int k = 0; k < 1 << n; k++) // 枚举 第i+1组 的状态
if (!(j & k) && (k & tmp) && !badm[k]) // 如果前i组状态j与第i+1组状态k没有冲突, k状态中包含tmp, 并且k不是非法状态
dp[i + 1][j | k] += dp[i][j]; // 注意, 由if中前两个条件, 可得的新组别k中, 最小序号为当前未选出的player中的最小序号, 这样可保证不重复枚举
}
cout << dp[t][(1 << n) - 1] << endl;
return 0;
}
本文来自博客园,作者:O2iginal,转载请注明原文链接:https://www.cnblogs.com/o2iginal/p/17557162.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律