【日常摸鱼】牛客挑战赛3
前言
无
A 珂学送分
期望dp,要用前缀和优化一下。。略了
B 遇见
比较水的追及问题。。。但是两个点一开始就在一起要特判掉。
C 位数差
稍微转化一下。。只用求数列中任意两对数的和的位数,这个可以随便套数据结构。
D 蝴蝶
思路大致跟上一场蝴蝶差不多。。而且比上场那个简单多了,连数据结构都不用套。。
E 迷宫
链接
https://ac.nowcoder.com/acm/contest/20/E
题意
给定一个n个点的树,以1为根,边权都是1。一个人从起点S开始随机游走(每一步等概率沿当前点的某一条边走),目的地是根节点。你可以选择k个点做标记,使得人在有标记的点时,他下一步一定向根节点方向的边走一步,并且再也不会回到有标记的点。在最优的标记方案下,人期望多少步能到根节点。
(nleq 100000,kleq 50)
题解
首先树上的随机游走的结论:从(i)到(fa_i)的期望步数为(2size_i-1)。记(F_i) (=) (2size_i-1)
然后如果没有标记的话,答案就是i到根节点所有的期望加起来。
有标记的情况下,这些标记显然打在i到1的路径上。
我们把这条路径从起点到根提取出来,这就变成一个序列(1号点是起点,最后一个点是根)上的DP问题。
式子:(f_{i,k}) (=) (min(f_{j-1,k-1}+1+sum_i-sum_j-2sz_j(i-j)))
(f_{i,k}) 表示前i个点放了k个标记的答案。
(sum_i)表示前i个点的(F_i)之和。
(sz_j)表示第i个点在原树中的(size)值
这个式子的形式明显可以斜率优化。
(Code)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const int N=1e5+10;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
int n,m,K,St;
vector<int> R[N];
int fa[N];
int sz[N];
void dfs(int x){
sz[x]=1;
for(int i=0;i<R[x].size();++i){
dfs(R[x][i]);
sz[x]+=sz[R[x][i]];
}
return;
}
LL a[N],b[N],s[N],c[N];
LL f[55][N];
int q[N];
LD slope(int j,int k){
return (LD)(c[k]-c[j])/(LD)(b[k]-b[j])/(LD)2;
}
int main(){
int u,v;
n=read();K=read();St=read();
for(int i=2;i<=n;++i) {
fa[i]=read();
R[fa[i]].push_back(i);
}
if(St==1){
puts("0");
return 0;
}
dfs(1);
m=0;
while(St!=1){
u=St;St=fa[St];++m;
b[m]=sz[u];
a[m]=(b[m]<<1)-1;
s[m]=s[m-1]+a[m];
f[0][m]=s[m];
}
n=m;
int l,r;
for(int k=1;k<=K;++k){
for(int i=1;i<=n;++i)
c[i]=f[k-1][i-1]-s[i]+(LL)2*i*b[i];
l=1;r=0;
for(int i=1;i<k;++i) {
f[k][i]=f[k-1][i];
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) --r;
q[++r]=i;
}
for(int i=k;i<=n;++i){
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) --r;
q[++r]=i;
while(l<r&&slope(q[l],q[l+1])<=(LD)i) ++l;
f[k][i]=f[k-1][q[l]-1]+1+s[i]-s[q[l]]-(LL)2*(i-q[l])*b[q[l]];
}
}
printf("%lld
",f[K][n]);
return 0;
}
F 01序列
链接
https://ac.nowcoder.com/acm/contest/20/F
题意
给定长度为 n 的01序列,序列中部分位置已经确定,剩余部分01等概率出现。对最终序列求最长不下降序列(如有多种可能序列,则在此基础上最大化1的个数),设最长不下降序列的长度为 len,最长不下降序列中1的个数为 num,求期望 E(len * num) * 2 ^ 10000 对 1000000007 取模的结果。
(nleq 1000)
题解
后面乘的2^10000没什么用,只是把前面E的分母都抵消了而已。。
于是我们要算的就是所有情况的len*num的总和。。
我们先考虑对已知序列,怎么求1最多的最长不下降子序列。。
最终的子序列必然是一段0和一段1。。。
我们从左往右依次考虑,假设当前序列的最优解是前面一段0,然后从第i个位置开始全选1。。
如果后面再放个1,直接取过来就行。。
如果后面放个0,如果从i开始的1的个数不少于0的个数,那么什么都不用做。
如果放0后,从i开始的0的个数大于1的个数,那么这一段全选0的长度一定优于全选1。于是这部分的1全部舍弃掉。
这个过程我们很容易能用dp来维护。。于是这题就做完了。
(Code)
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int INF=1e9;
const int N=1e3+100;
const LL P=1000000007;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
int n;
int a[N];
LL len[N][N],num[N][N],cnt[N][N],dp[N][N];
int main(){
int tt=10000;
n=read();
for(int i=1;i<=n;++i) a[i]=read();
cnt[0][0]=1;
for(int i=0;i<n;++i){
if(a[i+1]==-1) --tt;
for(int j=0;j<=i;++j){
if(a[i+1]!=1){
if(j>0) {
pls(len[i+1][j-1],len[i][j]);
pls(cnt[i+1][j-1],cnt[i][j]);
pls(num[i+1][j-1],num[i][j]);
pls(dp[i+1][j-1],dp[i][j]);
}
else{
pls(len[i+1][j],(cnt[i][j]+len[i][j])%P);
pls(cnt[i+1][j],cnt[i][j]);
}
}
if(a[i+1]!=0){
pls(len[i+1][j+1],(cnt[i][j]+len[i][j])%P);
pls(cnt[i+1][j+1],cnt[i][j]);
pls(num[i+1][j+1],(num[i][j]+cnt[i][j])%P);
pls(dp[i+1][j+1],(dp[i][j]+num[i][j]+len[i][j]+cnt[i][j])%P);
}
}
}
LL ans=0;
for(int i=0;i<=n;++i) pls(ans,dp[n][i]);
ans=(ans%P+P)%P;
for(int i=1;i<=tt;++i) pls(ans,ans);
printf("%lld
",ans);
return 0;
}