[CSP-S模拟测试63]题解

A.Median

这题的数据生成方式并没有什么规律,所以可以认为是随机数据。

维护一个桶,表示当前K长区间里的值域情况。

并且用变量记录中位数值域上的左侧有多少个数,当区间调整时一并调整桶和这个变量即可。

由于是随机数据,所以每次的调整幅度并不会很大,近似于常数。

复杂度$O(n)$。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e7+2;
int n,K,bu[N<<1];
ll ans=0;
int mod,a[N],b[N];
int pr[12000000],tot;
bool vis[200000005];
void ini()
{
    for(int i=2;i<=190000000;i++)
    {
        if(!vis[i])pr[++tot]=i;
        for(int j=1;j<=tot&&i*pr[j]<=190000000;j++)
        {
            vis[i*pr[j]]=1;
            if(i%pr[j]==0)break;
        }
    }
}

int main()
{
    ini();//cout<<tot<<endl;
    scanf("%d%d%lld",&n,&K,&mod);
    for(int i=1;i<=n;i++)
        a[i]=pr[i]*1LL*i%mod,b[i]=a[i]+a[i/10+1];
    for(int i=1;i<=K;i++)
        bu[b[i]]++;
    int lpos=K-1>>1,rpos=K>>1,cnt=0;
    int p=0;
    while(cnt<=lpos)cnt+=bu[p++];
    p--;
    for(int i=1;i+K-1<=n;i++)
    {
        int _p=p,_cnt=cnt;
        while(_cnt<=rpos)_cnt+=bu[++_p];
        ans+=p+_p;
        bu[b[i]]--;bu[b[i+K]]++;
        if(b[i]<=p)cnt--;
        if(b[i+K]<=p)cnt++;
        while(cnt<=lpos)cnt+=bu[++p];
        while(cnt-bu[p]>lpos)cnt-=bu[p--];
        //cout<<p<<endl;
    }
    cout<<ans/2<<(ans&1?".5":".0")<<endl;
    return 0;
}

B.Game

显然,最优策略即每次都选自己能选的最大的。

同样维护桶,表示当前可选集合内的值域情况。维护一个变量,表示这个集合内可选最大值。

如果要保证复杂度,就应当使这个变量整个过程中在值域上的跳动幅度不超过n。

所以每当一个新数加入集合,判断它和最大值的关系,如果大于最大值就直接被选走。反之把新数加入桶,最大值被选走,暴力跳值域找到新的最大值。

很容易发现这样最大值指针是单调不增的。

时间复杂度$O(nk)$。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
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;
}
typedef long long ll;
int n,a[N],K,P,b[N],key[N],bu[N];
ll ans[2];
void work()
{
    P=read();
    ans[0]=ans[1]=0;
    int now=0;
    for(int i=1;i<=P;i++)
        bu[a[i]]++,now=max(now,a[i]);
    int p=P+1,who=0;
    bu[now]--;ans[who]+=key[now];
    while(!bu[now])now--;
    for(int t=2;t<=n;t++)
    {
        who^=1;
        if(a[p]>=now)ans[who]+=key[a[p]];
        else
        {
            bu[a[p]]++,bu[now]--,ans[who]+=key[now];
            while(!bu[now])now--;
        }
        p++;
    }
    printf("%lld\n",ans[0]-ans[1]);
}
int main()
{
//	freopen("g.in","r",stdin);
    n=read();K=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[i]=a[i];
    sort(b+1,b+n+1);
    int len=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++)
    {
        int now=lower_bound(b+1,b+len+1,a[i])-b;
        key[now]=a[i];
        a[i]=now;
    }
    while(K--)work();
    return 0;
}

C.Park(CEOI2017 Chase)

思路很棒的dp。设$w[x]$为$x$的点权(铁球数),$al[x]$为与$x$相连所有点的点权和。

不难发现,每选择一个点扔下磁铁,就会对答案产生$al[x]-w[fa]$的贡献。

我们要求的是一条有序路径,不妨把它分成从下往上和从上往下两部分。

设$dp[x][i][0]$为从下往上走到x,使用了i个磁铁的最大贡献,$dp[x][i][1]$为从x往下走,使用i个磁铁的最大贡献。

这样就可以区分上一步从哪里来。

之后考虑怎么dp。首先向下dfs,在回溯过程中将儿子y的贡献计入x。注意要先把$dp[x]...$初始化为单点的贡献,先用它与$dp[y]...$拼接起来更新一下答案再进行转移。

$dp[x][i][0]=\max (dp[y][i][0],dp[y][i-1][0]+al[x]-w[y])$

$dp[x][i][1]=\max (dp[y][i][1],dp[y][i-1][1]+al[x]-w[fa])$

因为路径是有序的,所以$S \rightarrow T$与$T \rightarrow S$是不同的。需要把儿子顺序反转后再更新一遍。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
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;
}
const int N=1e5+5;
typedef long long ll;
int n,val,w[N];
int to[N<<1],head[N],nxt[N<<1],tot;
ll dp[N][102][2],al[N],ans;
//0 down to up
//1 up to down
void add(int x,int y)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
void up(int x,int y,int f)
{
    for(int i=1;i<val;i++)
        ans=max(ans,dp[x][i][0]+dp[y][val-i][1]);
    for(int i=1;i<=val;i++)
    {
        ll maxx=max(dp[y][i][0],dp[y][i-1][0]+al[x]-w[y]);
        dp[x][i][0]=max(dp[x][i][0],maxx);
        maxx=max(dp[y][i][1],dp[y][i-1][1]+al[x]-w[f]);
        dp[x][i][1]=max(dp[x][i][1],maxx);
    }
}
void dfs(int x,int f)
{
    stack<int> son;
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(y==f)continue;
        son.push(y);
        dfs(y,x);
    }
    for(int i=1;i<=val;i++)
        dp[x][i][0]=al[x],dp[x][i][1]=al[x]-w[f];
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(y==f)continue;
        up(x,y,f);
    }
    for(int i=1;i<=val;i++)
        dp[x][i][0]=al[x],dp[x][i][1]=al[x]-w[f];
    while(!son.empty())
        up(x,son.top(),f),son.pop();
    ans=max(ans,max(dp[x][val][0],dp[x][val][1]));
}

int main()
{
    n=read();val=read();
    for(int i=1;i<=n;i++)
        w[i]=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        add(x,y);add(y,x);
    }
    for(int x=1;x<=n;x++)
        for(int i=head[x];i;i=nxt[i])
            al[x]+=w[to[i]];
    dfs(1,0);
    cout<<ans<<endl;
    return 0;
}

原文地址:https://www.cnblogs.com/Rorschach-XR/p/11633352.html

时间: 2024-08-30 15:37:31

[CSP-S模拟测试63]题解的相关文章

[CSP-S模拟测试59]题解

以后题解还是单独放吧. A.Divisors 根号筛求所有数的因子,扫一遍去重统计即可. #include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<map> using namespace std; const int N=205; int a[N],m,n; map<int,int> bu; vector<int> re

CSP-S 模拟测试57题解

人生第一次A,B层一块考rank2,虽然说分差没几分,但还是值得纪念. 题解: T1 天空龙: 大神题,因为我从不写快读也没有写考场注释的习惯,所以不会做,全hzoi就kx会做,kx真大神级人物. T2 巨神兵: 大神题,一看数据范围这么小,我们考虑状压,最傻逼的暴力思路是压边,但是这显然不行.正解是压点,设$f[s]$为当前选定点集状态为$s$的方案数. 我们考虑转移,当前选定的点集肯定是可以通过边和没有连过来的点相连构成新的方案.所以转移所以我们考虑枚举补集的子集$k$,设$cnt$为s与k

CSP-S模拟测试69 题解

一如既往的垃圾,又回到了那个场场垫底的自己,明明考场上都想到正解了,但是就是拿不到分,可能是互奶把rp用光了吧以后一定加强训练代码能力. T1: 考场上一直yy矩阵快速幂,虽然自己矩阵快速幂一点都不会还是硬着头皮yy,发现不可做之后并没有及时转化思路,但其实自己预处理的数组就是正解. 切记:不仅矩阵快速幂是log的,普通快速幂也是2333 然后这题其实很水啊,我们设$dp[i][j]$为前$i$列放$j$个棋子的方案数,然后枚举最后一列放多少个棋子就好了. 转移方程为$dp[i][j]=\sum

[CSP-S模拟测试96]题解

以后不能再借没改完题的理由不写题解了…… A.求和 求$\sum \sum i+j-1$ 柿子就不化了吧……这年头pj都不考这么弱智的公式化简了…… 坑点1:模数不定,可能没有2的逆元,那么只要先把乘数里的2去掉就好了. 坑点2:1e18炸long long $\rightarrow$ 慢速乘即可 #include<cstdio> #include<iostream> #include<cstring> #include<vector> using name

[CSP-S模拟测试97]题解

A.小盆友的游戏 感觉题解解释的很牵强啊……还是打表找规律比较靠谱 对于每个人,它构造了一个期望函数$f(x)$,设它的跟班个数为$cnt[x]$,那么令$f(x)=2^{cnt[x]}-1$(??鬼知道为什么要等于这个) 然后再定义当前局面的期望函数为每个人期望函数之和. 然后你会发现每次猜拳后局面期望函数变化量都是1 那么期望步数其实就是终止局面期望函数值-初始局面期望函数值 $ans=2^{n-1}-1-\sum (2^{cnt[x]}-1)$ #include<bits/stdc++.h

[CSP-S模拟测试53]题解

A.u 只涉及到区间修改可以考虑差分,然而如果每一行都差分复杂度还是过高.我们发现差分标记也是连续的(一行横着的一行斜着的),所以可以维护两个 差分的差分,扫两遍统计即可. #include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=2005; int read() { int x=0,f=1;char ch=ge

[CSP-S模拟测试60]题解

回去要补一下命运石之门了…… A.嘟嘟噜 给定报数次数的约瑟夫,递推式为$ans=(ans+m)\% i$. 考虑优化,中间很多次$+m$后是不用取模的,这种情况就可以把加法变乘法了.问题在于如何找到下一次需要取模的位置. 解不等式$ans+km \ge i+k$即可,需要处理一下边界. 据说可以证明复杂度是$O(m \log n)$的,但我不是很会. //考场代码 稍丑 #include<bits/stdc++.h> using namespace std; typedef long lon

[CSP-S模拟测试74]题解

A.梦境 如果不用去重一定要用Multiset……挂30分算是出题人手下留情了. 贪心.把点排序,区间按右端点递增排序.依次考虑每个区间,取能选的最靠左的点即可.multiset维护. #include<cstdio> #include<iostream> #include<cstring> #include<set> #include<algorithm> using namespace std; int read() { int x=0,f=

2018-10-25 模拟测试题解

目录 问题 A: 魏传之长坂逆袭 题目描述 输入 输出 样例输入 样例输出 题解 问题 B: 蜀传之单刀赴会 题目描述 [问题描述] 输入 输出 样例输入 样例输出 题解 问题 C: 吴传之火烧连营 [题目背景] [问题描述] 输入 输出 样例输入 样例输出 [样例解释] [数据规模和约定] 题解 本篇题解也发表于zwcblog作者是同一个人 问题 A: 魏传之长坂逆袭 题目描述 众所周知,刘备在长坂坡上与他的一众将领各种开挂,硬生生从曹操手中逃了出去,随后与孙权一起火烧赤壁.占有荆益.成就霸业