【Atcoder】AGC031 C-F简要题解

这一场涉及了很多我不熟悉的技巧,都是神仙题


*C.Differ by 1 Bit

考试的时候想了好久

A → B A\to B AB的过程等价于 0 → A   x o r   B 0\to A\ xor \ B 0A xor B,考虑如何判断 0 → X 0\to X 0X是否可行

归纳位数 w w w构造:

  • 初始 w = 1 w=1 w=1 0 → 1 0\to 1 01
  • 对于 w > 1 w>1 w>1,固定 X X X中为 1 1 1的一位 b b b,剩下的 w − 1 w-1 w1位在前 2 w − 1 − 1 2^{w-1}-1 2w11次操作后变成某个数(这个过程中保持 b = 0 b=0 b=0),下一步 b : = 1 b:=1 b:=1,然后在后 2 w − 1 − 1 2^{w-1}-1 2w11次剩下的 w − 1 w-1 w1位又可以倒序执行前面的操作得到 0 0 0。最终可以得到一个只有 b b b这位为1的数。
    考虑归纳这个过程,剩余 w − 1 w-1 w1位最终必然得到了偶数个 1 1 1
    • 0 → 1 → 0 0\to 1\to 0 010得到 0 0 0 1 1 1 w = 2 w=2 w=2时成立
    • 设已知 w − 1 w-1 w1位时成立,则前 2 w − 1 − 1 2^{w-1}-1 2w11次操作除 b b b位一定能得到奇数个 1 1 1,后 2 w − 1 − 1 2^{w-1}-1 2w11次操作相当于这个有奇数个1的数异或上另一个有奇数个1的数,必然得到偶数个1,成立。。
  • 所以任意 X X X有奇数个 1 1 1的情况都可以递归构造出解。

详见代码

#include
#define RI register
using namespace std;
const int N=(1<<17)+10,mod=1e9+7;
typedef long long ll;

int n,A,B,bs;
int bel[N],tim;
int ans[N],cnt,S;
bool vs[N];

inline int cont(int x)
{
	int re=0,i;
	for(i=0;i<n;++i) if((x>>i)&1) re++;
	return re;
}

vector<int> cal(int t,int x)
{
	vector<int>re;re.resize((1<<t));
	if(t==1){re[1]=1;return re;}
	int pos,i;
	for(i=0;i<t;++i) if((x>>i)&1) {pos=i;break;}
	vector<int>nw;
	nw=cal(t-1,1);
	for(i=0;i<(1<<(t-1));++i) re[i]=(((nw[i]>>pos)<<(pos+1))|(nw[i]&((1<<pos)-1)));
	nw=cal(t-1,(((x>>(pos+1))<<pos)|(x&((1<<pos)-1)))^1);
	for(i=0;i<(1<<(t-1));++i){
		if(pos>0) re[i+(1<<(t-1))]=1^((1<<pos)|((nw[i]>>(pos))<<(pos+1))|(nw[i]&((1<<pos)-1)));
		else re[i+(1<<(t-1))]=1|((nw[i]^1)<<1);
	}
	return re;
}

int main(){
	int i,j,x,y;
	scanf("%d%d%d",&n,&A,&B);
	S=(1<<n);bs=A;B^=A;
	if(cont(B)%2==0) {puts("NO");return 0;}
	puts("YES");
	vector<int> as=cal(n,B);
	for(i=0;i<S;++i) printf("%d ",as[i]^A);
	return 0;
}


**D.A Sequence of Permutations

抽象代数一脸懵,真是伤脑筋(竟以为自己能找出规律,too young too naive)

设存在排列 p , q , t p,q,t p,q,t,定义置换运算乘法: t = p q t=pq t=pq,则 t i = p q i t_i=p_{q_i} ti=pqi g = p − 1 g=p^{-1} g=p1表示 p p p的逆变换, g p i = i g_{p_i}=i gpi=i,存在性质 ( p q ) − 1 = q − 1 p − 1 , p p − 1 = ( 1 ) (pq)^{-1}=q^{-1}p^{-1},pp^{-1}=(1) (pq)1=q1p1,pp1=(1)

发现 f ( p , q ) = q p − 1 f(p,q)=qp^{-1} f(p,q)=qp1,强势找规律:
a 1 = p a 2 = q a 3 = q p − 1 a 4 = q p − 1 q − 1 a 5 = q p − 1 q − 1 p q − 1 a 6 = q p − 1 q − 1 p 2 q − 1 a 7 = q p − 1 q − 1 p q p q − 1 a 8 = q p − 1 q − 1 p q p − 1 q p q − 1 a_1=p\\ a_2=q\\ a_3=qp^{-1}\\ a_4=qp^{-1}q^{-1}\\ a_5=qp^{-1}q^{-1}pq^{-1}\\ a_6=qp^{-1}q^{-1}p^2q^{-1}\\ a_7=qp^{-1}q^{-1}pqpq^{-1}\\ a_8=qp^{-1}q^{-1}pqp^{-1}qpq^{-1} a1=pa2=qa3=qp1a4=qp1q1a5=qp1q1pq1a6=qp1q1p2q1a7=qp1q1pqpq1a8=qp1q1pqp1qpq1

A = q p − 1 q − 1 p ( A − 1 = p − 1 q p q − 1 ) A=qp^{-1}q^{-1}p(A^{-1}=p^{-1}qpq^{-1}) A=qp1q1p(A1=p1qpq1)惊奇地发现 a 7 = A a 1 A − 1 , a 8 = A a 2 A − 1 a_7=Aa_1A^{-1},a_8=Aa_2A^{-1} a7=Aa1A1,a8=Aa2A1,归纳得到 a i = A a i − 6 A − 1 a_i=Aa_{i-6}A^{-1} ai=Aai6A1,即 a i = A . . . A a ( i − 1 ) % 6 + 1 A − 1 . . . A − 1 a_i=A...Aa_{(i-1)\%6+1}A^{-1}...A^{-1} ai=A...Aa(i1)%6+1A1...A1

所以求出 A ⌊ i − 1 6 ⌋ A^{\lfloor \frac{i-1}{6}\rfloor} A6i1即可,可以求出轮换直接位移。

code from sigongzi

#include 
#define fi first
#define se second
#define pii pair
#define mp make_pair
#define pb push_back
#define space putchar(' ')
#define enter putchar('\n')
#define MAXN 100005
#define eps 1e-10
//#define ivorysi
using namespace std;
typedef long long int64;
typedef unsigned int u32;
typedef double db;
template<class T>
void read(T &res) {
    res = 0;T f = 1;char c = getchar();
    while(c < '0' || c > '9') {
    if(c == '-') f = -1;
    c = getchar();
    }
    while(c >= '0' && c <= '9') {
    res = res * 10 + c - '0';
    c = getchar();
    }
    res *= f;
}
template<class T>
void out(T x) {
    if(x < 0) {x = -x;putchar('-');}
    if(x >= 10) {
    out(x / 10);
    }
    putchar('0' + x % 10);
}
int N,K;
struct pl {
    int f[100005];
    friend pl operator * (const pl &a,const pl &b) {
    pl c;
    for(int i = 1 ; i <= N ; ++i) c.f[i] = a.f[b.f[i]];
    return c;
    }
    pl inv() {
    pl c;
    for(int i = 1 ; i <= N ; ++i) {
        c.f[f[i]] = i;
    }
    return c;
    }
}p,q,iq,ip,s,res,t,ans;
void fpow(int c) {
    for(int i = 1 ; i <= N ; ++i) res.f[i] = i;
    t = s;
    while(c) {
    if(c & 1) res = res * t;
    t = t * t;
    c >>= 1;
    }
}
void Solve() {
    read(N);read(K);
    for(int i = 1 ; i <= N ; ++i) read(p.f[i]);
    for(int i = 1 ; i <= N ; ++i) read(q.f[i]);
    ip = p.inv();iq = q.inv();
    s = q * ip * iq * p;
    fpow((K - 1) / 6);
    int x = (K - 1) % 6;
    if(x == 0) {
    ans = res * p * res.inv();
    }
    else if(x == 1) {
    ans = res * q * res.inv();
    }
    else if(x == 2) {
    ans = res * q * ip * res.inv();
    }
    else if(x == 3) {
    res = res * q;
    ans = res * ip * res.inv();
    }
    else if(x == 4) {
    res = res * q * ip;
    ans = res * iq * res.inv();
    }
    else if(x == 5) {
    res = res * q * ip;
    ans = res * iq * p * res.inv();
    }
    for(int i = 1 ; i <= N ; ++i) {
    out(ans.f[i]);space;
    }
    enter;
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    Solve();
}

**E.Snuke the Phantom Thief

神仙费用流

分析:

一维的情况,可以把每个珠宝看做一个标号为坐标的点,然后按 L / R L/R L/R的限制分居两侧,建S-T最大费用最大流。
二维的情况,显然一个点不可能流出2个单位的流量分别满足 L / R , U / D L/R,U/D L/R,U/D的限制(带费用的流量不可拆分),可以拆出 2 N 2N 2N个点,分别代表 x , y x,y x,y坐标,将每个珠宝看做 ( x i , y i , 1 , v i ) (x_i,y_i,1,v_i) (xi,yi,1,vi)的一条边。但同时满足 L / R , U / D L/R,U/D L/R,U/D的限制还是不可做。

然后就需要关键的一步:

发现 n ≤ 80 n\leq 80 n80不仅可以跑一次费用流,还可以枚举最后选的珠宝数都跑一次费用流!

枚举珠宝数 K K K有奇效:

分别考虑两个维上的所有限制(将选的 K K K个点这一维上坐标按升序排序,假设第 x x x个点坐标大小为 p x p_x px):

  • ≤ a i \leq a_i ai的最多 b i b_i bi → p b i + 1 > a i \to p_{b_i+1}>a_i pbi+1>ai
  • ≥ a i \geq a_i ai的最多 b i b_i bi → p K − b i < a i \to p_{K-b_i}<a_i pKbi<ai
  • p 1 ≤ p 2 ≤ . . . ≤ p K p_1\leq p_2\leq...\leq p_K p1p2...pK

构图:
2 K 2K 2K个点分别表示对 x x x轴上选的第 i i i个点的限制 p x i ( 1 ≤ i ≤ K ) px_i(1\leq i\leq K) pxi(1iK),对 y y y轴上选的第 i i i个点的限制 p y i ( 1 ≤ i ≤ K ) py_i(1\leq i\leq K) pyi(1iK),连边 ( S , p x i , 1 , 0 ) , ( p y i , T , 1 , 0 ) (S,px_i,1,0),(py_i,T,1,0) (S,pxi,1,0),(pyi,T,1,0)
再建 2 N 2N 2N个点分别表示所有珠宝的横纵坐标,连边 ( x i , y i , 1 , v i ) (x_i,y_i,1,v_i) (xi,yi,1,vi)
p x i px_i pxi向所有限制范围以内的 x i x_i xi连流量为 1 1 1,费用为 0 0 0的边, y i y_i yi向所有可以匹配上的 p y i py_i pyi连流量为1 ,费用为0的边。

注意只有满流的情况才有资格和答案取 max ⁡ \max max
( Maxflow(S,T)=K \text{Maxflow(S,T)=K} Maxflow(S,T)=K)


*F.Walk on Graph

神仙题

设状态为 ( u , x ) (u,x) (u,x),表示在点 u u u时,值为 x x x

考虑倒着走, 走一条边 ( u , v , c ) (u,v,c) (u,v,c)相当于 ( u , x ) → ( v , 2 x + c ) (u,x)\to (v,2x+c) (u,x)(v,2x+c),询问 ( T , 0 ) (T,0) (T,0)是否能走到 ( S , R ) (S,R) (S,R)

性质1:

f ( x ) = 2 x + c ( m o d M O D ) ( x → 2 x + c ) f(x)=2x+c \pmod {MOD}(x\to 2x+c) f(x)=2x+c(modMOD)(x2x+c)是双射的(一一对应),所以若存在 ( u , x ) → ( v , 2 x + c ) (u,x)\to (v,2x+c) (u,x)(v,2x+c),那么也存在 ( v , 2 x + c ) → ( u , x ) (v,2x+c)\to (u,x) (v,2x+c)(u,x)

考虑 f ( x ) = y f(x)=y f(x)=y,已知 y y y,求解 x x x,则 2 x ≡ y − c ( m o d M O D ) 2x\equiv y-c\pmod{MOD} 2xyc(modMOD) 2 2 2在奇模数意义下存在逆元所以 x x x的取值是唯一的,同理不同的 x x x对应不同的 f ( x ) f(x) f(x),所以是一一对应的。

所以不断 x = f ( x ) x=f(x) x=f(x)一定可以走回 x x x,如果是个奇环,再走一遍就是偶环( u → v → . . . → v → u u\to v\to ...\to v\to u uv...vu)了。

所以 ( u , x ) ⇔ ( v , 2 x + c ) (u,x)\Leftrightarrow(v,2x+c) (u,x)(v,2x+c)之间可以直接连上边权为 c c c的双向边。

( T , 0 ) (T,0) (T,0) ( S , R ) (S,R) (S,R)在同一个连通块内则有解。

性质2:

( u , x ) (u,x) (u,x)沿着它连出的一条边走两次回到自己可以得到 ( u , 4 x + 3 c ) (u,4x+3c) (u,4x+3c),如果存在另一条连出的边(可以得到 4 x + 3 c ′ 4x+3c' 4x+3c),则存在 ( u , x ) → ( u , x + 3 k ( c − c ′ ) ) , k ∈ Z (u,x)\to (u,x+3k(c-c')),k\in Z (u,x)(u,x+3k(cc)),kZ

4 4 4在奇模数意义下存在逆元,所以 ( u , x ) ⇔ ( u , 4 x + 3 c ) (u,x)\Leftrightarrow(u,4x+3c) (u,x)(u,4x+3c)

( u , 4 x + 3 c ) → ( u , x ) → ( u , 4 x + 3 c ′ ) (u,4x+3c)\to (u,x)\to (u,4x+3c') (u,4x+3c)(u,x)(u,4x+3c)

4 x 4x 4x可以表示任何数(对于任意 y y y,都存在 4 x ≡ y ( m o d M O D ) 4x\equiv y\pmod{MOD} 4xy(modMOD)),所以 ( u , x ) → ( u , x + 3 k ( c − c ′ ) ) , k ∈ Z (u,x)\to (u,x+3k(c-c')),k\in Z (u,x)(u,x+3k(cc)),kZ

于是可以把有公共点 u u u的边的差值求一个 g c d G u gcdG_u gcdGu,对于 u u u来说,距离的循环节变成了 gcd ⁡ ( 3 G u , M O D ) \gcd(3G_u,MOD) gcd(3Gu,MOD)。考虑 ( u , x ) → ( v , 2 x + c ) (u,x)\to (v,2x+c) (u,x)(v,2x+c),实际上 v v v的循环节是 gcd ⁡ ( 2 ⋅ 3 G u , gcd ⁡ ( 3 G v , M O D ) ) \gcd(2·3G_u,\gcd(3G_v,MOD)) gcd(23Gu,gcd(3Gv,MOD)),继续拓展,求出连通图中所有边两两边权之差的 gcd ⁡ G \gcd G gcdG M O D MOD MOD就变成了 g = gcd ⁡ ( 3 G , M O D ) g=\gcd(3G,MOD) g=gcd(3G,MOD)

性质3:

在模 g g g意义下,所有边权值一样,设为 z z z,可以把所有状态第二位增加 z z z,所有边权 − z -z z,运算不变。

( u , x ′ ) → ( v , 2 ( x ′ − z ) + ( c ′ + z ) + z ) = ( v , 2 x ′ + c ′ ) (u,x')\to (v,2(x'-z)+(c'+z)+z)=(v,2x'+c') (u,x)(v,2(xz)+(c+z)+z)=(v,2x+c)

这样所有边权都是 g g g的倍数,转移形如 ( u , x ) → ( v , 2 p x + q g ) , q ∈ { 0 , 1 , 2 } (u,x)\to(v,2^px+qg),q\in\{0,1,2\} (u,x)(v,2px+qg),q{0,1,2},结合之前的变化 ( u , x ) → ( u , 4 x + 3 c ) = u ( 4 x ) (u,x)\to (u,4x+3c)=u(4x) (u,x)(u,4x+3c)=u(4x) x x x 4 x 4x 4x等价,所以 p ∈ { 0 , 1 } p\in \{0,1\} p{0,1}

最终得到一张有 6 n 6n 6n个点的图,并查集判断连通性即可

询问相当于判断 ( t , z ) (t,z) (t,z)是否能走到 ( s , z + r ) (s,z+r) (s,z+r),枚举 q q q的取值和 p p p的奇偶性(注意不是具体值),相当于求解是否存在 p p p使得 2 p z + q g = z + r 2^pz+qg=z+r 2pz+qg=z+r,预处理一下 2 p z 2^pz 2pz分别在奇偶次幂可以取到的数,判断 z + r − q g z+r-qg z+rqg能否取到即可。

code from alan_cty

#include 
#include 
#include 
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

const int N=1e6+5;

int n,m,q,mod,s,t,r,u[N],v[N],c[N],g,fa[N];
bool can[2][N];

int Id(int x,int y,int z) {return (x-1)*6+y*3+z;}
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}

void link(int x,int y) {
	x=get(x);y=get(y);
	if (x==y) return;
	fa[x]=y;
}

int main() {
	scanf("%d%d%d%d",&n,&m,&q,&mod);
	fo(i,1,m) {
		scanf("%d%d%d",&u[i],&v[i],&c[i]);
		g=__gcd(g,abs(c[i]-c[1]));
	}
	if (!g) g=mod;mod=__gcd(mod,3*g);int z=c[1]%g;
	fo(i,0,6*n) fa[i]=i;
	fo(i,1,m) {
		int w=(c[i]-z)/g;
		fo(p,0,1)
			fo(q,0,2) {
				link(Id(u[i],p,q),Id(v[i],1-p,(2*q+w)%3));
				link(Id(v[i],p,q),Id(u[i],1-p,(2*q+w)%3));
			}
	}
	for(int i=0,j=z;i<mod<<1;i++,(j<<=1)%=mod) can[i&1][j]=1;
	for(;q;q--) {
		scanf("%d%d%d",&s,&t,&r);
		int ret=0;
		fo(p,0,1) fo(q,0,2) if (get(Id(t,0,0))==get(Id(s,p,q))) ret|=can[p][(r+z+(3-q)*g)%mod];
		puts(ret?"YES":"NO");
	}
	return 0;
}

你可能感兴趣的:(妙,费用流,并查集,构造,atcoder)