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