CF1266H. Red-Blue Graph
有个图,\(1\)到\(n-1\)的点每个点有两条出边,分别为红色和蓝色。一开始人在点\(1\)且所有点的激活边为蓝色。\(n\)没有出边。
每个时间点,当前点的激活边切换且人立即沿着切换后的激活边走下去。如果到达点\(n\)则停止。
给出图和每个点的红蓝出边,接下来有若干个询问,询问某个状态(\(v,S\)表示当前点为\(v\)(\(v<n\)),激活边的状态为\(S\))是否可达。
\(n\le 58,Q\le 5000\)
保证从任意点出发存在路径到达点\(n\)。(此时不考虑上面走的规则,即走边的时候没有限制)
最后才发现原来题目开\(n\le 58\)似乎是因为\(57*2^{57}\)在long long
范围内。
结论:状态不会成环。
假设成环,即从某个状态出发一直走最终回到了这个状态,设在这个过程中经过的点集为\(S\)。显然任意边\((u,v)\in E,u\in S\)都被走过一次,并且由\(S\)的定义得\(v\in S\)。因此\(S\)中的点没有连向\(S\)外的点的边,成为一个极大的强联通分量,此时任意\(S\)内的点都不能经过有向边到达\(n\)。
考虑求出每条边被经过的次数。设\(x_i\)表示\(R_i\)的经过次数,\(x_i-z_i\)表示\(B_i\)的经过次数,以及\(w_i\)表达\(i\)的不经过\(R_i,B_i\)的额外流出(即如果\(i\)为当前点\(w_i+1\),如果\(i\)起点\(w_i-1\)),然后根据\(F_{入}=F_{出}\)列出\(n-1\)个等式。\(z_i\)和\(w_i\)由每个询问内容决定,所以把它们当做常数带入。于是就可以解出\(x_i\)的关于\(z,w\)的表达式。
由于题目保证了状态不会成环,所以解出的\(x_i\)一定是唯一解的。
合法的判定:
- \(x_i\)是非负整数。
- 对于所有至少经过一次的点,都可以只通过当前状态下的激活边到达当前点。必要性显然,充分性大概可以归纳。
时间\(O(n^3+qn^2)\)。
注意实现的时候如果用long double
会爆精度。我的做法是搞两个\(9*10^{18}\)级别的质数来算,如果答案是非负整数则两次结果相等,如果是负数或者是分数结果几乎不一定相等。
using namespace std;
#include <bits/stdc++.h>
#define N 205
#define ll long long
#define ull unsigned long long
#define db long double
#define mo1 9000000000000000041
#define mo2 9000000000000000053
//ll mul(ll x,ll y,ll mo){
// return (__int128)x*y%mo;
//}
ll mul(ll x,ll y,ll mo){
// x%=mo,y%=mo;
ll t=x*y-(ll)floor((long double)x*y/mo)*mo;
if (t<0) t+=mo;
if (t>=mo) t-=mo;
return t;
}
//ull mul(ull x,ull y,ull mo){
// ull s=0;
// for (;y;y>>=1,x=(x<<1)%mo)
// if (y&1)
// s=(s+x)%mo;
// return s;
//}
ll qpow(ll x,ll y,ll mo){
ll r=1;
for (;y;y>>=1,x=mul(x,x,mo))
if (y&1)
r=mul(r,x,mo);
return r;
}
int n;
int b[N],r[N];
ll o[N][N];
struct Num{
ull p,q;
bool _0(){return p==0 && q==0;}
Num inv(){return {qpow(p,mo1-2,mo1),qpow(q,mo2-2,mo2)};}
};
Num operator+(Num x,Num y){return {(x.p+y.p)%mo1,(x.q+y.q)%mo2};}
Num operator-(Num x,Num y){return {(x.p-y.p+mo1)%mo1,(x.q-y.q+mo2)%mo2};}
Num operator*(Num x,Num y){return {mul(x.p,y.p,mo1),mul(x.q,y.q,mo2)};}
Num operator*(ll x,Num y){return {mul((x+mo1)%mo1,y.p,mo1),mul((x+mo2)%mo2,y.q,mo2)};}
Num v[N][N];
char str[N];
void calc(){
for (int i=1;i<=n;++i){
Num inv=v[i][i].inv();
for (int k=i;k<=n*3;++k)
v[i][k]=v[i][k]*inv;
for (int j=i+1;j<=n;++j)
if (!v[j][i]._0()){
Num tmp=v[j][i];
for (int k=i;k<=n*3;++k)
v[j][k]=v[j][k]-v[i][k]*tmp;
}
}
for (int i=n;i>=1;--i)
for (int j=1;j<i;++j)
if (!v[j][i]._0()){
for (int k=n+1;k<=n*3;++k)
v[j][k]=v[j][k]-v[j][i]*v[i][k];
v[j][i]={0,0};
}
}
int z[N],w[N];
ll x[N];
ll doit(int c){
for (int i=1;i<=n;++i)
if (r[i]==n+1 && str[i]=='R')
return -1;
ll s=0;
for (int i=1;i<=n;++i){
Num t={0,0};
for (int j=1;j<=n;++j)
t=t-(z[j]*v[i][n+j]+w[j]*v[i][n+n+j]);
if (t.p!=t.q)
return -1;
x[i]=t.p;
if (x[i]-z[i]<0)
return -1;
s+=2*x[i]-z[i];
}
static int vis[N],BZ;
for (int i=1;i<=n;++i)
if (x[i]>0){
++BZ;
for (int y=i;vis[y]!=BZ;y=(str[y]=='R'?r[y]:b[y]))
vis[y]=BZ;
if (vis[c]!=BZ)
return -1;
}
return s;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d",&n);
n--;
w[1]--;
for (int i=1;i<=n;++i){
o[i][i]-=2;
o[i][n+i]++;
o[i][n+n+i]--;
}
for (int i=1;i<=n;++i){
scanf("%d%d",&b[i],&r[i]);
if (b[i]<=n) o[b[i]][i]++,o[b[i]][n+i]--;
if (r[i]<=n) o[r[i]][i]++;
}
for (int i=1;i<=n;++i)
for (int j=1;j<=n*3;++j)
v[i][j]={(o[i][j]+mo1)%mo1,(o[i][j]+mo2)%mo2};
calc();
int Q;
scanf("%d",&Q);
while (Q--){
int c;
scanf("%d%s",&c,str+1);
assert(c<=n);
w[c]++;
for (int i=1;i<=n;++i)
z[i]=(str[i]=='R'?1:0);
ll ans=doit(c);
printf("%lld\n",ans);
w[c]--;
}
return 0;
}