2020 HZNU Winter Training Day 10

B   CodeForces 1091D

题意:给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;
}
View Code

 

 

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;
}
View Code
#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)));
    }
}
View Code
posted @ 2020-02-07 15:34  Fzzf1  阅读(182)  评论(0编辑  收藏  举报