预测: 100 + 30 +( 10/20) = 140 / 150. 实际: 10 + 30 + 30 =70.
T1:暑假到了,小P正在计划他的假期.小P准备假期去体育馆锻炼或看电影.但体育馆和电影院都有可能当天不开放.因此每一天有 4 中状态,我们用 0,1,2,3 表示.0:体育馆和电影院都关闭;1:体育馆关闭,电影院开放;2:体育馆开放,电影院关闭;3:体育馆和电影院都开放.小P不希望一天没有任何事情可做也不希望连续两天做同一件事,现在请你为小 P 安排假期计划,在满足没有连续两天做同一件事的前提下最小化小 P 无事可做的天数.
S1:先看一眼,2-sat?不会.立即又想到了DP,用f[ i ][ 1/2 ],表示第i天的状态为i,前i天能玩的最大天数.对状态g[ i ]分g[ i ]=0/1/2/3分开讨论.①g[ i ] = 1时,f[ i ][ 1 ]=max{f[ i-1 ][ 2 ]+1},f[ i ][ 2 ] = f[ i -1][ 2 ].②g[ i ] = 2, f [ i ][ 2 ]=max{f[ i -1 ][ 1 ]+1} , f[ i ][ 1 ] = f[ i -1][ 1 ].③g[ i ] = 3,就是①②合并.④g[ i ] = 4 , f[ i ][ 1 ] = f[ i - 1][ 1 ],f[ i ][ 2 ]=f[ i - 1 ][ 2 ].写完测完样例与手玩样例后就以为A了.最后WA声一片.研究一番,自己对天数不选的f[ i ][ 0 ]状态竟然没设计?!这是思维不缜密的后果,丢了90分!最后与sy大佬交流后,发现f[ i ][ 0/1/2]表示前 i 天的休息时间,dp方程显而易见.
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define e exit(0) #define re register #define inf 19260817 int n,ans,f[1000010][3],g[1000010]; int main() { freopen("vocation.in","r",stdin); freopen("vocation.out","w",stdout); scanf("%d",&n); for(re int i=1;i<=n;++i){ scanf("%d",&g[i]); f[i][1]=f[i][2]=inf; } for(re int i=1;i<=n;++i){ f[i][0]=min(f[i-1][0],min(f[i-1][1],f[i-1][2]))+1; if(g[i]==1||g[i]==3) f[i][1]=min(f[i-1][0],f[i-1][2]); if(g[i]==2||g[i]==3) f[i][2]=min(f[i-1][0],f[i-1][1]); } printf("%d",min(f[n][0],min(f[n][1],f[n][2]))); return 0; }
T2:小 P 学习完树形结构后,对树的重心产生了兴趣.已知对于一颗树,重心是树上的某个节点,满足将这个节点删除后形成的所有联通块的大小都不超过整个树大小的一半.可以证明一颗树一定存在一个重心,且最多存在两个重心.现在小 P 想知道,给定一个以 1 为根的树,请问每棵子树的重心分别是哪个点.
S2:树的重心的裸题,只打了两个特殊情况的分.我们先对树处理出倍增数组,在dfs遍历树中处理出size[ x ],maxx[ x ](以x为根的子树中的最大size).对于每个点我们倍增去找maxx[ x ]<=size[ fa ] / 2的 fa的下限,再用size[ x ] >= size[ fa ]/2找出fa的上限.在这个区间符合的fa,ans[fa]=x.
细节: ①if(pos!=x) pos=fa[pos],其实可以参考一下lca倍增最后一步取father的问题.
②size[ x ]*2 >= size[ fa ].其实是为了限制另一边的联通块大小小于size[ fa ] / 2.
③maxx数组作用其实是表示x被割后它的子树中最大联通块的大小.
#include<iostream> #include<cstdio> using namespace std; #define re register const int maxn=1e6+10; int n,id,cnt,fa[maxn],size[maxn],maxx[maxn],head[maxn],ans[maxn],bz[maxn][24]; struct bian{ int to,next; }len[maxn<<1]; void add(int from,int to) { len[++cnt].to=to; len[cnt].next=head[from]; head[from]=cnt; } void dfs(int x) { size[x]=1; for(re int k=head[x];k;k=len[k].next){ int to=len[k].to; dfs(to); size[x]+=size[to]; maxx[x]=max(maxx[x],size[to]); } } void work(int x) { int pos=x; for(re int i=20;i>=0;--i) if(bz[pos][i]&&maxx[x]*2-size[bz[pos][i]]>0) pos=bz[pos][i]; if(pos!=x) pos=fa[pos]; while(pos&&size[x]*2-size[pos]>=0){ if(maxx[x]*2-size[pos]<=0) ans[pos]=x; pos=fa[pos]; } for(re int k=head[x];k;k=len[k].next){ int to=len[k].to; work(to); } } int main() { freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); scanf("%d%d",&n,&id); for(re int i=2;i<=n;++i){ scanf("%d",&fa[i]); bz[i][0]=fa[i]; add(fa[i],i); } dfs(1); for(re int j=1;j<=20;++j) for(re int i=1;i<=n;++i) bz[i][j]=bz[bz[i][j-1]][j-1]; work(1); for(re int i=1;i<=n;++i) printf("%d ",ans[i]); }
T3:小 P 最喜欢数组这一神奇的数据结构了,于是他得到了三个长度为 n 的数组 a,b,c.由于小 P 喜欢简约美,所以他决定只保留每个数组的一个前缀。假设小 P 保留的三个数组前缀长度分别 u,v,w(0 ≤ u, v, w ≤ n),为了保证数组的完备性,小 P 需要保证这三个前缀包含了原数组中的所有元素,并且 u+v+w 最小.现在请你告诉小 P 最小的 u+v+w 是多少?
S3:用map打的假贪心竟然有30分.先咕咕.