/**//* 问长度为N的字符串(uppercase)中,至少有K个长度为M的回文串的个数 N<=11
一开始我在那里dp推,发现有重复之类的东西不好搞 看了Sevenkplus的,容斥,感觉好神奇 最多有P = N - M + 1个回文串 由于规模比较小,枚举选出哪几个作为回文串,使得至少有K个,令这个模式为Ti (如N = 3, M= 2 , K = 1 ,一个合法的模式为 010) 则答案为|T0∪T1∪Tc|,这里就要用到容斥了 = ∑|Ti| -∑|Ti∩Tj| +∑|Ti∩Tj∩Tk| 直接套用的话复杂度为2^(2^P) !!
与平常的容斥有点不一样,这里存在很多“Tj包含于Ti”,即其交集就是子集了 如011包含于010 因为满足011的肯定满足010,所以011是010的子集,这里注意了!!二进制枚举Ti的超集Tj,是模式Ti的子集
画个韦恩图,一个集合Tj要计算的次数就是先减去其余集合Ti在这部分算的次数(减完就为空了),再加上1 即为 1 - 其余集合Ti在这部分算的次数 用num[i]表示集合Ti需要算的次数,则num[i] = 1 - ∑num[ii] ii为i的超集 for(int j = i+1 ; j < (1<<P) ; j++) { if((j & i) == i)//j是i的子集 num[j] -= num[i]; } 所以集合Ti对答案的贡献为 ans += num[i]*26^cnt O((2^p)^2) cnt为集合Ti独立变量的个数 对Ti,求独立变量的个数,可以先建图(利用回文串两端相等的性质),然后dfs算出连通块的个数就是独立变量的个数了 */ #include<cstdio> #include<iostream> #include<vector> #include<algorithm>
using namespace std;
class PalindromfulString { public:
bool vi[11]; vector<int>E[11]; int num[1<<10];//记录需要计算的次数 int bitCount(int n) { int cnt = 0; while(n) { if(n&1)cnt++; n>>=1; } return cnt; } long long ipow(int a , int n) { long long ans = 1; for(int i = 0; i < n ; i ++) ans *= a; return ans; } void dfs(int u) { vi[u] = true; for(int i = 0 ; i < E[u].size(); i++) { if(!vi[E[u][i]]) dfs(E[u][i]); } } long long count(int N, int M, int K) { int P = N - M + 1; for(int i = 0 ; i < (1<<P) ; i++)//初始,合法的Ti其num[i]=1,否则为0 { if(bitCount(i) >= K)num[i] = 1; else num[i] = 0; } long long ans = 0; for(int i = 0 ; i < (1<<P) ; i ++) { if(num[i] == 0) continue; for(int j = 0 ; j < N ; j ++) { E[j].clear(); } for(int j = 0 ; j < P; j ++) { if(i & (1<<j)) { int L = j , R = j + M - 1; while(L<R) { E[L].push_back(R); E[R].push_back(L); L++; R--; } } } memset(vi,0,sizeof(vi)); int cnt = 0;//dfs出有多少个连通块,答案就是26^cnt for(int j = 0 ; j < N ; j ++) if(!vi[j]) { cnt++; dfs(j); } ans += num[i]*ipow(26,cnt); for(int j = i+1 ; j < (1<<P) ; j++)//这里应认为j是i的子集 { if((j & i) == i) num[j] -= num[i];//减去各个i对j这个子集已经算过的次数,然后这个地方就空了, //再加上j本身初始的1就是答案了 } } return ans; } }; // //int main() //{ // int n , m , k; // while(cin>>n>>m>>k) // { // PalindromfulString test; // cout<<test.count(n,m,k)<<endl; // } // // return 0; //}
以上的做法很快 另外的做法是枚举放了多少个字母,怎么放,然后判断是否可行,比较慢
/**//* 问长度为N的字符串(uppercase)中,至少有K个长度为M的回文串的个数 N<=11
N比较小,暴力枚举这些位置放了什么,然后判断是否满足条件 dfs(str , cur , used) 在前面cur个中放了used个字母 if cur = N if 满足条件 return A[used] //A[used]指排列A(26,used) else 枚举cur放的字母0 used 复杂度应该是O(MNN!) */
#include<cstdio> #include<cstring> #include<algorithm> #include<string>
using namespace std;
class PalindromfulString { public :
long long A[12]; int N,M,K;
bool chk(string str) { int cnt = 0; for(int i = 0 ; i + M -1 < N ; i ++) { int L = i , R = i + M - 1; bool ok = true; while(L < R) { if(str[L] != str[R]) { ok = false; break; } L ++; R --; } if(ok) cnt++; } return cnt >= K; }
long long dfs(string str ,int cur , int used) { if(cur == N) { return chk(str) ? A[used] : 0; } long long ans = 0; for(int i = 0 ; i < used ; i ++) { ans += dfs(str + (char)('A' + i) , cur + 1 , used); } ans += dfs(str + (char)('A'+used) , cur+1 , used+1); return ans; }
long long count(int _N, int _M, int _K) { N = _N; M = _M; K = _K;
A[0] = 1; for(int i = 1; i <= N ; i ++) { A[i] = A[i-1] * (26-i+1); } return dfs("",0,0); } };
|
|
常用链接
随笔分类
Links
搜索
最新评论
|
|