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;
}