2020 HZNU Winter Training Day 10
题意:给n!个n的排列,按字典序从小到大连成一条序列,例如3的情况为:[1,2,3, 1,3,2, 2,1,3 ,2,3,1 ,3,1,2 ,3,2,1],问其中长度为n,且和为sum=n*(n+1)/2的序列有多少个?
思路:我们考虑一下next_perumation函数产生字典序递增的全排列的过程:
假设某一个序列长度为n,最长的递减的后缀长度k,那么它的下一个排列是这样产生的:选取序列第n-k个数,与后k个数中比第n - k个数大的最小的数交换,然后将后k个数按从小到大排序。
例如序列1,2,5,4,3的下一个排列为1,3,2,4,5。我们观察发现:这种时候1,2,(5,4,3,1,3,)2,4,5不满足和为sum了,因为在产生下一个排列的过程中,第n-k个位置的数被替换了。
也就是说,假设一个序列存在长度为k的递减后缀,那么这个后缀不能产生一个长度为sum的序列。例如,1,2,(5,4,3,1,3,)2,4,5不行,但是1,(2,5,4,3,1,)3,2,4,5可以。
所以,我们的任务是找出每个长度为k的递减后缀有多少个?应该为C(n,n-k)*(n-k)!=A(n,n-k)=n!/k!个。因为只要选了前面n-k个数,后面长度为k的递减的序列是固定的,所以我们只需要选n-k个数全排列就行了。
我们可以得到最终的答案了:一共有n*n!-(n-1)个序列,要减去( ∑(k from 1 to n-1) n!/k! )- (n-1)个。
为什么要减去n-1个呢?我们来看最后一个排列(假设n为5)5,4,3,2,1 。5之后的序列不存在,所以要从总的序列数中减去。而这(n-1)个不存在的序列恰好会被判定为不满足题意,也应该减去。
所以总的来说,答案应该是:(所有的序列-不存在的序列)-(不满足的序列-不存在的序列)。我们可以把答案写的更优雅一点:ans=n*n!-∑(k from 1 to n-1) n!/k!。
#include<iostream> #include<cstring> #include<cmath> #include<queue> #include<stack> #include<list> #include<map> #include<set> #include<sstream> #include<string> #include<vector> #include<cstdio> #include<ctime> #include<bitset> #include<algorithm> #include<string.h> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; #define lson l , mid , rt << 1 #define rson mid + 1 , r , rt << 1 | 1 ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); } while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } const int maxn = 1000010; const ll mod = 998244353; ll s[maxn], f[maxn]; int main() { ll n; scanf("%lld", &n); f[0] = 1, s[n] = 1; for (ll i = 1; i <= n; i++) { f[i] = (f[i - 1] * i) % mod; } for (ll i = n - 1; i >= 1; i--) { s[i] = (s[i + 1] * (i + 1)) % mod; } ll ans = (n*(f[n])) % mod; for (ll i = 1; i <= n - 1; i++) { ans = (ans - s[i] + mod) % mod; } cout << ans << endl; return 0; }
F POJ 3691
AC自动机+DP,模板题
题意:给出n个带病毒的DNA与一个将要修改的DNA,要求修改后不能与任何一个带病毒的DNA相匹配,问最少修改次数。修改一次即为改动一个字符(可以为A,G,C,T)。
思路:考虑dp,用dp[i][j]表示当前已经判断到了待修改DNA的第i位,且在AC自动机上已经成功地匹配到了第j号点。
我们枚举将要把待修改DNA的第i为修改成的字符k,显然,如果k与原来的第i+1位相同,则不用修改。
这道题的关键是如何在修改DNA后,在AC自动机上找到下一次匹配的位置,只有找到这个位置,我们才能成功地进行状态转移。
用一个数组next,next[j][k]表示当前在AC自动机上的j号点,将下一位修改成k后,下一次在AC自动机上匹配的点的编号。我们先暂时不讨论转移后是否是带病毒节点,那么:
1.如果当前节点有字符为k的后继节点,直接将next[j][k]赋值成它即可。
2.否则,就到当前节点的fail节点去找
3.如果当前节点的fail节点是一个“危险节点”,则将当前节点也标记为危险节点(它能匹配上病毒串)
#include<iostream> #include<cstring> #include<cmath> #include<queue> #include<stack> #include<list> #include<map> #include<set> #include<sstream> #include<string> #include<vector> #include<cstdio> #include<ctime> #include<bitset> #include<algorithm> #include<string.h> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; #define lson l , mid , rt << 1 #define rson mid + 1 , r , rt << 1 | 1 ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); } while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } const int maxn = 1010; const int M = 4; int m, n, T, t, x, y, u; int ch[maxn][4]; int v[maxn]; int f[maxn], last[maxn], num; char str[maxn]; int d[maxn][maxn]; void clear()//Trie树初始化 { memset(d, -1, sizeof(d)); num = 1; memset(ch[0], 0, sizeof(ch[0])); memset(v, 0, sizeof(v)); memset(last, 0, sizeof(last)); } int idx(char c) { switch (c) { case 'A': return 0; case 'C': return 1; case 'G': return 2; case 'T': return 3; } return 0; } void insert(char str[], int value)//建Trie树 { int len = strlen(str); int u = 0; for (int i = 0; i < len; ++i) { int c = idx(str[i]); if (!ch[u][c])//保存的是结点坐标 { memset(ch[num], 0, sizeof(ch[num])); ch[u][c] = num++;// } u = ch[u][c]; } v[u] = value; } void getac() { queue<int> q;//保存的节点下标 f[0] = 0; for (int c = 0; c < M; ++c) { int u = ch[0][c]; if (u)//不需要优化的else { q.push(u); f[u] = 0; last[u] = v[u];//WA,可能有长度为1的串 } } while (!q.empty()) { int r = q.front(); q.pop(); for (int c = 0; c < M; ++c) { int u = ch[r][c]; if (u) { q.push(u); int s = f[r]; f[u] = ch[s][c]; last[u] = (v[u] || last[f[u]]);//改 } else //重要优化 ch[r][c] = ch[f[r]][c]; } } } int dp(int u, int k) { if (k == n)return 0; int &ans = d[u][k]; if (ans != -1)return ans; ans = inf; for (int i = 0; i < 4; i++) { int c = ch[u][i]; if (last[c] == 0) { ans = min(ans, dp(c, k + 1) + (idx(str[k]) != i ? 1 : 0)); } } return ans; } int main() { int ncase = 0; while (scanf("%d", &m) == 1 && m) { clear(); while (m--) { scanf("%s", str); insert(str, 1); } getac(); scanf("%s", str); n = strlen(str); printf("Case %d: %d\n", ++ncase, dp(0, 0) == inf ? -1 : dp(0, 0)); } return 0; }
#include<iostream> #include<cstring> #include<cmath> #include<queue> #include<stack> #include<list> #include<map> #include<set> #include<sstream> #include<string> #include<vector> #include<cstdio> #include<ctime> #include<bitset> #include<algorithm> #include<string.h> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; #define lson l , mid , rt << 1 #define rson mid + 1 , r , rt << 1 | 1 ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); } while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } int nextt[1010][4], trie[1010][4], val[1010], ncnt; int idx(char c) { if (c == 'A') return 0; if (c == 'G') return 1; if (c == 'C') return 2; return 3; } void init() { memset(trie[0], 0, sizeof(trie[0])), memset(nextt[0], 0, sizeof(nextt[0])), memset(val, 0, sizeof(val)), ncnt = 0; } void insert(char s[], int len) { int now = 0; for (int i = 1; i <= len; i++) { int c = idx(s[i]); if (!trie[now][c]) { now = trie[now][c] = ++ncnt; memset(trie[now], 0, sizeof(trie[now])); } else now = trie[now][c]; } val[now] = 1; } queue<int> q; int fail[1010]; void bfs() { while (!q.empty()) q.pop(); for (int i = 0; i < 4; i++) if (trie[0][i]) q.push(trie[0][i]), fail[trie[0][i]] = 0, nextt[0][i] = trie[0][i]; else nextt[0][i] = 0; while (!q.empty()) { int now = q.front(); q.pop(); for (int i = 0; i < 4; i++) { if (trie[now][i]) { int f = fail[now]; while (f && !trie[f][i]) f = fail[f]; int son = trie[now][i]; fail[son] = trie[f][i], q.push(son); nextt[now][i] = trie[now][i]; val[son] |= val[fail[son]]; } else nextt[now][i] = nextt[fail[now]][i]; } } } int dp[1010][1010], n; int Dp(char s[], int n) { memset(dp, 1, sizeof(dp)); dp[0][0] = 0; for (int i = 0; i < n; i++) for (int j = 0; j <= ncnt; j++) { for (int k = 0; k < 4; k++) if (!val[nextt[j][k]]) dp[i + 1][nextt[j][k]] = min(dp[i + 1][nextt[j][k]], dp[i][j] + (idx(s[i + 1]) != k)); } int ans = 0x3f3f3f3f; for (int j = 0; j <= ncnt; j++) if (!val[j]) ans = min(ans, dp[n][j]); return ans < 1e7 ? ans : -1; } char s[1010]; int main() { int n, cas = 0; while (~scanf("%d", &n) && n) { init(); for (int i = 1; i <= n; i++) scanf("%s", s + 1), insert(s, strlen(s + 1)); bfs(); scanf("%s", s + 1); printf("Case %d: %d\n", ++cas, Dp(s, strlen(s + 1))); } }