test20180829

试题限制均为128MB,1Sec
总分150.

试题一

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,将子树中区间统计在子树根中,排序判断,是合法区间则上传区间两端点,不是合法区间则上传-1。
明显的bug,有可能子树不合法但当前根统计后交错一下就合法了。

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
	int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
typedef pair<int,int> pii;
const int INF=0x7fffffff;

const int MAXN=1e5+7;
int n;
struct Edge
{
	int nx,to;
}E[MAXN];
int head[MAXN],ecnt;
bool noroot[MAXN];

void addedge(int x,int y)
{
	E[++ecnt].to=y;
	E[ecnt].nx=head[x],head[x]=ecnt;
}

int ans;

pii dfs(int x)
{
	vector<pii>a;
	for(int i=head[x];i;i=E[i].nx)
		a.push_back(dfs(E[i].to));
	a.push_back(pii(x,x));
	sort(a.begin(),a.end());
	bool valid=1;
	for(int i=0;i<a.size();++i)
	{
		if(a[i].first==-1)
		{
			valid=0;
			break;
		}
		if(i&&a[i].first!=a[i-1].second+1)
		{
			valid=0;
			break;
		}
	}
	if(valid)
	{
		++ans;
		return pii(a[0].first,a[a.size()-1].second);
	}
	else
		return pii(-1,0);
}

int main()
{
  freopen("A.in","r",stdin);
  freopen("A.out","w",stdout);
	read(n);
	for(int i=1;i<n;++i)
	{
		int x,y;
		read(x);read(y);
		addedge(x,y);
		noroot[y]=1;
	}
	int r=0;
	for(int i=1;i<=n;++i)
		if(!noroot[i])
		{
			r=i;
			break;
		}
	dfs(r);
	printf("%d\n",ans);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

标解

简单题,一遍dfs求出每棵子树中最小顶点编号,最大顶点编号以及子树大小即可判断该子树是否满足要求。
这种做法成立的条件是节点编号两两不同。
哈哈,T1爆零导致rank很难看。

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
	int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
typedef pair<int,int> pii;
const int INF=0x7fffffff;

const int MAXN=2e5+7;
int n;
struct Edge
{
	int nx,to;
}E[MAXN];
int head[MAXN],ecnt;
bool noroot[MAXN];

void addedge(int x,int y)
{
	E[++ecnt].to=y;
	E[ecnt].nx=head[x],head[x]=ecnt;
}

int ans;
int sz[MAXN],maxv[MAXN],minv[MAXN];

void dfs(int x)
{
	sz[x]=1;
	minv[x]=maxv[x]=x;
	for(int i=head[x];i;i=E[i].nx)
	{
		int y=E[i].to;
		dfs(y);
		sz[x]+=sz[y];
		minv[x]=min(minv[x],minv[y]);
		maxv[x]=max(maxv[x],maxv[y]);
	}
	if(sz[x]==(maxv[x]-minv[x]+1))
		++ans;
}

int main()
{
  freopen("A.in","r",stdin);
  freopen("A.out","w",stdout);
	read(n);
	for(int i=1;i<n;++i)
	{
		int x,y;
		read(x);read(y);
		addedge(x,y);
		noroot[y]=1;
	}
	int r=0;
	for(int i=1;i<=n;++i)
		if(!noroot[i])
		{
			r=i;
			break;
		}
	dfs(r);
	printf("%d\n",ans);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

试题二

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\)

分析

考场做法

\(f(i,j)\)表示前\(i\)个数满足前\(i-1\)个条件,最后一个为\(j\)的排列数个数。
拓展一下,j表示的其实是排名。
那么已经很好处理了,j确定后前i-1个数是什么就确定了,只需要前i-1个数最后一个满足条件。

  1. i-1为?,那么\(f(i,j)=\sum_{k=1}^{i-1}f(i-1,k)\)
  2. i-1为I,那么\(f(i,j)=\sum_{k=1}^{j-1}f(i-1,k)\)
  3. i-1为D,那么\(f(i,j)=\sum_{k=j}^{i-1}f(i-1,k)\)
    第三种情况为什么k从j开始呢?因为第i-1个数要比i大,而在前i-1个数中j不存在,所以j+1以后向左平移了一位。
    然后我第二维维护的树状数组,其实前缀和就行了。
    时间复杂度\(O(N^2\log N)\)
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
	int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;

const int MAXN=1e3+7,mod=1000000007;
int n;
char s[MAXN];
int f[MAXN][MAXN];

inline int lowbit(int x)
{
	return x&-x;
}

inline void add(int id,int p,int v)
{
	while(p<=n)
	{
		f[id][p]+=v;
		if(f[id][p]>=mod)
			f[id][p]-=mod;
		p+=lowbit(p);
	}
}

inline int query(int id,int p)
{
	int res=0;
	while(p)
	{
		res+=f[id][p];
		if(res>=mod)
			res-=mod;
		p-=lowbit(p);
	}
	return res;
}

int main()
{
  freopen("B.in","r",stdin);
  freopen("B.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1)+1;
	s[0]='?';
	add(0,1,1);
//	cerr<<"init end"<<endl;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=i;++j)
		{
//			cerr<<"procesxing "<<i<<' '<<j<<endl;
			if(s[i-1]=='?')
			{
				add(i,j,query(i-1,n));
			}
			else if(s[i-1]=='D')
			{
				add(i,j,(query(i-1,n)+mod-query(i-1,j-1))%mod);
			}
			else if(s[i-1]=='I')
			{
				add(i,j,query(i-1,j-1));
			}
		}
/*	for(int i=1;i<=n;++i)
		for(int j=1;j<=i;++j)
		{
			cerr<<i<<' '<<j<<" f="<<query(i,j)-query(i,j-1)<<endl;
		}*/
	printf("%d\n",query(n,n));
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

标解

\(𝑑p[𝑖][𝑗]\)表示考虑了排列的前\(𝑖\)个数,其中最后一个数在前\(𝑖\)个数中是第\(𝑗\)大的,并且符合前\(𝑖−1\)个字符的这样的方案数。
考虑𝑠[𝑖−1]=′𝐼′时的转移:
\(𝑑p[𝑖][𝑗]=Σ^{𝑗−1}_{𝑘=1}𝑑p[𝑖−1][𝑘]\),只需要对𝑑p维护一个前缀和即可以做到\(𝑂(1)\)地转移。
在要求为下降或者没有限制的时候同理。
复杂度\(O(N^2)\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 1009
using namespace std;
char s[maxn];
int f[maxn][maxn],sum[maxn][maxn];
const int MOD=1000000007;
int main()
{
	freopen("B.in", "r", stdin);
    freopen("B.out", "w", stdout);
	while(scanf("%s",s+1)!=EOF)
	{
		int n=strlen(s+1);
		memset(f,0,sizeof(f));
		memset(sum,0,sizeof(sum));
		f[1][1]=1;
		sum[1][1]=1;
		for(int i=2;i<=n+1;i++)
		{
			int cur=n+2-i;
			for(int j=1;j<=i;j++)
			{
				if(s[cur]=='D')
				{
					if(j==1)
						f[i][j]=0;
					else
						f[i][j]=(f[i][j]+sum[i-1][j-1])%MOD;
				}
				else if(s[cur]=='I')
				{
					if(j==i)
						f[i][j]=0;
					else
					{
						f[i][j]=(f[i][j]+sum[i-1][i-1])%MOD;
						f[i][j]=(f[i][j]-sum[i-1][j-1])%MOD;
						f[i][j]+=MOD;
						f[i][j]%=MOD;
					}
				}
				else
				{
					f[i][j]=(f[i][j]+sum[i-1][i-1])%MOD;
				}
			}
			for(int j=1;j<=i;j++)
				sum[i][j]=(sum[i][j-1]+f[i][j])%MOD;
		}
		printf("%d\n",sum[n+1][n+1]);
	}
	//system("pause");
	return 0;
}

试题三

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\)

分析

考场50分

以为是以前做过的一道省选题,直接打了上去。没想到是在问X中有多少满足的,没有问对数,所以错了。

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
	int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;

const int MAXN=1e3+7,mod=1e9+7;
char a[MAXN],b[MAXN];
int f[MAXN][MAXN],g[MAXN][MAXN];

int main()
{
  freopen("C.in","r",stdin);
  freopen("C.out","w",stdout);
	scanf("%s",a+1);
	scanf("%s",b+1);
	int n=strlen(a+1),m=strlen(b+1);
	for(int i=0;i<=m;++i)
		g[0][i]=1;
	for(int i=1;i<=n;++i)
		g[i][0]=1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			if(a[i]==b[j])
				f[i][j]=max(f[i][j],f[i-1][j-1]+1);
			if(f[i][j]==f[i-1][j])
			{
				g[i][j]+=g[i-1][j];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}	
			if(f[i][j]==f[i][j-1])
			{
				g[i][j]+=g[i][j-1];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}
			if(a[i]==b[j]&&f[i][j]==f[i-1][j-1]+1)
			{
				g[i][j]+=g[i-1][j-1];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}
			if(a[i]!=b[j]&&f[i][j]==f[i-1][j-1])
			{
				g[i][j]+=mod-g[i-1][j-1];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}
//			cerr<<i<<" "<<j<<" f="<<f[i][j]<<" g="<<g[i][j]<<endl;
		}
//	cerr<<f[n][m]<<endl;
	printf("%d\n",g[n][m]);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}


后来发现只需要改一个地方就行了。

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
	int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;

const int MAXN=1e3+7,mod=1e9+7;
char a[MAXN],b[MAXN];
int f[MAXN][MAXN],g[MAXN][MAXN];

int main()
{
  freopen("C.in","r",stdin);
  freopen("C.out","w",stdout);
	scanf("%s",a+1);
	scanf("%s",b+1);
	int n=strlen(a+1),m=strlen(b+1);
	for(int i=0;i<=m;++i)
		g[0][i]=1;
	for(int i=1;i<=n;++i)
		g[i][0]=1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			if(a[i]==b[j])
				f[i][j]=max(f[i][j],f[i-1][j-1]+1);
			if(f[i][j]==f[i-1][j])
			{
				g[i][j]+=g[i-1][j];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}	
			if(a[i]!=b[j]&&f[i][j]==f[i][j-1]) // edit 1
			{
				g[i][j]+=g[i][j-1];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}
			if(a[i]==b[j]&&f[i][j]==f[i-1][j-1]+1)
			{
				g[i][j]+=g[i-1][j-1];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}
			if(a[i]!=b[j]&&f[i][j]==f[i-1][j-1])
			{
				g[i][j]+=mod-g[i-1][j-1];
				if(g[i][j]>=mod)
					g[i][j]-=mod;
			}
//			cerr<<i<<" "<<j<<" f="<<f[i][j]<<" g="<<g[i][j]<<endl;
		}
//	cerr<<f[n][m]<<endl;
	printf("%d\n",g[n][m]);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

还有更简单的写法

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<queue>
#define ll long long
#define del(a,b) memset(a,b,sizeof(a))
using namespace std;
const int mod=1e9+7;
const int MAXN=1010;
char s1[MAXN]={0},s2[MAXN]={0};
int l1,l2;
int f[MAXN][MAXN]={0};
int cnt[MAXN][MAXN]={0};
int main()
{
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	scanf("%s%s",s1,s2);
	l1=strlen(s1);l2=strlen(s2);
	int n=max(l1,l2);
	for(int i=0;i<=n;i++) cnt[i][0]=cnt[0][i]=1;
	for(int i=1;i<=l1;i++)
	for(int j=1;j<=l2;j++)
	{
		if(s1[i-1]==s2[j-1])
		{
			f[i][j]=f[i-1][j-1]+1;cnt[i][j]=cnt[i-1][j-1];
            if(f[i][j]==f[i-1][j]) cnt[i][j]=(cnt[i][j]+cnt[i-1][j])%mod;
		}
		else
		{
			f[i][j]=max(f[i][j-1],f[i-1][j]);
			if(f[i][j]==f[i-1][j]) cnt[i][j]=(cnt[i][j]+cnt[i-1][j])%mod;
            if(f[i][j]==f[i][j-1]) cnt[i][j]=(cnt[i][j]+cnt[i][j-1])%mod;
            if(f[i][j]==f[i-1][j-1]) cnt[i][j]=(cnt[i][j]-cnt[i-1][j-1]+mod)%mod;
		}
	}
	printf("%d",cnt[l1][l2]);
	return 0;
}

其实避免的是这种转移
\(X[i]=Y[j] \wedge f(i,j)=f(i,j-1)\)
意思是Y[i]其实跟Y中靠前的一个是一样的,那么这是Y的答案增加,与X无关,所以不应该转移g。

标解

首先用\(𝑂(𝑛^2)\)的动态规划处理出𝑑p数组,𝑑p[𝑖][𝑗]表示X串的前i个字符和Y串的前j个字符的最长公共子序列的长度,在这个基础上再进行一个动态规划。用𝑓[𝑖][𝑗]表示在X串的前i个字符中,有多少个长度为𝑑p[𝑖][𝑗]的子序列在Y的前j个字符中也出现了。转移:若𝑑p[𝑖−1][𝑗]𝑑p[𝑖][𝑗],则𝑓[𝑖][𝑗]+=𝑓[𝑖−1][𝑗],表示i这个字符不选;再考虑选i这个字符,找到Y串前j个字符中最靠后的与X[i]相同的字符的位置,设为𝑝 ,若𝑑p[𝑖−1][𝑝 −1]+1𝑑p[𝑖][𝑗],则𝑓[𝑖][𝑗]+=𝑓[𝑖−1][𝑝 −1]。最终的答案即为𝑓[𝑛][𝑚]。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <stack>
#include <bitset>
#define INF 0x3f3f3f3f
#define eps 1e-8
#define FI first
#define SE second
using namespace std;
typedef long long LL;
const int N = 1005;
const int Mod = 1e9 + 7;
char A[N], B[N];
int dp[N][N], f[N][N];
int pre[N][26], pos[30];

inline void add(int &x, int v) {
    x += v;
    if(x >= Mod) x -= Mod;
}

int main() {
    freopen("C.in", "r", stdin);
    freopen("C.out", "w", stdout);
        scanf("%s%s", A + 1, B + 1);
        int n = strlen(A + 1);
        int m = strlen(B + 1);
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= m; ++j) {
                dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
                if(A[i] == B[j]) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
            }
        }
        memset(pos, -1, sizeof(pos));
        for(int i = 1; i <= m; ++i) {
            pos[B[i] - 'a'] = i;
            for(int j = 0; j < 26; ++j) pre[i][j] = pos[j];
        }
        for(int i = 0; i <= n; ++i) f[i][0] = 1;
        for(int i = 0; i <= m; ++i) f[0][i] = 1;
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= m; ++j) {
                f[i][j] = 0;
                if(dp[i][j] == dp[i - 1][j]) add(f[i][j], f[i - 1][j]);
                int p = pre[j][A[i] - 'a'];
                if(p != -1 && dp[i - 1][p - 1] + 1 == dp[i][j]) add(f[i][j], f[i - 1][p - 1]);
            }
        }
        printf("%d\n", f[n][m]);
    return 0;
}

posted on 2018-08-29 16:21  autoint  阅读(138)  评论(0编辑  收藏  举报

导航