• 【做题】arc070_f-HonestOrUnkind——交互+巧妙思维


    做的第一道交互题……
    首先,有解的一个必要条件是(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
    ",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 = 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
    ",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 = 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 = egin{cases} max (frac {1} {2^d} + a_{d+1} - frac {1} {2^{d+1}},frac {1} {2^d}),& ext{if $ 0 <= d < log_2n$} \ 0, & ext{if $d = log_2n$} \ ext{undefined}. & ext{otherwise} end{cases}]

    稍微YY一下我们就得到,当(0 <= d < log_2n)时,(a_d = frac {1} {2^d})
    那么,总询问次数不多于(n imes 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)))


    小结:感觉根本想不出那么巧妙的东西。或许是思维能力还不够吧。
  • 相关阅读:
    .net SMTP发送Email 更新(可带附件)
    Ext JS4百强应用: 做可编辑的,可checked的treegrid--第11强
    使你更有思想的20本书
    Sencha Architect 2 的使用
    Test SRM Level One: TemperatureScales
    Test SRM Level Two: CountExpressions, Brute Force
    JPDA 利用Eclipse和Tomcat进行远程调试 --转
    非对称加密算法RSA--转
    java/php/c#版rsa签名以及java验签实现--转
    JAVA/PHP/C#版RSA验签--转
  • 原文地址:https://www.cnblogs.com/cly-none/p/9021812.html
Copyright © 2020-2023  润新知