ARC184 随便写点
最逆天的一集。
ARC184 A#
题目解析#
关键观察:注意到 很小,只有 ,并且 。考虑有什么性质。
我们发现 很小,我们考虑挖掘性质。
有一个关键观察:如果有大于 个数的类型相等,那么他们一定都是好的。
如果我们知道了 个数中的相对情况,假设有 个数类型相等,其他 个数类型相等,这两种数类型不同,由于上面的性质,当 时,数量较多的一类必定是好数,数量较少的一类必定是坏数。
按照上面的启发,我们考虑将 个数进行分组,每组内有 个数。我们计算这 个数的相对情况(这是容易的),然后根据上面的结论,我们就能知道当一组内两种类型数目不相等时它们的好坏之分。
因此,我们只需要考虑 的情况(这种组别显然最多只有一个)。这好办,我们只需要将在这组内的任意一个数和不在这组内的任意一个数比较,这样我们就能区分出哪一种类别是好数。
需要注意一个细节:这样算下来最多的询问次数会到达 ,所以需要轻微的调整来解决这一问题。具体比较简单,可以看代码。
void solve() {
cin >> n >> m >> q;
vector <int> ans;
for (int i = 1; i <= 50; i ++) {
int l = (i - 1) * 20 + 1, pos = 0, cnt0 = 1, cnt1 = 0;
int tar = -1;
for (int j = 2; j <= 20 && (i != 50 || !ans.empty()); j ++) {
cout << "? " << l << ' ' << l + j - 1 << endl;
cin >> col[l + j - 1];
cnt0 += (col[l + j - 1] == 0);
cnt1 += (col[l + j - 1] == 1);
}
if (i == 50 && ans.empty()) {
for (int j = 1; j <= 19; j ++) {
cout << "? " << 1 << ' ' << j + l - 1 << endl;
cin >> col[j + l - 1];
cnt0 += (col[l + j - 1] == 0);
cnt1 += (col[l + j - 1] == 1);
if (col[j + l - 1] == 1) ans.push_back(j + l - 1);
}
if (cnt1 == 9)
ans.push_back(1000);
break;
}
if (cnt0 < cnt1) {
for (int j = 1; j <= 20; j ++)
if (col[l + j - 1] == 0) ans.push_back(l + j - 1);
} else if (cnt0 > cnt1) {
for (int j = 1; j <= 20; j ++)
if (col[l + j - 1] == 1) ans.push_back(l + j - 1);
} else {
pos = (i == 1 ? 1000 : 1);
cout << "? " << l << ' ' << pos << endl;
cin >> tar;
tar ^= 1;
for (int j = 1; j <= 20; j ++)
if (col[l + j - 1] == tar) ans.push_back(l + j - 1);
break;
}
}
cout << "! ";
for (int x : ans) cout << x << ' ';
cout << endl;
}
ARC184 B#
ARC184 C#
ARC184 D#
关键转换:删点不是很好考虑,所以转换成选点。
首先我们考虑选择的点集有多少不同的。我们先对第一维 坐标进行排序,然后容易想到 。
显然,选择的点集中相邻两个点 肯定需要满足 。据此,可以想到一种比较 naive 的 方法:设 表示当前考虑了前 个点(选择了 ),枚举 ,如果 则可以直接转移。
但是上面这个 有一个显然的漏洞:重复。因为对于一个删除的点集,可能有多个选择的点集能得到它。因此,我们需要在这些结果相同的点集中选择出一种具有代表性且容易计算的方案。
关键观察:我们再加一条限制,选择任意一个没被删除且没被操作的点,操作它之后一定会删除一个其他点。
通过上面这一个限制,我们就有了一个比较明确的 思路:设 表示当前考虑了前 个点(选择了 )的方案数,然后每次枚举 ,并且判断这一转移合不合法。
假设现在有一个选择的点集,我们要判断它是否合法。显然,我们设所有没被删除且没被操作的点所在的点集为 ,然后在点集 中遍历每个点 判断 前面是否存在 使得 或者 后面是否存在 使得 。
但是现在还有一个问题:在 的转移中如何判断该方案是否合法?
关键观察:我们称相邻两个已操作的点之间所有没被删除的点为一个段。那么,任意两个段之间没有任何关系。即,设左边的点集为 ,右边的段的点集为 ,那么 。
上面这个观察是显然的,但很重要。那么,我们就可以在转移的过程中,只判断当前这一段是否满足要求即可。
void solve() {
n = read();
for (int i = 1; i <= n; i ++)
t[i].x = read(), t[i].y = read();
sort(t + 1, t + n + 1);
t[0] = {0, n + 1}, t[n + 1] = {n + 1, 0};
dp[0] = 1;
for (int i = 1; i <= n + 1; i ++) {
for (int j = 0; j < i; j ++) {
if (t[j].y <= t[i].y) continue;
for (int k = 1; k <= n; k ++)
vis[k] = 0;
bool flg = 1;
long long mn = 2e18, mx = -2e18;
for (int k = j + 1; k < i; k ++) {
if (!(t[j].y > t[k].y && t[k].y > t[i].y))
continue;
vis[k] |= (mn < t[k].y);
mn = min(mn, t[k].y);
}
for (int k = i - 1; k > j; k --) {
if (!(t[j].y > t[k].y && t[k].y > t[i].y))
continue;
vis[k] |= (mx > t[k].y);
mx = max(mx, t[k].y);
}
for (int k = j + 1; k < i; k ++) {
if (!(t[j].y > t[k].y && t[k].y > t[i].y))
continue;
flg &= (vis[k] == 1);
}
if (flg) {
(dp[i] += dp[j]) %= mod;
}
}
}
cout << dp[n + 1];
}
作者:DE_aemmprty
出处:https://www.cnblogs.com/aemmprty/p/18425972
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库