NOIP模拟题 2016.11.7 [DP*3]

来源:互联网 发布:什么叫手机网络病毒 编辑:程序博客网 时间:2024/05/08 08:55

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。


维护子树MIN,MAX,SIZE,对于每个节点判断MAX-MIN+1是否等于SIZE即可。
可以算半个DP。。

#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<vector>#include<queue>#include<stack>#include<map>#include<set>#include<string>#include<iomanip>#include<ctime>#include<cctype>#include<algorithm>#ifdef WIN32#define AUTO "%I64d"#else#define AUTO "%lld"#endifusing namespace std;#define smax(x,tmp) x=max((x),(tmp))#define smin(x,tmp) x=min((x),(tmp))#define maxx(x1,x2,x3) max(max(x1,x2),x3)#define minn(x1,x2,x3) min(min(x1,x2),x3)typedef long long LL;template <class T> inline void read(T &x){    x = 0;    T flag = 1;    char ch = (char)getchar();    while(ch<'0' || ch>'9')    {        if(ch == '-') flag = -1;        ch = (char)getchar();    }    while(ch>='0' && ch<='9')    {        x = (x<<1) + (x<<3) + ch - '0';        ch = (char)getchar();    }    x *= flag;}template <class T> T gcd(T a,T b) { return !b?a:gcd(b,a%b); }const int INF=0x3f3f3f3f;const int maxn = 100005;struct Edge{    int to,next;}edge[maxn];int head[maxn];int maxedge;inline void addedge(int u,int v){    edge[++maxedge] = (Edge) { v,head[u] };    head[u] = maxedge;}int size[maxn],MIN[maxn],MAX[maxn];int n;int ans;int root;inline void init(){    memset(head,-1,sizeof(head)); maxedge=-1;    read(n);    LL sum = 0;    for(int i=1;i<n;i++)    {        int x,y;        read(x); read(y);        addedge(x,y);        sum += y;    }    ans = 0;    root = (LL)n*(n+1)/2 - sum;}void dfs(int u){    MIN[u] = MAX[u] = u;    size[u] = 1;    for(int i=head[u];~i;i=edge[i].next)    {        int v = edge[i].to;        dfs(v);        smin(MIN[u],MIN[v]);        smax(MAX[u],MAX[v]);        size[u] += size[v];    }    if(MAX[u]-MIN[u]+1 == size[u]) ans++;}int main(){    freopen("A.in","r",stdin);    freopen("A.out","w",stdout);    init();    dfs(root);    printf("%d",ans);    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。


一开始一直在纠结怎么消除后效性。。。GG
这道题要卡暴力。。写丑了要GG。。。
dp(i,j) 表示选了i个数,最后一个数是 j,那么如果当前是’I’,转移到dp(i-1,1~j-1),这里表示所有上一个数小于j的都是合法的情况,由于这里用了 j 这个数,需要把之前大于等于j的数都加1,给j留位置。。
‘D’情况同理,’?’就是两个情况之和了。
前缀和优化。

#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<vector>#include<queue>#include<stack>#include<map>#include<set>#include<string>#include<iomanip>#include<ctime>#include<cctype>#include<algorithm>#ifdef WIN32#define AUTO "%I64d"#else#define AUTO "%lld"#endifusing namespace std;#define smax(x,tmp) x=max((x),(tmp))#define smin(x,tmp) x=min((x),(tmp))#define maxx(x1,x2,x3) max(max(x1,x2),x3)#define minn(x1,x2,x3) min(min(x1,x2),x3)typedef long long LL;template <class T> inline void read(T &x){    x = 0;    T flag = 1;    char ch = (char)getchar();    while(ch<'0' || ch>'9')    {        if(ch == '-') flag = -1;        ch = (char)getchar();    }    while(ch>='0' && ch<='9')    {        x = (x<<1) + (x<<3) + ch - '0';        ch = (char)getchar();    }    x *= flag;}template <class T> T gcd(T a,T b) { return !b?a:gcd(b,a%b); }const int INF=0x3f3f3f3f;const int mod = 1000000007;const int maxn = 1005;int dp[2][maxn],sum[maxn]; // processing ith number with the last one jinline int Sum(int i,int j) { return (sum[j]-sum[i-1]+mod) %mod; }int main(){    freopen("B.in","r",stdin);    freopen("B.out","w",stdout);    char ch = (char) getchar();    int cur = 0 , last = 1;    dp[cur][1] = 1;    int n = 1;    for(int i=2;ch=='?'||ch=='I'||ch=='D';i++,ch=(char)getchar())    {        n++;        swap(cur,last);        for(int j=1;j<i;j++) sum[j] = sum[j-1] + dp[last][j] , sum[j]%=mod;        memset(dp[cur],0,sizeof(dp[cur]));        if(ch == 'I') for(int j=2;j<=i;j++) dp[cur][j] = Sum(1,j-1); // j from 2 to i        else if(ch == 'D') for(int j=1;j<i;j++) dp[cur][j] = Sum(j,i-1); // j from 1 to i-1        else        {            for(int j=2;j<=i;j++) dp[cur][j] = Sum(1,j-1);            for(int j=1;j<i;j++) (dp[cur][j]+=Sum(j,i-1))%=mod;        }    }    int ans = 0;    for(int i=1;i<=n;i++) (ans+=dp[cur][i])%=mod;    printf("%d",ans);    return 0;}

C
问题描述:
小A非常喜欢字符串,所以小K送给了小A两个字符串作为礼物。两个字符串分别为X,Y。小A非常开心,但在开心之余她还想考考小K。小A定义 L 为X与Y的最长公共子序列的长度(子序列在字符串内不一定连续,一个长度为 L 的字符串有2^L个子序列,包括空子序列)。现在小A取出了X的所有长度为 L 的子序列,并要求小K回答在这些子序列中,有多少个是Y的子序列。因为答案可能很大,所以小K只需要回答最终答案模109 + 7。
输入:
第一行包含一个非空字符串X。
第二行包含一个非空字符串Y。
字符串由小写英文字母构成。
输出:
对于每组测试数据输出一个整数,表示对应的答案。
样例输入:
aa
ab
样例输出:
2
数据范围:
对于20%的数据,1 ≤ |X|,|Y| ≤ 10;
对于100%的数据,1 ≤ |X|,|Y| ≤ 1000。


一般都是dp(i,j,k)表示X串处理到i,Y串处理到j,一共选了k个数的答案。
但是空间时间都要GG。

这道题的优化方法很巧妙,由于我们需要的最终答案是dp(lenx,leny,len_LCS),所有最后不是LCS长度的状态都是废物。。
改变dp表示的意义,dp(i,j)表示处理到i,j,长度为LCS(i,j)的答案。
这样就可以保证最后达到了(lenx,leny)时长度为LCS的长度。

以i为阶段进行dp,dp(i)由dp(i-1)得到,那么对于第i个字符有两种决策,是否选为LCS的一份子。

  • 不选,如果LCS(i-1,j)==LCS(i,j),那么说明这个字符是没有对LCS进行贡献的,dp(i,j) += dp(i-1,j),直接把前面的答案直接继承到这个阶段来。
  • 选,定义p = last(j,a[i]),为a[i]这个字符在Y串中上一次出现的位置,如果LCS(i-1,j) == LCS(i-1,p-1) +1,那么说明这个字符是LCS的一部分,dp(i,j) += dp(i-1,p-1),贪心地把最后一个相同字符用掉,为了给之前的串更大的匹配可能性。

这样一来每个状态转移到的状态都是合法并且不会重复的。
注意是要求X的子序列,所以Y中有再多匹配的方法也只能算作一种子序列方案。

#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<vector>#include<queue>#include<stack>#include<map>#include<set>#include<string>#include<iomanip>#include<ctime>#include<cctype>#include<algorithm>#ifdef WIN32#define AUTO "%I64d"#else#define AUTO "%lld"#endifusing namespace std;#define smax(x,tmp) x=max((x),(tmp))#define smin(x,tmp) x=min((x),(tmp))#define maxx(x1,x2,x3) max(max(x1,x2),x3)#define minn(x1,x2,x3) min(min(x1,x2),x3)typedef long long LL;template <class T> inline void read(T &x){    x = 0;    T flag = 1;    char ch = (char)getchar();    while(ch<'0' || ch>'9')    {        if(ch == '-') flag = -1;        ch = (char)getchar();    }    while(ch>='0' && ch<='9')    {        x = (x<<1) + (x<<3) + ch - '0';        ch = (char)getchar();    }    x *= flag;}template <class T> T gcd(T a,T b) { return !b?a:gcd(b,a%b); }const int INF=0x3f3f3f3f;const int mod = 1000000007;const int maxn = 1005;const int SIGMA_SIZE = 30;inline int idx(char ch) { return ch-'a'+1; }char X[maxn],Y[maxn];int f[maxn][maxn];int lenx,leny;void LCS(){    memset(f,0,sizeof(f));    for(int i=1;i<=lenx;i++)        for(int j=1;j<=leny;j++)        {            f[i][j] = max(f[i-1][j],f[i][j-1]);            if(X[i] == Y[j]) smax(f[i][j],f[i-1][j-1]+1);        }}int last[maxn][SIGMA_SIZE];void Link(){    memset(last,0,sizeof(last));    for(int j=1;j<=leny;j++)    {        memcpy(last[j],last[j-1],sizeof(last[j-1]));        last[j][idx(Y[j])] = j;    }}inline bool init(){    if(!~scanf("%s%s",X+1,Y+1)) return false;    lenx = strlen(X+1);    leny = strlen(Y+1);    Link();    LCS();    return true;}int dp[maxn][maxn];int dynamic(){    memset(dp,0,sizeof(dp));    for(int i=0;i<=lenx;i++) dp[i][0] = 1;    for(int j=0;j<=leny;j++) dp[0][j] = 1;    for(int i=1;i<=lenx;i++)        for(int j=1;j<=leny;j++)        {            if(f[i-1][j] == f[i][j]) (dp[i][j] += dp[i-1][j]) %=mod;            int p = last[j][idx(X[i])];            if(p>=1 && f[i-1][p-1]+1 == f[i][j]) (dp[i][j] += dp[i-1][p-1]) %=mod;        }    return dp[lenx][leny];}int main(){    freopen("C.in","r",stdin);    freopen("C.out","w",stdout);    while(init())    {        int ans = dynamic();        printf("%d\n",ans);    }    return 0;}
0 0