noip模拟题11.7

这篇博客详细解析了三道NOIP模拟题,包括:1)如何确定一棵有根树的美丽子树数量;2)计算满足特定排列表示的排列种类;3)求解两个字符串的最长公共子序列及其子序列出现次数。每道题目都提供了问题描述、输入输出样例、数据范围,并给出了关键思路和部分代码片段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

T1 A题

问题描述:

小A得到了一棵美丽的有根树。这棵树由n个节点以及n - 1条有向边构成,每条边都从父亲节点指向儿子节点,保证除了根节点以外的每个节点都有一个唯一的父亲。树上的节点从1到n标号。该树的一棵子树的定义为某个节点以及从该节点出发能够达到的所有节点的集合,显然这棵树共有n棵子树。小A认为一棵有根树是美丽的当且仅当这棵树内节点的标号构成了一个连续的整数区间。现在小A想知道这棵树上共有多少棵美丽的子树。

输入:

第一行有一个整数n,表示树的节点数。
接下来n–1行,每行两个整数u,v,表示存在一条从u到v的有向边。
输入保证该图是一棵有根树。

输出:

输出一个整数占一行,表示对应的答案。

样例输入:

4
2 3
2 1
2 4

样例输出:

3

数据范围:

对于20%的数据,1 ≤ n ≤ 1000。
对于100%的数据,1 ≤ n ≤ 100000。

一遍dfs,搜出一个节点的子树中权值最大最小的节点和这个节点的子节点有多少个,判断max-min==son-1就可以了。
代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int head[100005],tov[100005],nex[100005],maxn[100005],minx[100005];
int ru[100005],way[100005],son[100005],ans,tot;
void add(int a,int b)
{
    tov[++tot]=b;
    nex[tot]=head[a];
    head[a]=tot;
}
void dfs(int k)
{
    int t=head[k];
    int v=tov[t];
    maxn[k]=minx[k]=k;
    if(v==0)
    {
        ans++;
        return ;
    }
    while(v)
    {
        dfs(v);
        son[k]+=son[v]-1;
        maxn[k]=max(maxn[k],maxn[v]);
        minx[k]=min(minx[k],minx[v]);
        t=nex[t];
        v=tov[t];
    }
    if(maxn[k]-minx[k]==son[k]-1)
        ans++;
}
int main()
{
    freopen("A.in","r",stdin);
    freopen("A.out","w",stdout);
    int root,n;
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        ru[b]++;
        son[a]++;
        if(ru[a]==0)
            root=a;
    }
    for(int i=1;i<=n;i++)
        son[i]++;
    dfs(root);
    printf("%d",ans);
    return 0;
}

T2 B题

问题描述:

对于一个排列,考虑相邻的两个元素,如果后面一个比前面一个大,表示这个位置是上升的,用I表示,反之这个位置是下降的,用D表示。如排列3,1,2,7,4,6,5可以表示为DIIDID。
现在给出一个长度为n-1的排列表示,问有多少种1到n的排列满足这种表示。

输入:

一个字符串S,S由I,D,?组成。?表示这个位置既可以为I,又可以为D。

输出:

有多少种排列满足上述字符串。输出排列数模1000000007。

样例输入:

?D

样例输出:

3

数据范围:

对于20%的数据,S长度≤ 10;
对于100%的数据,S长度 ≤ 1000。

想过用dp,写错之后换暴力…
记dp[i][j]为前 i 个数中最后一个数为 j 的方案数,易想到dp[i][j]=dp[i][j]+sum。
有一个问题,如果前面选了 j ,那就有重复。这里可以证明排除:我们把前 i-1 个数中每个大于 j 的数都+1,那么 j 就可选,不用判重。

代码:

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int mod=1000000007;
char c[1005];
int dp[1005][1005],sum[1005][1005];
int main()
{
    freopen("B.in","r",stdin);
    freopen("B.out","w",stdout);
    scanf("%s",c+1);
    int len=strlen(c+1);
    dp[1][1]=sum[1][1]=1;
    for(int i=2;i<=len+1;i++)
    {
        int tmp=i-1;
        for(int j=1;j<=i;j++)
        {
            if(c[tmp]=='D')
            {
                if(j==1)
                    dp[i][j]=0;
                else
                    dp[i][j]=(dp[i][j]+sum[i-1][j-1])%mod;
            }
            else if(c[tmp]=='I')
            {
                if(j==i)
                    dp[i][j]=0;
                else
                    dp[i][j]=(dp[i][j]+sum[i-1][i-1]-sum[i-1][j-1]+mod)%mod;
            }
            else
                dp[i][j]=(dp[i][j]+sum[i-1][i-1])%mod;
            sum[i][j]=(sum[i][j-1]+dp[i][j])%mod;
        }
    }
    printf("%d",sum[len+1][len+1]);
    return 0;
}

T3 C题

问题描述:

小A非常喜欢字符串,所以小K送给了小A两个字符串作为礼物。两个字符串分别为X,Y。小A非常开心,但在开心之余她还想考考小K。小A定义����为X与Y的最长公共子序列的长度(子序列在字符串内不一定连续,一个长度为����的字符串有2����个子序列,包括空子序列)。现在小A取出了X的所有长度为����的子序列,并要求小K回答在这些子序列中,有多少个是Y的子序列。因为答案可能很大,所以小K只需要回答最终答案模109 + 7。

输入:

第一行包含一个非空字符串X。
第二行包含一个非空字符串Y。
字符串由小写英文字母构成。

输出:

对于每组测试数据输出一个整数,表示对应的答案。

样例输入:

aa
ab

样例输出:

2

数据范围:

对于20%的数据,1 ≤ |X|,|Y| ≤ 10;
对于100%的数据,1 ≤ |X|,|Y| ≤ 1000。

先用一个dp处理出最长公共子序列。
再dp处理字符串,每个地方选或不选。
pre[i][j]数组表示离B串的 i 最近的字母 j 的位置。
代码:

#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int mod=1e9+7;
char x[1005],y[1005];
int f[1005][1005],dp[1005][1005];
int pre[1005][1005],pos[1005];
int maxn,ans,cnt,lenx,leny;
int main()
{
    freopen("C.in","r",stdin);
    freopen("C.out","w",stdout);
    scanf("%s",x+1);
    scanf("%s",y+1);
    lenx=strlen(x+1);
    leny=strlen(y+1);
    leny+=0;
    for(int i=1;i<=lenx;i++)
        for(int j=1;j<=leny;j++)
            if(x[i]==y[j])f[i][j]=f[i-1][j-1]+1;
            else f[i][j]=max(f[i-1][j],f[i][j-1]);
    for(int i=1;i<=leny;i++)
    {
        pos[y[i]-'a']=i;
        for(int j=0;j<26;j++)
            pre[i][j]=pos[j];
    }
    for(int i=0;i<=lenx;i++)dp[i][0]=1;
    for(int i=0;i<=leny;i++)dp[0][i]=1;
    for(int i=1;i<=lenx;i++)
        for(int j=1;j<=leny;j++)
        {
            if(f[i][j]==f[i-1][j])dp[i][j]=dp[i-1][j];
            int op=pre[j][x[i]-'a'];
            if(op&&f[i][j]==f[i-1][op-1]+1)
                dp[i][j]=(dp[i][j]+dp[i-1][op-1])%mod;
        }
    printf("%d",dp[lenx][leny]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值