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;
    }
    
  • 相关阅读:
    POJ 1811 Prime Test 素性测试 分解素因子
    sysbench的安装与使用
    电脑中已有VS2005和VS2010安装.NET3.5失败的解决方案
    I.MX6 show battery states in commandLine
    RPi 2B Raspbian system install
    I.MX6 bq27441 driver porting
    I.MX6 隐藏电池图标
    I.MX6 Power off register hacking
    I.MX6 Goodix GT9xx touchscreen driver porting
    busybox filesystem httpd php-5.5.31 sqlite3 webserver
  • 原文地址:https://www.cnblogs.com/ssdfzhyf/p/14540647.html
Copyright © 2011-2022 走看看