zoukankan      html  css  js  c++  java
  • 2019 ICPC Nanchang Summon

    题目链接

    https://vjudge.net/contest/418216#problem/J

    这道题也是(Burnside)计数问题,我之前也写过一篇讲(Burnside)的博客,链接如下

    https://www.cnblogs.com/ssdfzhyf/p/14533929.html

    这道题说你需要拿(0,1,2,3)四种宝石排列成一个长度(n)的环形阵,环形阵无首尾,若一个阵旋转一定角度后和另一个阵重合,则视为同一个阵。

    (m)种非法子串,每个子串长度都是(4)。假如你的阵里面出现了一种子串,那么就是非法的。

    求有多少种合法的阵,其中(0le mle 256,4le nle 100000)

    (Burnside)问题需要定义一个集合和对应的置换群

    设一个长度为(n)的数列的集合(S(n)={a|a=a_0,a_1,...,a_{n-1},将a首尾连成环形后不出现非法的子串})

    注意(a=0,1,2,3)(b=1,2,3,0)是两个不同的元素,因为数列是有序的

    (S(n))上的置换(G)

    (G={f_k|b=f_k(a):b_i=a_{(i+k)mod\,n},kin[0,n)cap N})

    那么根据(Burnside)定理

    答案就是(frac{1}{n}sum_{k=0}^{n-1}sum_{ain S(n)}[f_k(a)=a])

    表达式([f_k(a)=a]),等价于

    ([forall iin [0,n)cap N,a_{(i+k)mod\,n}=a_i])

    那么我们可以把({0,1,2,...,n-1})划分成若干等价类

    ({{x|x=(i+kj)mod\,n,jin N}|iin[0,n)cap N})

    对于(i)所在等价类里面的元素

    ({x|x=(i+kj)mod\,n,jin N})

    (xequiv i+kj(mod\,n))

    根据贝祖定理,该方程有解的等价条件为(xequiv i (mod\,gcd(k,n)))

    那么共有(gcd(n,k))个等价类,每个等价类都有(frac{n}{gcd(k,n)})个元素

    故对应着循环节为(gcd(k,n))的一个序列

    不妨记(t=gcd(k,n))

    那么满足条件的序列形如(a=a_0,a_1,...,a_{t-1},a_0,a_1,...,a_{t-1},...,a_0,a_1,...,a_{t-1})

    我们现在需要对这个数列进行计数

    如果(tge 4),那么序列(a)首尾相连后不出现非法子串等价于(a_0,a_1,...,a_{t-1})首尾相连后不出现非法子串

    那么我们可以统计集合(S(t))中有几个元素即可

    (S(t))内的元素也是数列,是有顺序的,我们可以利用字符串自动机的思想进行统计

    我们先计算(T(t)={a|a=a_0,a_1,...,a_{t-1},a中不出现非法的子串})的元素数量

    我们知道非法字符串都是(4)位的,故我们可以把状态设置为长度为(3)的字符串,共(64)个状态

    由于每个状态对应的长度都是(3),转移的初始状态有(3)个字符,每一步转移给这个字符串末尾添加一个新字符,一共转移(t-3)次。

    比如非法字符串只有(0123)一种,那么对于状态(012),下一个状态可以是(120,121,122)

    通过枚举状态和转移时新加入的字符,可以得到一个(64*64)的矩阵(ATM)

    (ATM[i][j]=1)就表示从状态(i)添加一个字符可以到达状态(j),且不新增违规字符串

    (ATM[i][j]=0)则相反

    计算出(P=ATM^{t-3})

    (P[i][j])就表示如果这个字符串的长度为(3)的前缀对应状态(i),且这个字符串的长度为(3)的后缀对应状态(j),在考虑中间没有违规字符串的前提下,有多少种合法的序列

    这样,将(P[i][j])直接求和即可算出不考虑数列首尾相接的情况,有多少个合法的串

    如果考虑首尾相接,那么仍先计算(P=ATM^{t-3})

    之后枚举(0le i,j< 64),查询(ji)(这是一个长度为(6)的字符串)中是否存在非法子串。

    如果存在,则说明将该序列首尾相接后会出现非法子串,忽略即可。

    如果不存在,则加上(P[i][j])

    如果(t=3),要求有多少个满足首尾详解后不存在非法子串的数列(a=a_0,a_1,a_2,...,a_0,a_1,a_2)

    由于题目规定(nge4),且(t=gcd(k,n)),那么(nge 6)

    那么它等价于(a_0,a_1,a_2,a_0)(a_1,a_2,a_0,a_1)(a_2,a_0,a_1,a_2)都不是非法子串

    我们可以构造串(a_0,a_1,a_2,a_0,a_1,a_2),在不考虑首尾相接的情况下有多少种合法的情况,枚举即可

    如果(t=2),要求有多少个满足首尾详解后不存在非法子串的数列(a=a_0,a_1,...,a_0,a_1)

    那么它等价于(a_0,a_1,a_0,a_1)(a_1,a_0,a_1,a_0)都不是非法子串

    我们可以构造(a_0,a_1,a_0,a_1,a_0),在不考虑首尾相接的情况下有多少种合法的情况,枚举即可

    在确定(t)后,以上的方案数简记为(f(t))

    那么答案为(frac{1}{n}sum_{k=0}^{n-1}f(gcd(k,n))=frac{1}{n}sum_{t|n}f(t)sum_{k=0}^{n-1}[t=gcd(k,n)]=frac{1}{n}sum_{t|n}f(t)varphi(frac{n}{t}))

    编程实现

    我们要计算(frac{1}{n}sum_{t|n}f(t)varphi(frac{n}{t})),首先需要枚举(n)的所有因数,求出它们对应的欧拉函数,以及(f(t))

    在求(f(t))时,要分(4)种情况讨论

    1.(t=1)

    2.(t=2)

    3.(t=3)

    4.(tge4)

    每个情况写一个函数进行计算

    对于第(4)种,肯定是最复杂的

    (4)种涉及一个基础的矩阵(ATM),它可以预处理。

    然后还要预处理出(P=ATM^{t-3})中,哪些转移是合法的,它也可以预处理

    每次计算(ATM^{t-3}),需要使用矩阵快速幂计算,每次矩阵乘法的计算次数为(64^3approx 2.6e5)

    经过实际检测,当(n=98280)时,需要进行(1743)次的矩阵乘法运算,这是上限

    那么最终的计算次数约(4.5e8),在(4s)内应该可以通过

    放代码

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #include<cmath>
    #include<stack>
    #include<vector>
    using namespace std;
    typedef long long ll;
    const ll mod=998244353;
    int prime[100010],first[100010],phi[100010];
    bool isp[100010];
    int euler(int n)//计算欧拉函数
    {
    	int cnt=0;
    	isp[1]=0;
    	phi[1]=1;
    	for(int i=2;i<=n;i++)isp[i]=1;
    	for(int i=2;i<=n;i++)
    	{
    		if(isp[i])
    		{
    			prime[++cnt]=i;
    			first[i]=i;
    			phi[i]=i-1;
    		}
    		for(int j=1;j<=cnt&&prime[j]*i<=n;j++)
    		{
    			isp[i*prime[j]]=0;
    			first[i*prime[j]]=prime[j];
    			if(i%prime[j])
    			{
    				phi[i*prime[j]]=phi[i]*(prime[j]-1);
    			}
    			else
    			{
    				phi[i*prime[j]]=phi[i]*prime[j];
    				break;
    			}
    		}
    	}
    }
    struct mat
    {
    	vector<ll>a[65];
    	int n,m;
    	void init(int n_,int m_)//矩阵的初始化
    	{
    		n=n_;m=m_;
    		for(int i=0;i<n;i++)
    		{
    			a[i].resize(m);
    			for(int j=0;j<m;j++)a[i][j]=0;
    		}
    	}
    	void I(int n_)//生成单位矩阵
    	{
    		init(n_,n_);
    		for(int i=0;i<n;i++)a[i][i]=1;
    	}
    	mat operator *(const mat &b)const//模意义下矩阵乘法
    	{
    		mat res;
    		res.init(n,b.m);
    		for(int i=0;i<res.n;i++)
    		{
    			for(int j=0;j<res.m;j++)
    			{
    				for(int k=0;k<m;k++)
    				{
    					res.a[i][j]=(res.a[i][j]+a[i][k]*b.a[k][j])%mod;
    				}
    			}
    		}
    		return res;
    	}
    }ATM;
    mat qpow(mat A,int n)//矩阵快速幂
    {
    	mat ans;
    	ans.I(A.n);
    	while(n)
    	{
    		if(n&1)ans=ans*A;
    		A=A*A;
    		n>>=1;
    	}
    	return ans;
    }
    int str[260][6];//非法子串
    int list[200];//因数表
    //pattern[i]=1就代表子串i是一个非法子串,i是一个8bit的整数,比如0123即1*16+2*4+3=27
    bool pattern[260];
    bool check(int a[],int len)//检查a[0]~a[len-1]中间有没有非法子串
    {
    	int now=0;
    	for(int i=0;i<len;i++)
    	{
    		now=(now*4+a[i])%256;
    		if(i>=3&&pattern[now])return 1;
    	}
    	return 0;
    }
    int f_1()
    {
    	int a[4];
    	int ans=0;
    	for(a[0]=0;a[0]<4;a[0]++)
    	{
    		a[3]=a[2]=a[1]=a[0];
    		if(!check(a,4))ans++;
    	}
    	return ans;
    }
    int f_2()
    {
    	int a[6];
    	int ans=0; 
    	for(a[0]=0;a[0]<4;a[0]++)
    	{
    		for(a[1]=0;a[1]<4;a[1]++)
    		{
    			a[4]=a[2]=a[0];
    			a[5]=a[3]=a[1];
    			if(!check(a,6))ans++;
    		}
    	}
    	return ans;
    }
    int f_3()
    {
    	int a[6];
    	int ans=0; 
    	for(a[0]=0;a[0]<4;a[0]++)
    	{
    		for(a[1]=0;a[1]<4;a[1]++)
    		{
    			for(a[2]=0;a[2]<4;a[2]++)
    			{
    				a[3]=a[0];
    				a[4]=a[1];
    				a[5]=a[2];
    				if(!check(a,6))ans++;
    			}
    		}
    	}
    	return ans;
    }
    
    ll trans[70][70];//trans[i][j]表示P[i][j]的转移是合法的
    ll f(int d)
    {
    	mat P;
    	P=qpow(ATM,d-3);//计算P
    	ll ans=0;
    	for(int i=0;i<64;i++)
    		for(int j=0;j<64;j++)
    			ans=(ans+P.a[i][j]*trans[i][j])%mod;
    	return ans;
    }
    ll qpow(ll a,ll n)
    {
    	ll ans=1;
    	while(n)
    	{
    		if(n&1)ans=ans*a%mod;
    		a=a*a%mod;
    		n>>=1;
    	}
    	return ans;
    }
    ll inv(ll x)
    {
    	return qpow(x,mod-2);
    }
    void solve(int n)
    {
    	int sqr=sqrt(n);
    	int tot=0;
    	for(int i=1;i<=sqr;i++)//把因数放到列表里面
    	{
    		if(n%i==0)list[++tot]=i;
    	}
    	for(int i=sqr;i>0;i--)
    	{
    		if(n%i==0&&n/i!=sqr)list[++tot]=n/i;
    	}
    	ll ans=0;
    	for(int k=1;k<=tot;k++)
    	{
    		int i=list[k];
    		ll func=0;
    		if(i==1)func=f_1();
    		else if(i==2)func=f_2();
    		else if(i==3)func=f_3();
    		else func=f(i);
    		ans=(ans+func*phi[n/i])%mod;//求和
    	}
    	ans=ans*inv(n)%mod;
    	printf("%lld",ans);
    }
    
    int main()
    {
    	euler(100000);
    	int n,m;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++)
    	{
    		for(int j=1;j<=4;j++)scanf("%d",&str[i][j]);
    		int tmp=0;
    		for(int j=1;j<=4;j++)tmp=tmp*4+str[i][j];
    		pattern[tmp]=1;
    	}
    	ATM.init(64,64);
    	for(int i=0;i<64;i++)//建立一步转移矩阵
    	{
    		for(int ch=0;ch<4;ch++)
    		{
    			int now=i*4+ch;
    			if(pattern[now])continue;
    			else ATM.a[i][now%64]++;
    		}
    	}
    	for(int i=0;i<64;i++)//检测P[i][j]是否合法
    	{
    		for(int j=0;j<64;j++)
    		{
    			int a[6];
    			int num=j*64+i;
    			for(int k=5;k>=0;k--)
    			{
    				a[k]=num%4;
    				num>>=2;
    			}
    			if(!check(a,6))trans[i][j]++;
    		}
    	}
    	solve(n);
    	return 0;
    }
    
  • 相关阅读:
    Linux文件系统介绍
    httpd 2.4连接php-fpm
    基于lnmp环境安装Discuz
    apache 与 php-fpm 几种处理方式
    Discuz!安装搭建
    Linux中实现文本过滤
    httpd-2.4安装配置
    firewall-cmd.man
    了解JSON
    JSTL和EL表达式
  • 原文地址:https://www.cnblogs.com/ssdfzhyf/p/14540647.html
Copyright © 2011-2022 走看看