CF1442F

以下内容搬运自cf官方题解

真是毒瘤。

先考虑对于一张空图怎么做。

如果最后是一张有向无环图,则每一个点有唯一的SG函数值。通过嬴或者是输的询问只能确定目标节点的SG函数值,对于SG函数值相同的点便无能为力。

于是需要 $O(n^2)$ 条边使得每一个点的SG值均不同,而且每次询问只能排除一个点,单个阶段需要 $O(n)$ 次询问,这样无论如何都是无法通过的。

于是需要利用环的性质。

但是有环时不容易判断游戏的结果。

考虑到有一个点有连向自己的边并且该点上有至少一枚棋子,则先手不会失败(没有必胜策略时,操作方可以沿自环移动已保证不败)。

考虑将点集划分为 $S_1$ 和 $S_2$ ,不存在边 $e:i->j$ 同时使 $i \in S_1$ 和 $j \in S_2$ 成立,且:

$$\underset{i \in S_1}{\text{mex}} \space SG_i=|S_1|+1$$

设 $j \in S_2$ 出边连向点与 $S_1$ 交集为 $T_j$ ,且 $T_j$ 互不相同, $S_2$ 中所有点向自己有边。

然后,牛逼的来了,只需要做 $|S_1|$ 次询问,每次询问一个点,便可确定答案。

假设答案是点 $x$ 。

如果询问点 $i$ 时先手必败,说明 $x \notin S_2$ (原因如上),由 $SG_i \space \text{xor} \space SG_x=0$ 知 $x=i$ 。

如果没有先手必败的情况,则平局点集即为 $T_x$ !证明显然。

只需取 $|S_1|=20$ ,连最少的边使 $T_j$ 互不相同即可。

非空图类似,用拓扑序找 $S_1$ ,然后改变最少的边使 $T_j$ 互不相同(可以删边)即可。

稍加计算得改变边数在 $4000$ 以内,可以通过本题。

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
int D=20;

int n,m,q,cnt=-1;
int fir[1000],in[1000];
int nxt[200000],to[200000];
int tp[1000],pos[1000];
int have[1<<20];
int K=0,A[10000],B[10000];
bool C[10000];
inline void addedge(int a,int b,bool c)
{
    A[K]=a; B[K]=b; C[K++]=c;
    return;
}
inline void add(int a,int b)
{
    to[++cnt]=b;
    in[b]++;
    nxt[cnt]=fir[a];
    fir[a]=cnt;
    return;
}
#include<queue>
queue<int> que;
void topo(void)
{
    for(int i=0;i<n;i++)
        if(in[i]==0){
            tp[cnt++]=i;
            que.push(i);
        }
    while(que.size()>0){
        int i=que.front();
        que.pop();
        for(int j=fir[i];j!=-1;j=nxt[j]){
            in[to[j]]--;
            if(in[to[j]]==0){
                tp[cnt++]=to[j];
                que.push(to[j]);
            }
        }
    }
    return;
}

void changeS(int p,int S)
{
    for(int p1=D;p1>=0;p1--)
        for(int p2=(p1==D?D:p1-1);p2>=0;p2--)
            for(int p3=(p2==D?D:p2-1);p3>=0;p3--){
                int S2=(S^(1<<p1)^(1<<p2)^(1<<p3))&((1<<D)-1);
                if(have[S2]==-1){
                    have[S2]=p;
                    if(p1<D) addedge(p,tp[n-D+p1],!(S>>p1&1));
                    if(p2<D) addedge(p,tp[n-D+p2],!(S>>p2&1));
                    if(p3<D) addedge(p,tp[n-D+p3],!(S>>p3&1));
                    return;
                }
            }
    return;
}
void edge(void)
{
    for(int i=0;i<n;i++)
        pos[tp[i]]=i;
    for(int i=n-D;i<n;i++)
        for(int j=i+1;j<n;j++)
            addedge(tp[i],tp[j],1);
    for(int i=0;i<n;i++){
        if(pos[i]>=n-D) continue;
        addedge(i,i,1);
        int S=0;
        for(int p=fir[i];p!=-1;p=nxt[p]){
            int j=to[p];
            if(pos[j]>=n-D)
                S|=(1<<(pos[j]-n+D));
        }
        changeS(i,S);
    }
    return;
}

int main(void)
{
    scanf("%d%d%d",&n,&m,&q);
    if(D>n) D=n;
    for(int i=0;i<n;i++)
        fir[i]=-1;
    for(int i=0;i<(1<<D);i++)
        have[i]=-1;
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a-1,b-1);
    }
    cnt=0; topo(); edge();
    printf("%d\n",K);
    for(int i=0;i<K;i++)
        if(C[i]==1) printf("+ %d %d\n",A[i]+1,B[i]+1);
        else printf("- %d %d\n",A[i]+1,B[i]+1);
    fflush(stdout);
    while(q--){
        char s[20];
        int S=0;
        for(int i=0;i<D;i++){
            printf("? 1 %d\n",tp[i+n-D]+1);
            fflush(stdout);
            scanf(" %s",s);
            if(s[0]=='L'){
                printf("! %d\n",tp[i+n-D]+1);
                fflush(stdout);
                break;
            }
            if(s[0]=='W') S|=(1<<i);
        }
        if(s[0]!='L'){
            printf("! %d\n",have[S]+1);
            fflush(stdout);
        }
        scanf(" %s",s);
        if(s[0]=='W') return 0;
    }
    return 0;
}
View Code
posted @ 2021-05-02 14:16  Miracle_Creater  阅读(206)  评论(0)    收藏  举报