51nod 1583 犯罪计划——矩阵乘法优化dp
文泽想在埃及做案n次,并且想在最后不用得到惩罚。案件的被分成几种类型。比如说,案件A,当案件A被重复犯两次时,案件A将被认为不是犯罪案件,因此犯案人不用得到惩罚。也就是说,案件A被犯偶数次时,犯案人将不用得到惩罚。又比如案件B,当案件B被犯的次数是5的倍数时,犯案人将不用得到惩罚。
更具体的说,现在知道有c组条件。每组条件包含的信息如下:
1. 案件类型 ti ,
2. 底数 mi ,表示该类型案件重复n* mi (n是非负整数)次时,犯案人可以不用得到惩罚。
对于同一种案件,他的条件可能会被列出多个,在这种情况下,犯案人对该种案件犯罪次数至少满足其中一个条件就可以不受到惩罚。
文泽想要知道他犯n次罪后,并且不得到惩罚的犯罪情况的种数。
不得到惩罚的条件是,对于所有犯过n个案件的所有案件类型中,每种类型的案件都不用得到惩罚。
每个案件的犯罪顺序是有意义的。说明一下,现在有两种犯罪的案件序列W1和W2,如果对于所有的i(1≤i≤n)使得 W1i=W2i ,我们就认为这两个案件序列是一样的。
样例解释:
在样例中,有16种情况,分别为: AAAAA, AAABB, AABAB, AABBA, ABAAB, ABABA, ABBAA, BAAAB, BAABA, BABAA, BBAAA, ABBBB, BABBB, BBABB, BBBAB, BBBBA.
Input
单组测试数据
第一行包含两个整数,n和c(0≤n≤10^18,0≤c≤1000),
分别表示文泽想要犯罪案件的个数和避免被惩罚的条件的组数。
接下来有c行。每行表示一组条件。
每组条件包含案件类型ti,和底数mi
这里ti一共有26种案件类型,分别用26个大写字母表示。
每个底数mi是一个正整数,所有mi的乘积不会超过123。
有些条件可能被重复列出多次。
如果某些案件类型没有在条件中被列出,那么文泽将不会考虑去犯这些类型的案件。
Output
共一行,表示文泽犯n次罪后,并且不得到惩罚的犯罪情况的种数对12345取余。
Input示例
5 2
A 1
B 2
Output示例
16
(吐槽 对于一个刚开始学矩乘的人来说 这种题还是有点困难 存着以后加深理解
————————————————————————————————
这道题呢 因为所有mi的乘积不会超过123 所以本质不同的状态(S)只有<=123种
因为 对于每个有限制条件的字符,只需记录(它的出现次数)%(限制条件的mi之积)
枚举n以及状态 可以做到n*123^2吧
那就可以矩阵乘法做到123^3logn了(ccz原话
类似(f[i][S]表示考虑前i个字符,字符出现情况是S的方案数)
因为状态很少 我们可以预处理状态 然后离散化成整数
我直接用vector表示26维的状态 然后在离散化(利用map)
S有26维,dfs求出所有S,每个S直接用一个大小26的vector存,放进map离散化,枚举转移就行辣
具体不好讲 看看代码吧QAQ 不懂可以私我(我尽力
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<map> #define LL long long using namespace std; const int mod=12345; LL read(){ LL ans=0,f=1,c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+(c-'0'); c=getchar();} return ans*f; } LL mx=1,n,m; char ch[15]; int k,vis[37],ed[37][155],h[37][155]; int b[137][137],c[137][137],d[137][137]; int gcd(int a,int b){return b?gcd(b,a%b):a;} int lcm(int a,int b){return a/gcd(a,b)*b;} vector<int>v1(26); map<vector<int>,int>mmp; int cnt; void dfs(int k){ if(k==26){mmp[v1]=cnt++; return;} if(vis[k])for(int i=0;i<vis[k];i++){v1[k]=i; dfs(k+1);} else{v1[k]=0; dfs(k+1);} } void pmod(int b[137][137],int c[137][137]){ for(int i=0;i<mx;i++) for(int j=0;j<mx;j++) d[i][j]=0; for(int i=0;i<mx;i++) for(int k=0;k<mx;k++) for(int j=0;j<mx;j++) (d[i][j]+=b[i][k]*c[k][j])%=mod; for(int i=0;i<mx;i++) for(int j=0;j<mx;j++) b[i][j]=d[i][j]; } int main() { n=read(); m=read(); for(int i=0;i<m;i++){ scanf("%s",ch); k=read(); vis[ch[0]-'A']=1; ed[ch[0]-'A'][k]=1; } for(int i=0;i<26;i++) if(vis[i]){ for(int j=0;j<133;j++) if(ed[i][j]) vis[i]=lcm(vis[i],j); mx*=vis[i]; for(int j=0;j<133;j++) if(ed[i][j]){ for(int k=0;k<vis[i];k+=j) h[i][k]=1;//左闭右开 } } dfs(0);//预处理出所有状态 map离散化 int v2; for(map<vector<int>,int>::iterator it=mmp.begin();it!=mmp.end();it++){//处理状态之间的转移 v1=it->first; v2=it->second; for(int i=0;i<26;++i)if(vis[i]){ v1[i]=(v1[i]+1)%vis[i]; ++c[mmp[v1]][v2]; v1[i]=(v1[i]-1+vis[i])%vis[i]; } } for(int i=0;i<mx;i++) b[i][i]=1; for(;n;n>>=1,pmod(c,c)) if(n&1) pmod(b,c); int ans=0; for(map<vector<int>,int>::iterator it=mmp.begin();it!=mmp.end();it++){//枚举状态是否合法 bool f=false; v1=it->first; v2=it->second; for(int i=0;i<26;i++) if(vis[i]&&!h[i][v1[i]]) f=true; if(!f) ans=(ans+b[v2][0])%mod; } printf("%d\n",ans); return 0; }