做的第一道交互题……

首先,有解的一个必要条件是\(a>b\)。否则,即当\(a<=b\)时,可以有\(a\)个unkind的人假装自己就是那\(a\)个honest的人。(彼此之间都说是honest的,说别人都是unkind的)

那么,现在我们考虑处理\(a>b\)的情况。

标算

一个显然的思路就是在\(n+1\)次询问内确定一个人是honest的。这样可以再花\(n-1\)询问确定答案。

注意到一个性质:

若\(p\)说\(q\)是unkind的,那么\(p\)和\(q\)不可能都是honest的。

我们发现,如果直接删除\(p\)和\(q\)减小问题规模,那么新问题仍然满足\(a'>b'\),也一定存在honest的人。

那么,我们不禁想知道,对于一个有\(k\)个元素的序列\(a_1a_2a_3...a_k\),怎样的大概\(k\)回答均为Y的询问能让我们确信某个\(a_i\)一定是honest的?

宛如是一只小鸟(题解)把答案告诉了我们,我们发现

\(\forall i, 1 <= i < k,\) \(a_i\)说\(a_{i+1}\)是honest的\(\implies\)

\(a_k\)是honest的。

这是因为若\(a_i\)是honest的,那么\(a_{i+1}\)也是honest的。并且,这里至少有一个人是honest的。那么,我们就可以以如下算法生成\(a_1a_2a_3...a_k\)。

维护一个栈。枚举每一个人:

  • 如何当前栈为空,将其加入栈。
  • 如果栈顶说它是honest的,将其加入栈。
  • 如果栈顶说它是unkind的,放弃它且取出栈顶。

时间复杂度\(O(n)\)。

#include <bits/stdc++.h>
#define gc() getchar()
using namespace std;
const int N = 4010;
int sta[N],top,a,b,ans[N],t,n;
bool ask(int x,int y) {
static char tmp;
printf("? %d %d\n",x,y);
fflush(stdout);
for (tmp = gc() ; tmp != 'Y' && tmp != 'N' ; tmp = gc());
return (tmp == 'Y');
}
int main() {
scanf("%d%d",&a,&b);
if (a <= b) return 0 * puts("Impossible\n");
n = a + b;
for (int i = 0 ; i < n ; ++ i) {
if (!top) sta[++top] = i;
else {
if (ask(sta[top],i))
sta[++top] = i;
else top--;
}
}
t = sta[top];
for (int i = 0 ; i < n ; ++ i)
ans[i] = ask(t,i);
printf("! ");
for (int i = 0 ; i < n ; ++ i)
printf("%d",ans[i]);
puts("");
return 0;
}

顺便一提,还有个高妙的做法。
#### 高妙做法
这个做法基于一个结论:

如\(p\)和\(q\)互相说对方是honest的,那么它们的状态相同。

状态也就是指unkind或honest。显然,这些信息可以用并查集维护。

注意到询问上限\(2n = \sum_{k>=0} \frac {n} {2^k}\),故考虑不断把问题个规模缩小一倍。

下面,直接给出算法及代码:

考虑现在有\(k\)个元素集合,每个集合保证其中的元素状态相同。我们把它们每两个分成一组,丢弃多余出来的一个。对于每一组,花两次询问判断这两个集合是否一定状态相同,是则将它们合并;否则丢弃它们。

对新产生的集合再递归进行这个操作。

最后,令\(t\)为最后一次在分组时因多余而被丢弃的集合。可以证明,\(t\)是honest的。我们向\(t\)询问上面所有被丢弃的集合,得出答案。

#include <bits/stdc++.h>
#define gc() getchar()
using namespace std;
const int N = 4010;
int a,b,n,t,ans[N],q[N],tmp[N],cnt,tcnt,fa[N];
vector<int> threw;
int get_fa(int x) {
return x == fa[x] ? x : fa[x] = get_fa(fa[x]);
}
bool ask(int x,int y) {
static char tmp;
printf("? %d %d\n",x,y);
fflush(stdout);
for (tmp = gc() ; tmp != 'Y' && tmp != 'N' ; tmp = gc());
return (tmp == 'Y');
}
void solve() {
if (cnt == 0) return;
tcnt = 0;
for (int i = 0 ; i+1 < cnt ; i += 2) {
if (ask(q[i],q[i+1]) == 1 && ask(q[i+1],q[i]) == 1) {
fa[q[i+1]] = q[i];
tmp[tcnt++] = q[i];
} else threw.push_back(q[i]), threw.push_back(q[i+1]);
}
if (cnt&1) t = q[cnt-1], threw.push_back(q[cnt-1]);
for (int i = 0 ; i < tcnt ; ++ i) q[i] = tmp[i];
cnt = tcnt;
solve();
}
int main() {
scanf("%d%d",&a,&b);
if (a <= b) return 0 * printf("Impossible\n");
n = a + b;
for (int i = 0 ; i < n ; ++ i)
q[i] = i, fa[i] = i;
cnt = n;
solve();
for (int i = 0 ; i < n ; ++ i) get_fa(i);
for (int i = 0 ; i < (int)threw.size() ; ++ i) {
if (threw[i] == t) continue;
ans[threw[i]] = ask(t,threw[i]);
}
ans[t] = 1;
for (int i = 0 ; i < n ; ++ i)
ans[i] = ans[fa[i]];
printf("! ");
for (int i = 0 ; i < n ; ++ i)
printf("%d",ans[i]);
puts("");
return 0;
}

接下来证明一下为什么这个算法是正确的。

首先证明总询问次数是不多于\(2n\)的。

我们考虑递归中的第\(d+1\)层,此时最多有\(\frac {n} {2^d}\)个元素。那么,单是在这一层的处理中每一个元素平均下来的对询问次数的贡献是\(\frac {1} {2^d}\)。我们记\(a_d\)表示某一个元素从第\(d+1\)层起所能产生的最大贡献。下面考虑两种情况:

  • 这个元素与另一个元素合并,为下一层递归添加了一个元素,也为最后的确定答案产生了\(- \frac {1} {2^{d+1}}\)的贡献。那么,有\(a_d = \frac {1} {2^d} + a_{d+1} - \frac {1} {2^{d+1}}\)。
  • 这个元素未能与另一个元素合并。那么,有\(a_d = \frac {1} {2^d}\)。

故我们得出

\[a_d = \begin{cases}
\max (\frac {1} {2^d} + a_{d+1} - \frac {1} {2^{d+1}},\frac {1} {2^d}),& \text{if $ 0 <= d < \log_2n$} \\
0, & \text{if $d = \log_2n$} \\
\text{undefined}. & \text{otherwise}
\end{cases}\]

稍微YY一下我们就得到,当\(0 <= d < \log_2n\)时,\(a_d = \frac {1} {2^d}\)。

那么,总询问次数不多于\(n \times a_0 + n = 2n\)。

然后,证明为什么\(t\)就是honest的。

首先声明,在递归过程中,可能会出现\(a=b\)。要出现这情况,一个必要条件是在分组时丢弃了一个honest的集合。当然,容易想到不会再出现有\(a<b\)的情况。

我们讨论递归的最后一层,集合个数可能是1或2。

  • 集合个数为1。那么,\(t\)将成为这个集合。因为\(a>=b\),所以这个集合一定是honest的。
  • 集合个数为2。此时,\(a=b=1\)。考虑前面最后一层导致了\(a=b\)的递归。在那里,因分组而丢弃了一个honest的集合\(S\)。那之后的每一层递归中,一定有\(a=b\)(否则那就不是最后一层导致\(a=b\)的递归),则集合个数为偶数,不会再因分组而丢弃集合。因此,\(t\)就是\(S\),是honest的。

现在,我们就可以确信,这个算法没问题了。

时间复杂度\(O(n \alpha (n))\)。

小结:感觉根本想不出那么巧妙的东西。或许是思维能力还不够吧。

【做题】arc070_f-HonestOrUnkind——交互+巧妙思维的更多相关文章

  1. 【做题】CF177G2. Fibonacci Strings——思维+数列

    题意:定义斐波那契字符串为: $f_1 = $ "a" \(f_2 =\) "b" \(f_n = f_{n-1} + f_{n-2}, \, n > 2 ...

  2. 【做题】apc001_f-XOR Tree——巧妙转化及dp

    对树上的路径进行操作是十分难处理的事情.一开始的思路主要针对于\(a_i<=15\)这一特殊性质上.于是考虑了\(a_i<=1\)的情况,然而除了糊出一个适用范围极小的结论外,并没有什么用 ...

  3. AtCoder Grand Contest 11~17 做题小记

    原文链接https://www.cnblogs.com/zhouzhendong/p/AtCoder-Grand-Contest-from-11-to-20.html UPD(2018-11-16): ...

  4. AtCoder Grand Contest 1~10 做题小记

    原文链接https://www.cnblogs.com/zhouzhendong/p/AtCoder-Grand-Contest-from-1-to-10.html 考虑到博客内容较多,编辑不方便的情 ...

  5. 【做题】BZOJ2342 双倍回文——马拉车&并查集

    题意:有一个长度为\(n\)的字符串,求它最长的子串\(s\)满足\(s\)是长度为4的倍数的回文串,且它的前半部分和后半部分都是回文串. \(n \leq 5 \times 10^5\) 首先,显然 ...

  6. noip做题记录+挑战一句话题解?

    因为灵巧实在太弱辽不得不做点noip续下命QQAQQQ 2018 积木大赛/铺设道路 傻逼原题? 然后傻逼的我居然检查了半天是不是有陷阱最后花了差不多一个小时才做掉我做过的原题...真的傻逼了我:( ...

  7. 【做题记录】AtCoder AGC做题记录

    做一下AtCoder的AGC锻炼一下思维吧 目前已做题数: 75 总共题数: 239 每一场比赛后面的字母是做完的题,括号里是写完题解的题 AGC001: ABCDEF (DEF) AGC002: A ...

  8. FJOI2017前做题记录

    FJOI2017前做题记录 2017-04-15 [ZJOI2017] 树状数组 问题转化后,变成区间随机将一个数异或一,询问两个位置的值相等的概率.(注意特判询问有一个区间的左端点为1的情况,因为题 ...

  9. NOIP2016考前做题(口胡)记录

    NOIP以前可能会持续更新 写在前面 NOIP好像马上就要到了,感觉在校内训练里面经常被虐有一种要滚粗的感觉(雾.不管是普及组还是提高组,我都参加了好几年了,结果一个省一都没有,今年如果还没有的话感觉 ...

随机推荐

  1. 代码审查Code Review

    代码审查清单 常规项 代码能够工作么?它有没有实现预期的功能,逻辑是否正确等. 所有的代码是否简单易懂? 代码符合你所遵循的编程规范么?这通常包括大括号的位置,变量名和函数名,行的长度,缩进,格式和注 ...

  2. Web API 跨域请求

    分布式技术在项目中会频繁用到,以前接触过WebService(可跨平台).WCF(功能强大,配置繁琐),    最近由于上层业务调整,将原来的MVC项目一分为三,将数据层提取出来,用API去访问.然后 ...

  3. zookeeper日志清理

    环境 Red Hat Enterprise Linux Server release 7.3 (Maipo) jdk1.7.0_80      zookeeper-3.4.11 一.事务日志和快照日志 ...

  4. win10 Xshell5连ubuntu服务器

    相当于ubuntu做服务器,win10远程访问,同一局域网下,完全参考度娘 教你怎么使用xshell远程连接linux服务器 ubuntu系统ifconfig -a查看ip地址,添加到主机那里,连接时 ...

  5. 谈谈Groovy闭包

    A closure is a function with variables bound to a context or environment in which it executes. 概述 闭包 ...

  6. LDA模型了解及相关知识

    什么是LDA? LDA是基于贝叶斯模型的,涉及到贝叶斯模型离不开“先验分布”,“数据(似然)”和"后验分布"三块.贝叶斯相关知识:先验分布 + 数据(似然)= 后验分布. 贝叶斯模 ...

  7. 微信小程序制作家庭记账本之六

    第六天,自己根据样本代码做的记账本可实现添加账目,删除账目和消费总和,实机可以测试,但是没有域名,别人无法访问.

  8. LUHN 模10 算法 银行卡校验

    信用卡Luhn算法(模10)具体的校验过程如下: 1.从卡号最后一位数字开始,逆向将奇数位(1.3.5等等)相加. 2.从卡号最后一位数字开始,逆向将偶数位数字,先乘以2(如果乘积为两位数,则将其减去 ...

  9. Java多线程循环打印ABC的5种实现方法

    https://blog.csdn.net/weixin_39723337/article/details/80352783 题目:3个线程循环打印ABC,其中A打印3次,B打印2次,C打印1次,循环 ...

  10. <转>jmeter(一)基础介绍

    本博客转载自:http://www.cnblogs.com/imyalost/category/846346.html 个人感觉不错,对jmeter讲解非常详细,担心以后找不到了,所以转发出来,留着慢 ...