有趣的题目。
实际上题目就是让我们构造一个自动机让节点数最小。
有一个简单的构造:构造k个点,点i表示现在%k=i,点i向im+j(0<=j<m)连边,意思是前面的部分*=m,后面的加上当前这一位。
这个构造的点数不是最小的。有一个定理可以用来求出最简自动机:
定义2个节点a,b等价为:这两个节点中不能一个包含终态,一个不包含。若转移函数是f,则对于所有c,f(a,c)=f(b,c)。用并查集可以求出答案获得30分。
考虑每次合并的时候在干什么。实际上,im=jm(%k)的2个点可以被合并,因为他们的转移是相同的。
设f(i)表示i接受的最短串为多少,g(i)表示i*m^f(i)%k,则两个节点i,j当f(i)=f(j)且g(i)=g(j)可以合并。
如果i*m^k=j*m^k,则走长度为k的串后,i接受的串集=j接受的串集。
但是如果一个点能够走<k到达终点,则走的前面的串可能有一个节点可以接受而另一个不行的,不符合要求。
通过这个条件,我们可以得到一个算法:每次把所有节点*k(这下节点i的值就是i*m^k),合并权值相同的点(这些点符合上面的合并要求)。
然后删除序列末尾>i-m^k+1的点,每个点累加答案。
原因是因为这些点在经过k次以后,有一条路径会经过0点,不符合条件,要全部删除。
可以获得50分。
接下来的素数的点可以推一下拿到另外10分。
貌似有类欧拿到80分的做法,以后再学。
满分的测试点需要更快的模拟这个过程。
设d=gcd(k,m)
若d=1,则每个节点的出边集都是不同的,无法合并。
把所有点*=m%=k后,所有点都是d的倍数。
若t=k/d,由数论知识可得1~k-1 *m%k的结果的循环节为t,d的所有倍数都会出现。
前面的过程要求把*m%k相同的数合并。实际上可以合并(k-1)-t次
现在还有t个节点。设k'=k/d,m'=m/d。由于d的倍数在集合内都出现了,所以后面的m'个数都要被删除。剩下的数是1,2,....k'-m'
如果输入的数是n,k,要对节点1,2,....v去重。
初始,v=k-1,m=1,k=k
设d=gcd(k,m),t=k/d
如果d=1,根据之前的分析无法合并
若v<=t,则循环节未完全出现,乘以m得到的结果互不相同,无法合并
若v>t,则所有节点可以被合并,合并完后还有t个点,则m'=m/(n/d)
后面的m个点都被删除,所以新v=t-m',k'=t
把所有数都除以d,递归到下一个子问题。
#include<bits/stdc++.h> using namespace std; #define int long long int t,n,k; int gcd(int x,int y){ return y?gcd(y,x%y):x; } signed main(){ scanf("%lld",&t); while(t--){ scanf("%lld%lld",&n,&k); int v=k-1,r=k,m=1; while(v>0){ int d=__gcd(k,n),t=k/d; if(d==1||v<=t)break; r-=v-t; if(!(t/m/(n/d)))break; m*=n/d; v=t-m; k/=d; } printf("%lld ",r); } }