3月28日考试 题解(二分答案+树形DP+数学(高精))

前言:考试挂了很多分,难受……

---------------------

T1:防御

题意简述:给一条长度为$n$的序列,第$i$个数的值为$a[i]$。现让你将序列分成$m$段,且让和最小的一段尽可能得大。求这个最大值。

-------------------------

题意很明显,最小值最大。不难想到二分答案,为节约时间我们在$(minn,sum)$这个区间内维护。考虑用前缀和维护$sum$。(不知道为什么不用前缀和挂掉50分……。

代码:

//二分答案 nlogn=4e6 
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int n,m,a[100005],sum[100005],minn=0x3f3f3f3f;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;    
} 
bool check(int x)
{
    int jishu=0,last=0;
    for (int i=1;i<=n;i++)
    {
        if (sum[i]-sum[last]>=x)
        {
            last=i;
            jishu++;
        }
        if(jishu==m) return true; 
    }
    return false;
}
signed main()
{
    n=read(),m=read();
    for (int i=1;i<=n;i++)
    {
        a[i]=read();
        sum[i]=sum[i-1]+a[i];
        minn=min(minn,a[i]);
    }
    int l=minn,r=sum[n];
    while(l<=r)
    {
        int mid=(l+r)/2;
        if (check(mid)) l=mid+1;
        else r=mid-1;
    }
    printf("%ld",r);
    return 0;
}

T2:切树游戏

题意简述:现在有一颗黑白树。每个节点为黑点或白点。现在让你删去一些边,使每个连通块中恰好有一个黑点。求方案数。答案对1e9+7取模。

------------------

树形DP。

我们设$f[i][0]$为以i为根的树删去一些子树后不含黑点的方案数,设$f[i][1]$为以i为根的树删去一些子树后含黑点的方案数。

则有:

$f[u][1]=f[u][1]*(f[v][0]+f[v][1])+f[u][0]*f[v][1]$

$f[u][0]=f[u][0]*(f[v][0]+f[v][1])$

先对$f[u][1]$进行解释:若当前$u$所在的连通块已含有黑点,若子树含黑点,则删去;若子树不含黑点,则连上。又根据乘法原理得第一部分式子。若当前$u$所在的节点不含黑点,那么只有当子树含黑点时,才连上。由此得出$f[u][1]=f[u][1]*(f[v][0]+f[v][1])+f[u][0]*f[v][1]$。$f[u][0]$同理。

注意两条方程不能调换顺序,因为$f[u][1]$要用到上一阶段$f[i][0]$的值。

对于树形dp我们用dfs递归实现。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
const int MOD=1e9+7;
int head[maxn*2],cnt,f[maxn][2];
int a[maxn],n;
struct node
{
    int next,to;
}edge[maxn];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;    
}
void add(int from,int to)
{
    edge[++cnt].next=head[from];
    edge[cnt].to=to;
    head[from]=cnt;
}
void dfs(int u,int fa)
{
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa) continue;
        dfs(v,u);
        f[u][1]=(f[u][1]*(f[v][0]+f[v][1])%MOD+f[u][0]*f[v][1]%MOD)%MOD;
        f[u][0]=(f[u][0]*(f[v][0]+f[v][1]))%MOD;
    }
}
signed main()
{
    n=read();
    for (int i=2;i<=n;i++)
    {
        int p=read();p++;
        add(i,p);add(p,i);
    }
    for (int i=1;i<=n;i++) a[i]=read(),f[i][a[i]]=1;
    dfs(1,0);
    printf("%ld",f[1][1]);
    return 0;
}

T3;二叉树

题意简述:现在给你含有$n$个节点的二叉树,问组成不同二叉树的方案数。每个点认为是互不相同的。$n\leq 10000$

-----------------

假设我们已经将一个节点定为根。

那么子树组成方案为$f[0]\times f[i-1]+f[1]\times f[i-2]+…+f[i-1]\times f[0]$。

符合卡特兰数的定义。

因为n的数据范围较大,要用到高精度。(卡特兰数有通项公式,但我并不会高精度除法QAQ。

代码:

#include<bits/stdc++.h>
using namespace std;
int a[10005][1000005];
int c[1000005];
void cheng(int f,int h)
{
    for (int i=1;i<=a[f][0];i++)
    {
        int x=0;
        for (int j=1;j<=a[h][0];j++)
        {
            c[i+j-1]=a[f][i]*a[h][j]+x+c[i+j-1];
            x=c[i+j-1]/10;
            c[i+j-1]%=10;
        }
        c[i+a[h][0]]=x;
    }
    int lenc=100000;
    while(!c[lenc]) lenc--;
    c[0]=lenc;
}
void jia(int f)
{
    int x=0;
    int len=1;
    while(len<=c[0]||len<=a[f][0])
    {
        a[f][len]+=c[len]+x;
        x=a[f][len]/10;
        a[f][len]%=10;
        len++;
    }
    a[f][len]=x;
    if (x) a[f][0]+=len;
    else a[f][0]+=len-1;
}
int main()
{
    int n;cin>>n;
    a[1][0]=1;a[1][1]=1;
    a[0][1]=1;a[0][0]=1;
    for (int i=2;i<=n;i++)
    {
        for (int j=0;j<=i-1;j++)
        {
            memset(c,0,sizeof(c));
            cheng(j,i-1-j);
            jia(i);
        }
    }
    int len=100000;
    while(!a[n][len]) len--;
    for (int i=len;i>=1;i--) printf("%d",a[n][i]);
    return 0;
}

 

posted @ 2020-03-28 23:21  我亦如此向往  阅读(138)  评论(0编辑  收藏  举报