题目大意
给出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,多发现题目的一些特殊性,降低编程复杂度。比如判断这个点有没有取完,直接查询从汇点或源点到这个点的流量用完没有就行。而且这里为了方便可以用邻接矩阵储存。