高二&高一&初三模拟赛25 总结

前言

这次比赛比较简单,然而T1太自信结果炸了,T3脑子坏掉又写错了,真是没办法啊,分数就白白丢掉了好多。。


匹配数

题目描述

一个匹配模式是由一些小写字母和问号’?’组成的一个字符串。当一个由小写字母组成的字符串s,长度和匹配模式长度相同,并且在对应的每一位都相等或模式串相应位置是‘?’,则称字符串s与这个模式相匹配。例如:”abc”与”a?c”匹配地,但不与”a?b”或”abc?”相匹配。
现给你 M 个匹配模式,它们长度相同,问恰好与其中有 K 个模式相匹配的字符串有多少个?(答案模1,000,003)


输入格式

第一行,两个整数 M K。
下面有M行字符串,表示M个匹配模式。

1<= M <= 15
模式长度len满足:1 <= len<= 50
1 <= K <=M
模式中只含小写英文字母和 ‘?’


输出格式

只一行,一个整数(模1000003之后)。


输入样例

样例1:

2 2
a?
?b

样例2

1 1
?????


输出样例

样例1:

1

样例2:

881343
(注:881343 = 26^5 mod 1000003。)


题解(容斥原理)

这题可以用很暴力的状压dp切掉,但是优美的方法是容斥原理。

就是那个被大佬们成为广义容斥原理的东东。

首先注意题目求的是恰好k个匹配。我们枚举出所有状态,然后看看有几个匹配,假如有k个匹配就加进答案。但是可能匹配有多,假如匹配了k+1个就要减,但是减多了k+2的加回来。于是就是一个容斥原理了。

但是广义在于哪里呢?一开始我错了,原因在于假如匹配了k+1的方案数,对k答案的贡献是要被减去的,但减去可不止一个!设方案为v,次数就是 Ckk+1 ,于是当前匹配了p个模式,贡献就是 Ckpvsign 。其中当p-k为偶数时sign=1。当然p < <script type="math/tex" id="MathJax-Element-3"><</script>k不做。剩下的一堆就是细节的处理,具体是怎样算出v,这个很简单,看看有无矛盾,统计?个数就行了。

预处理出组合数会更好。我一开始80分因为取模的时候,可能是负数取模,忘了模数再取模了!QAQ!

时间一点也不复杂。


代码

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#define maxm 20
#define maxl 60
#define mods 1000003

using namespace std;

int m, k, ans;
int Len[maxm];
int C[maxm][maxm];
char s[maxm][maxl], cc[maxl];
bool vis[maxm];

void Da(){
    C[0][0] = 1;
    for(int i = 1; i <= m; i++){
        C[i][0] = 1;
        for(int j = 1; j <= i; j++)
            C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mods;
    }
}

int main(){

    scanf("%d%d", &m, &k);

    Da();

    for(int i = 1; i <= m; i++){  
        scanf("%s", &s[i]);
        Len[i] = strlen(s[i]);
    }

    for(int i = 1; i < (1<<m); i++){
        int temp = i, b = 1, cnt = 0, sign;
        for(int j = 1; j <= m; j++)  vis[j] = false;
        while(temp){
            if(temp & 1){
                vis[b] = true;
                cnt ++;
            }
            temp >>= 1;
            b ++;
        }

        if(cnt < k)  continue;
        else if((cnt - k) & 1)  sign = -1;
        else  sign = 1;

        int len = -1;
        bool ok = true;
        for(int j = 1; j <= m; j++){
            if(!vis[j])  continue;
            if(len == -1)  len = Len[j];
            else if(len != Len[j]){
                ok = false;
                break;
            }
        }

        if(!ok)  continue;

        for(int j = 0; j < len; j++)  cc[j] = '?';

        ok = true;
        for(int j = 1; j <= m; j++){
            if(!vis[j])  continue;
            for(int jj = 0; jj < len; jj++){
                if(s[j][jj] == '?')  continue;
                if(cc[jj] == '?')  cc[jj] = s[j][jj];
                else if(cc[jj] != s[j][jj]){
                    ok = false;
                    break;
                }  
            }
            if(!ok)  break;
        }

        if(!ok)  continue;

        int res = 1;
        for(int j = 0; j < len; j++)  if(cc[j] == '?')  res = res * 26 % mods;

        res = 1LL * res * C[cnt][k] % mods;

        ans = (ans + res * sign + mods) % mods;
    }

    printf("%d\n", ans);

    return 0;
}

统计子串

这里写图片描述


题解(乱搞)

枚举第一段的长度和头,然后右移过程中由于固定了长度,所以进一个出一个,第三维单调。于是 O(n2)

虽然很水,但是有时候枚举开头结尾换成枚举长度的方法是很好的,以长度为阶段使这题变得简单。处理两点间的位置也可以记相对距离而不是具体坐标。这种方法要牢记。

ps:这题暴力好像比正解跑得快,数据真是感人。


代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <cmath>
#include <iostream>
#define maxn 5005
using namespace std;

int D, ans, len;
char s[maxn];
int main(){

    scanf("%d", &D);
    scanf("%s", &s);

    len = strlen(s);

    for(int L = 1; L <= (len>>1); L++){
        int cnt = 0;
        for(int i = 0; i < L; i++)
            if(s[i] != s[i+L])  cnt ++;
        if(cnt <= D)  ans ++;

        for(int i = 1; i+L-1+L < len; i++){
            if(s[i-1] != s[i+L-1])  cnt --;
            if(s[i+L-1] != s[i+L-1+L])  cnt ++;
            if(cnt <= D)  ans ++;
        }
    }

    printf("%d\n", ans);

    return 0;
}

树环转换

题目描述

给定一棵N个节点的树,去掉这棵树的一条边需要消耗值1,为这个图的两个点加上一条边也需要消耗值1。树的节点编号从1开始。在这个问题中,你需要使用最小的消耗值(加边和删边操作)将这棵树转化为环,不允许有重边。
环的定义如下:
(1)该图有N个点,N条边。
(2)每个顶点的度数为2。
(3)任意两点是可达的。
树的定义如下:
(1)该图有N个点,N-1条边。
(2)任意两点是可达的。

对于20%的数据,有1≤N≤10。
对于100%的数据,有1≤N≤1000000。


输入格式

第一行是一个整数N代表节点的个数。
接下来N-1行每行有两个整数U, V(1 ≤ U, V ≤ N),表示双向边(U, V)


输出格式

输出把树转化为环的最小消耗值。


输入样例

4
1 2
2 3
2 4


输出样例

3


题解(贪心+树形DP(bfs))

这是一道好题,我想了好久还码错了。

我们发现将树变成环其实就是先拆成链再连起来。其中一些边不动,另外一些变要断掉然后再和其他边连成链。那我们怎么知道那些边是要切断,哪些边要保留呢?这里我们考虑贪心。

如果一个节点它的儿子没有,不考虑。有1个儿子,肯定不要断掉下面的边。我们先树形DP一下求出所有节点的儿子数siz,这里的儿子要满足上述的情况,记为f。一开始我的错误就是在这里忘了判这个,样例居然还过了。

然后我们再dfs一遍,如果当前的节点的f<2,不用割,如果f>=2,需要割掉自成链的个数就是f-1,因为我们保留其中跨根的一条链,还要删掉与根父亲的连边。为什么不考虑和父亲连边呢?因为这样不会优(贪心)。于是我们只考虑能连到这里的儿子,然后考虑保留一条链,于是我们贪心地算出了最小链操作数,答案就是算出来的*2+1(每个操作包括断与连,最后要连成环要+1)。

这样我们每次连到一个点的儿子数是固定的,然后直接计算就可以了。注意如果是根节点的话要特判。

时间复杂度 O(n)

ps:n太大爆栈,bfs保平安。


代码

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstring>
#define maxn 1000010

using namespace std;

int n;
int ans;
int head_p[maxn], f[maxn], fa[maxn], q[maxn], cur = -1;
struct Adj{int next, obj;} Edg[maxn<<1];

void Insert(int a, int b){
    cur ++;
    Edg[cur].next = head_p[a];
    Edg[cur].obj = b;
    head_p[a] = cur;
} 

void bfs1(){
    int head = 0, tail = 0;
    q[0] = 1;
    while(head <= tail){
        int now = q[head++];
        for(int i = head_p[now]; ~ i; i = Edg[i].next){
            int v = Edg[i].obj;
            if(v == fa[now])  continue;
            fa[v] = now;
            q[++tail] = v;
        }
    }
    for(int i = tail; i > 0; i--){
        int now = q[i];
        if(f[now] < 2)  f[fa[now]] ++;
    }
}

void bfs2(){
    int head = 0, tail = 0;
    q[0] = 1;
    while(head <= tail){
        int now = q[head++];
        if(f[now] >= 2){
            ans += f[now] - 1;
            if(now == 1)  ans --;
        }
        for(int i = head_p[now]; ~ i; i = Edg[i].next){
            int v = Edg[i].obj;
            if(v == fa[now])  continue;
            q[++tail] = v;
        }
    }
}

int main(){

    scanf("%d", &n);

    for(int i = 1; i <= n; i++)  head_p[i] = -1;

    int a, b;
    for(int i = 1; i < n; i++){
        scanf("%d%d", &a, &b);
        Insert(a, b);
        Insert(b, a);
    }

    bfs1();

    bfs2();

    printf("%d\n", ans << 1 | 1);

    return 0;
}

总结

这次比赛本来以为分数可以很理想,结果T1粗心了,T3脑抽了。这主要是我在家码完程序就去看番了,以后一定要检查代码,有空多写对拍,手画数据很必要(像T3)。

还有,别天真地以为smoj是无限栈的。


这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值