JZOJ4698 A Game of Thrones

题目大意

给出n种不同的数,每种数x[i]有cnt[i]个。两个人A,B轮流操作。A先手,选择一个数x并删除。接下来每一次操作,操作者能删除的数x’需要满足一下任意一个条件
1,x

分析

直接暴力就不说了。
首先从操作入手:若上一次选了x,下一次肯定只能选x/pri或x*pri,我们把它看成从一个点有一条边连向另一个点,那么这个图是个二分图(质数的性质)。
二分图就有搞头了,我们来考虑某种情况下,A,B的策略与胜负情况。匹配吗?从哪里开始?先想想看完美匹配时,毕竟简单一点。在这种情况下,A选某一个数,B只要选这个数匹配的另一个数,那么最后一次操作肯定是属于B的,这时候所有匹配的点对都取完了,A必输。
想到这一点,我们可以想一下不是完美匹配:如果A选了一个点不一定属于最大匹配,那B无论怎样都要选到最大匹配里的点。
这个时候,只要A选择与B选的点匹配的对应点,那么情况就跟完美匹配一样了,只不过这里可以看成是A制造的,让B先手的完美匹配局面。
所以,这种情况B必输。

这样看来,我们构个图跑个网络流就能知道谁必胜了。具体方法:假设我们已经获得二分图。首先对二分图染色。然后构图:
1,对于一个白点i,我们让源点向他们连流量为cnt[i]的边,并向二分图中原本连向的黑点连流量为正无穷;
2,对于黑点j,向汇点连流量为cnt[j]的边。
这之后跑一遍最大流就行了。

但怎么解决要输出的第二个东西?首先转化问题:判断某一个点在网络流上是不是一定属于最大匹配?我们只要把经过这个点的一条增广路退流1,把这个点到源点或者汇点的边的容量-1,再重新找新的增广路,找得到,这个点就是不必要的。
当然了,如果原本到源点或者汇点的边就没有满流,就意味着这种数有剩余,肯定是可以作为A选的第一个数了。
找增广路方法就是从汇点沿着有流量的反向弧bfs跑回去。那么一次退流并重新增广的时间复杂度 O(n2) ,枚举哪一个数O(n),总共 O(n3)

其实这里还有最后一个问题,就是你要判断一个数是不是质数, 1018 使你不能直接分解质因数来做。那我们用miller_rabin就行。

代码

#include<cstdio>
#include<algorithm>
#include<ctime>
#include<cmath>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=505,M=50005,mx=1000000009;
const ll mx1=1000000000000000005;
ll x[N],y[N],x11,y11,z11,prt,ans;
int ret[N],i,j,ed[N][N],dis[N],tp,ttt,tk,tf,q1,q2,pd[N],e[N],f[N],rev[M],n,b[M],next[M],c[M],first[M],go[N][N],tt,d[M],pdd[N],col[N],cnt,dur,kx;
void dfs(int x,int sig)
{
    col[x]=sig;
    for(int p=1;p<=n;p++)
        if (!col[p]&&go[x][p])
            dfs(p,-sig);
}
ll cheng(ll x,ll y,ll mo)
{
    ll ret=0;
    int i,ta[70];
    for(i=0;i<=62;i++)
    {
        ta[i]=y%2;
        y/=2;
    }
    for(i=62;i>=0;i--)
    {
        ret=ret*2%mo;
        if (ta[i]) 
            ret=(ret+x)%mo;
    }
    return ret;
}
ll ksm(ll x,ll y,ll mo) 
{
    if (y==0) return 1;
    if (y==1) return x;
    ll dur=ksm(x,y/2,mo);
    return cheng(cheng(dur,dur,mo),ksm(x,y%2,mo),mo);
}
bool prime(ll z11)
{
    ll tp;
    for(int i=1;i<=20;i++)
    {
        do
        {
            tp=(ll)(ll)rand()*(ll)rand()%z11;
        }while (!tp);
        if (ksm(tp,z11-1,z11)!=1) return 0;
    }
    return 1;
}
void cr(int x,int y,int z,int t)
{
    tt++;
    b[tt]=y;
    c[tt]=t;
    rev[tt]=z+tt;
    next[tt]=first[x];
    first[x]=tt;
}
int flow(int x,int y)
{
    pdd[x]=ttt;
    f[++tf]=x;
    if (x==n+1)
    {
        ans+=(ll)y;
        return y;
    }
    for(int p=first[x];p;p=next[p])
        if (pdd[b[p]]!=ttt&&c[p])
        {
            int l=flow(b[p],min(y,c[p]));
            if (l)
            {
                c[p]-=l;
                c[rev[p]]+=l;
                return l;
            }
        }
    tf--;
    return 0;
}
void redo(int tar)
{
    if (col[tar]==1&&c[ed[0][tar]]!=0||col[tar]==-1&&c[ed[tar][n+1]]!=0)
    {
        prt=min(prt,x[tar]);
        return;
    }
    q1=0;
    q2=1;
    d[1]=n+1;
    dis[1]=1;
    tp++;
    while (q1<q2)
    {
        q1++;
        for(int p=first[d[q1]];p;p=next[p])
            if (pd[b[p]]!=tp&&rev[p]<p&&c[p])
            {
                pd[b[p]]=tp;
                d[++q2]=b[p];
                ret[q2]=q1;
                dis[q2]=dis[q1]+1;
            }
    }
    fo(i,1,q2) if (d[i]==tar) break;
    tk=dis[i];
    for(i;i!=1;i=ret[i]) e[dis[i]]=d[i];
    e[1]=n+1;
    d[1]=tar;
    tp++;
    fo(i,1,tk) pd[e[i]]=tp;
    q1=0;
    q2=1;
    dis[1]=tk;
    while (q1<q2)
    {
        q1++;
        for(int p=first[d[q1]];p;p=next[p])
            if (pd[b[p]]!=tp&&rev[p]<p&&c[p])
            {
                pd[b[p]]=tp;
                d[++q2]=b[p];
                ret[q2]=q1;
                dis[q2]=dis[q1]+1;
            }
    }
    fo(i,1,q2) if (!d[i]) break;
    tk=dis[i];
    for(i;i!=1;i=ret[i]) e[dis[i]]=d[i];
    fo(i,1,tk-1)
    {
        dur=ed[e[i]][e[i+1]];
        c[dur]--;
        c[rev[dur]]++;
    }
    if (col[tar]==1)
    {
        c[ed[0][tar]]--;
    }
    else
    {
        c[ed[tar][n+1]]--;
    }
    ll tmp=ans;
    ans--;
    ttt++;
    tf=0;
    flow(0,mx);
    if (ans==tmp)
    {
        prt=min(prt,x[tar]);
        fo(i,1,tf-1) 
        {
            dur=ed[f[i]][f[i+1]];
            c[dur]++;
            c[rev[dur]]--;
        }
    }
    if (col[tar]==1)
    {
        c[ed[0][tar]]++;
    }
    else
    {
        c[ed[tar][n+1]]++;
    }
    fo(i,1,tk-1)
    {
        dur=ed[e[i]][e[i+1]];
        c[dur]++;
        c[rev[dur]]--;
    }
}
int main()
{
    srand(time(0));
    scanf("%d",&n);
    fo(i,1,n)
        scanf("%lld%lld",x+i,y+i);
    fo(i,1,n)
        fo(j,i+1,n)
        {
            x11=x[i];
            y11=x[j];
            if (x11<y11)
            {
                z11=x11;
                x11=y11;
                y11=z11;
            }
            if (x11%y11) continue;
            z11=x11/y11;
            if (z11%2==0&&z11!=2) continue;
            if (prime(z11)) 
            {
                go[i][j]=1;
                go[j][i]=1;
            }
        }
    fo(i,1,n)
        if (!col[i])
            dfs(i,1);
    fo(i,1,n)
        if (col[i]==1)
            fo(j,1,n)
                if (go[i][j])
                {
                    cr(i,j,1,mx);
                    ed[i][j]=tt;
                    cr(j,i,-1,0);
                    ed[j][i]=tt;
                }
    fo(i,1,n)
    {
        if (col[i]==1)
        {
            cr(0,i,1,y[i]);
            ed[0][i]=tt;
            cr(i,0,-1,0);
            ed[i][0]=tt;
        }
        else
        {
            cr(i,n+1,1,y[i]);
            ed[i][n+1]=tt;
            cr(n+1,i,-1,0);
            ed[n+1][i]=tt;
        }
    }
    ttt++;
    prt=mx1;
    while (flow(0,mx)) 
    {
        ttt++;  
        tf=0;
    },
    fo(kx,1,n)  
        redo(kx);
    if (prt!=mx1)
        printf("Bran %lld",prt);
    else
        printf("Tyrion");
}

点评

这道题细节巨多,一不小心容易打错,特别是还要经常注意哪些变量是long long或int,这是一道比较有趣的题目吧,想的时候是很爽,只不过写的时候···呵呵。

不足

1,中间tmp是用来临时储存ans的值的,一开始没有开long long,原本贪方便直接用了另一个int变量,所以说注意long long与int的存储与切换;
2,有个数组范围开小了,以后打题最好把数组分类来声明;
3,多发现题目的一些特殊性,降低编程复杂度。比如判断这个点有没有取完,直接查询从汇点或源点到这个点的流量用完没有就行。而且这里为了方便可以用邻接矩阵储存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值