• AtCoder AGC031F Walk on Graph (图论、数论)


    题目链接

    https://atcoder.jp/contests/agc031/tasks/agc031_f

    题解

    这题真是太神仙了……

    首先我们转化一下问题,倒着来做,一开始有一个数(0), 每次走过一条边该数变为乘以(2)再加上这条边的边权。
    我们用((u,x))代表一个状态,表示当前在点(u),该数值为(x), (x)始终在(mod p)意义下定义,(p)为模数。
    假设(u)点有一条边连向(v)边权为(w), 则((u,x))可以变成((v,2x-w))(下用(Rightarrow)表示),然后((v,2x-w)Rightarrow(u,4x-3w),(u,4x-3w)Rightarrow(v,8x-7w),...)
    但是由于(p)是奇数,存在(k>1)使(2^kequiv 1(mod p)), 这个序列是循环的,由((u,x))最终依然会到达((u,x)). 也就是说,我们可以认为这个递推关系是双向的,((u,x)Leftrightarrow (v,2x-w)Leftrightarrow (u,4x-3w)Leftrightarrow ...)

    这时,不要往(2)的幂的方向思考而一去不复返。考虑这个递推序列的前三步: ((u,x)Leftrightarrow (u,4x-3w))
    如果有两条连接的边分别是(w_1,w_2), 则((u,x)Leftrightarrow (u,4x-3w_1) Leftrightarrow (u,4x-3w_2)). 又因为(4)(mod p)意义下有逆元,故(4x)可以代表任何数,即((u,x)Leftrightarrow (u,xpm 3(w_1-w_2))).
    考虑一个点相连的那些边,由数论的基本知识可得,((u,x)Leftrightarrow (u,x+kt_u)) ((k)为整数),其中(t_u=gcd(3g_u,p)), (g_u)为与(u)相连所有边权的(gcd). 即所有模(t_u=gcd(3g_u,p))同余的(x), ((u,x))的状态是一样的。
    再进一步说,考虑不同的点,((u,x)Leftrightarrow (v,2x+w)), (u)循环节为(t_u)导致(v)具有长度为(2t_u)的循环节,和其本身的取(gcd)(gcd(t_u,2t_v)=gcd(t_u,t_v)) ((2)可以去掉是因为(2)(3g_u)互质因而与(t_u)互质)。由于整个图是联通的,这可以扩展到所有点,也就是对于所有(u,x)((u,x)Leftrightarrow (u,x+T)), 其中(T=gcd^n_{u=1}t_u=gcd(3gcd^m_{i=1}w_i,p)). 我们令(G=gcd^m_{i=1}w_i). 显然(T=G)(T=3G).
    整理一下,同一点所有模(T)同余的状态是一样的,而所有边权是模(G)同余的,且(T=G)(T=3G).
    这样,我们可以把题目中的(p)直接改成(T), 不会对答案有任何影响。下面我们用(p)来代表(T).
    注: 这里之所以我们只取了((4x-3y))这一项而没有继续深入下去依然能得到正确的结论的原因是对任何(kin mathbb{N}), ((u,x)Leftrightarrow (u,2^{2k}x+(2^{2k}-1)w)), 而(3|(2^{2k}-1)).

    既然所有边权是模(G)同余的,我们就可以设(w_i=c_iG+z), 其中(0le zlt G)是一个对于所有的边(i)都一样的数。
    这个边权带常数(z)看上去很不优美,我们想办法把它消掉!我们把所有的当前数值(x)都增加(z), 并把所有的边权都减少(z). 改变后的值记为(x'), 那么现在的一次游走将会是: (x'-z=x ightarrow (2x+c_iG+z)=(2(x+z)+c_iG)-z=(2x'+c_iG)-z), 也就是说(x')经过一次操作会变成(2x'+c_iG), (z)的余数没了,其余操作没有变化!
    于是我们执行把数值(x)增加(z)同时把边权减少(z)的操作,现在我们认为(w_i=c_iG),且询问的是((t,z))((s,z+r))是否可达。

    因为(w_i=c_iG),故((u,x)Leftrightarrow (u,4x+3c_iG)Leftrightarrow (u,4x)), 且因为边权全部是(G)的倍数,在(mod 3G)意义下只有(3)种取值,即可以认为(c_i=0,1,2). 那每个状态就可以表示成((u,2^jx+kG))的形式,其中(0le jlt 2,0le klt 3).
    也就是说,对于每个点,我们只有(6)种状态有用!
    然后的做法就很显然了,我们建出这(6n)个状态,对于每一条输入的边,操作权值之后把相应的状态用并查集缩起来。
    对于询问,我们想要的是((t,z))((s,z+r))是否联通。枚举(jmod 2)(k), 我们得到(2^jz+kGequiv z+r(mod p)). 我们需要判断是否存在(j)满足(2^jzequiv z+r-kG(mod p)), 这个对于每个(j)预处理一下(2^jz)记录下来奇偶即可。若存在且((t,0,0))((s,j,k))这两个状态联通,则答案是YES, 否则是NO.

    时间复杂度(O(6(n+m+q)alpha(n)+p)).

    代码

    #include<bits/stdc++.h>
    #define llong long long
    #define mkpr make_pair
    #define riterator reverse_iterator
    using namespace std;
    
    inline int read()
    {
    	int x = 0,f = 1; char ch = getchar();
    	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
    	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
    	return x*f;
    }
    
    const int N = 5e4;
    const int mxP = 1e6;
    struct AEdge
    {
    	int u,v,w;
    } ae[N+3];
    int uf[N*6+3];
    bool ispw2[mxP+3][2];
    int n,m,q,p,g;
    
    int gcd(int x,int y) {return y==0?x:gcd(y,x%y);}
    
    int getid(int x,int j,int k) {return (j*3+k)*n+x;}
    int findfa(int u) {return u==uf[u]?u:uf[u]=findfa(uf[u]);}
    void unionfa(int u,int v)
    {
    	int x = findfa(u),y = findfa(v);
    	if(x!=y) {uf[x] = y;}
    }
    
    int main()
    {
    	scanf("%d%d%d%d",&n,&m,&q,&p); g = p;
    	for(int i=1; i<=n*6; i++) uf[i] = i;
    	for(int i=1; i<=m; i++)
    	{
    		scanf("%d%d%d",&ae[i].u,&ae[i].v,&ae[i].w);
    		g = gcd(g,abs(ae[i].w-ae[1].w));
    	}
    	p = gcd(p,3*g);
    	int z = ae[1].w%g;
    	for(int i=1; i<=m; i++)
    	{
    		int w = (ae[i].w-z)/g%3,u = ae[i].u,v = ae[i].v;
    		for(int j=0; j<2; j++) for(int k=0; k<3; k++)
    		{
    			unionfa(getid(u,j,k),getid(v,j^1,(k+k+w)%3));
    			unionfa(getid(v,j,k),getid(u,j^1,(k+k+w)%3));
    		}
    	}
    	for(int i=0,x=z; i<p; i++,x=(x<<1)%p) ispw2[x][i&1] = true;
    	while(q--)
    	{
    		int u,v,l; scanf("%d%d%d",&u,&v,&l); bool ans = false;
    		for(int j=0; j<2; j++) for(int k=0; k<3; k++)
    		{
    			int tmp = (z+l+(3-k)*g)%p;
    			if(ispw2[tmp][j])
    			{
    				int uu = findfa(getid(v,0,0)),vv = findfa(getid(u,j,k));
    				if(uu==vv) {ans = true; break;}
    			}
    		}
    		if(ans==true) puts("YES"); else puts("NO");
    	}
    	return 0;
    }
    
  • 相关阅读:
    zabbix表结构
    ubuntu 安装微信开发者工具
    价格正则
    数组从0开始排序
    js 时间戳 和 格式化时间转化
    js 时间戳 转化
    vim 到文件开头 结尾
    crontab注意事项
    GIT每次都要输入用户名和密码的解决方案
    管理lnmp常用命令,lnmp重启,start|stop|reload|restart等命令
  • 原文地址:https://www.cnblogs.com/suncongbo/p/12285863.html
Copyright © 2020-2023  润新知