8月20日考试题解(思维题+贪心+数论+动态规划)
T2打暴力都能拿80分,可怕。
T1
题目大意:给定一个实数序列$A$。设$S=\sum_{i=1}^n A_i$。你可以做下列操作$n$次:
选择两个未被选过的下标$i,j$,将$A_i$变为不超过$A_i$的最大整数,将$A_j$变为不小于$A_j$的最小整数。要求操作完成后新的序列中元素之和与原来的差值的绝对值尽可能小。现给定一段序列和多个询问区间,要求对询问区间求解。
显然整数对答案是没有影响的。设$cnt$表示区间内的小数个数,$sum$表示区间原来的和,$top$表示区间内所有数都上取整后的和,$n$表示区间最多的操作次数。
显然$top$不是最优的答案,肯定有一些数要选择下取整。对应的操作就是$top$减$1$。但有一些限制条件,要进行分类讨论:
1.$top$不管怎么减。其值都是大于原来的值的。我们直接减就好。
2.注意到如果有最优答案,其值和原来序列和的差值不超过$0.5$。因为如果大于$0.5$我们总有办法将差值调整到小于$0.5$。所以我们在$cnt$,$n$,$floor(top-sum+0.5)$间取$\min$。如果取到最优答案了就先用整数顶上去,整数不够了再用小数,以保证答案的最优性。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=500005; int n,q,l,r; double sum[maxn],t[maxn],x,c[maxn]; int main() { //freopen("data.in","r",stdin); //freopen("ans.out","w",stdout); scanf("%d%d",&n,&q); for (int i=1;i<=n;i++) { scanf("%lf",&x); sum[i]=sum[i-1]+x; t[i]=t[i-1]+ceil(x); c[i]=c[i-1]+(ceil(x)!=floor(x)); } while(q--) { scanf("%d%d",&l,&r); double n=(double)(r-l+1)/(double)2; double cnt=c[r]-c[l-1],zs=(double)(r-l+1)-cnt; double top=t[r]-t[l-1],ss=sum[r]-sum[l-1]; if (top-min(cnt,n)>ss) top-=min(cnt,n); else { double v=min(min(cnt,n),floor(top-ss+0.5)),t=n-v; top-=v; if (zs<t) top-=t-zs; } printf("%.3lf\n",abs(top-ss)); } return 0; }
T2
题目大意:给定一棵含有$n$个结点的树,每条边长度为1。有$m$个形如$A,B,D$的限制条件,意为找到距离$A$和$B$的总长度不超过$D$的点$x$。要求找到满足这$m$个条件的任意一点,如果没有输出$NO$。
一道神奇的贪心题,但并不会证明其正确性。直接说做法:
根据题意,我们将限制条件转化为$dis(A,x)+dis(B,x)\leq D$
设$dist(x,A,B)$表示$x$到链$AB$的距离,则有:
$dist(x,A,B)*2+dis(A,B)\leq D$
接下来我们推式子:
$dist(x,A,B)*2\leq D-dis(A,B)$
$dist(x,A,B)\leq \frac{D-dis(A,B)}{2}$
注意到树上路径长度可以用点的深度表示,我们有:
$dep[x]\geq dep[LCA(A,B)]-\frac{D-dis(A,B)}{2}$
易知$dis(A,B)=dep[A]+dep[B]-2*dep[LCA(A,B)]$,转化为:
$dep[x]\geq \frac{dep[A]+dep[B]-D}{2}$
设不等式右边的值为$w[i]$,显然我们要找到这个最大的$w[i]$,且可以找出对应的$A,B$。然后我们遍历一遍树,找到满足这个条件的且深度最小的点,看这个点能不能满足其他的限制条件。如果不能则无解。反则此点为答案。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=100005; int dep[maxn][5],n,m; int s[maxn],t[maxn],d[maxn]; int head[maxn],cnt,mx,pos; struct node { int next,to; }edge[maxn*2]; 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; } inline void add(int from,int to) { edge[++cnt].next=head[from]; edge[cnt].to=to; head[from]=cnt; } inline void dfs(int now,int fa,int opt) { for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (to==fa) continue; dep[to][opt]=dep[now][opt]+1; dfs(to,now,opt); } } signed main() { n=read();m=read(); for (int i=1;i<n;i++) { int u=read(),v=read(); add(u,v);add(v,u); } dfs(1,0,0); for (int i=1;i<=m;i++) { s[i]=read(),t[i]=read(),d[i]=read(); mx=max(mx,max(0,dep[s[i]][0]+dep[t[i]][0]-d[i])); } for (int i=1;i<=m;i++) if (max(0,dep[s[i]][0]+dep[t[i]][0]-d[i])==mx) { dfs(s[i],0,1),dfs(t[i],0,2),mx=d[i];break; } for (int i=1;i<=n;i++) if (dep[i][1]+dep[i][2]<=mx&&(!pos||dep[i][0]<dep[pos][0])) pos=i; dfs(pos,0,3); for (int i=1;i<=m;i++) if (dep[s[i]][3]+dep[t[i]][3]>d[i]) { printf("NO"); return 0; } printf("%d",pos); return 0; }
T3
题目大意:一个数可以写成多个$2$的次方之和。现给定$T$个数,求解数的拆分方案。例如,$4$有$4$种方案:
$4=2^0+2^0+2^0+2^0$
$4=2^1+2^0+2^0$
$4=2^1+2^1$
$4=2^2$
只会递推的分,$n\leq 10^7$。打表可以发现:
if (i%2) ans[i]=ans[i-1]+ans[i/2+1]; else ans[i]=ans[i-1]+ans[i/2];
正解动态规划。传送门:https://www.cnblogs.com/Invictus-Ocean/p/13557247.html