首页 > 其他分享 >9.3 上午 becoder 模拟赛总结 & 题解

9.3 上午 becoder 模拟赛总结 & 题解

时间:2024-09-03 19:35:52浏览次数:8  
标签:cnt int 题解 sum becoder ans Size dp 9.3

T1 能量获取

简单的树形 DP,设 $dp_{i,j}$ 表示向 $i$ 节点传递了 $j$ 点能量并全部花费完后能激活的封印石的数量。

显然有:$ans=\sum \max_{j=0}^{j\leq W_i}{dp_{i,j}}(i \in son_0)$,转移的初始状态为 $dp_{i,E_i}=E_i$。

设当前枚举到的节点为 $x$,子节点为 $y$,有经典树上背包转移式:$dp_{x,i}=\max_{j=0}^{j\leq \min(W_j,i)}{dp_{y,j},dp_{x,i-j}}(0\leq i \leq W_x)$。

主要代码如下,时间复杂度 $O(nV_w)$(100pts):

#define N 1005
vector<int> v[N];//v存储树
int n,sum,ans,e[N],w[N],dp[N][105];
void dfs(int x,int fa){
    dp[x][e[x]]=1;
    for(auto y:v[x]){
        if(y==fa)  continue;
        dfs(y,x);
        for(int i=100;i>=0;i--)
            for(int j=min(i,w[y]);j>=0;j--)
                dp[x][i]=max(dp[x][i],dp[x][i-j]+dp[y][j]);
    }
}
int main(){
    for(auto x:v[0]){
        ans+=sum,sum=0,dfs(x,0);
        for(int i=0;i<=w[x];i++)  sum=max(sum,dp[x][i]);
    }
    printf("%d\n",ans+sum);
}

T2 封印一击

设 $sum_i$ 表示封印力度为 $i$ 时的总能量值,则有 $ans=max{sum_i}$。

考虑枚举 $i$,我们用 $in$ 表示 $i$ 被包含在了多少个区间的 $(l,r]$ 中间,则有如果 $i$ 不为任何一个区间的端点时的转移:$sum_i=sum_{i-1}+in$。

对于 $i$ 为一个区间的左端点的情况,我们只需要在 $sum_i$ 计算完毕后 ++$in$ 就可以了。

而对于 $i$ 为一个区间的右端点个情况,我们不仅需要在 $sum_i$ 计算完毕后 --$in$,还需要在 $ans$ 和 $sum_i$ 取了最大值过后,将 $sum_i$ 减去一个 $i$ 值,因为这个区间无法对后面产生贡献了。

这样的时间复杂度是 $O(V)$ 的,我们考虑怎么优化。

已知只有在 $i$ 作为一个区间的左右端点时,才会对 $in$ 以及 $sum$ 的转移产生影响。

所以我们枚举的 $i$ 值就只需要是区间的左右端点了,而 $sum$ 的转移则变成了:$sum_i=sum_{lst}+in*(i-lst)$。

以上就是全部做法了,注意本体的坑点:$ans$ 和 $num$ 的初始化:$ans=\sum a_i$,$num=1$,不然如果只有一个区间的话,将 $ans$ 初始化为 $0$ 就会挂掉。

不多说了,代码贴在下面,时间复杂度 $O(n\log n)$,瓶颈在排序:

#define N 100005
#define LL long long
LL lst,sum,ans;
int n,in,num=1,a[N],b[N];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",a+i,b+i),sum+=a[i];
    ans=sum,sort(a+1,a+n+1),sort(b+1,b+n+1);
    for(int i=1,j=1;j<=n;){
        if(i<=n&&a[i]<=b[j])
            sum+=(a[i]-lst)*in,lst=a[i],in++,i++;
        else{
            sum+=(b[j]-lst)*in;
            if(sum>ans)  ans=sum,num=b[j];
            lst=b[j],in--,sum-=b[j],j++;
        }
    }
    printf("%d %lld\n",num,ans);
}

T3 归途与征程

我们可以发现题意是可以被转化的:对于 $A$ 中所有被 '*' 分割开的字符串,我们需要在 $B$ 的循环同构串,找到每个字符串对应的 $B$ 中子串。

且这些子串要满足条件:不能互相重叠,且起始位置的顺序要与对应字符串在 $A$ 中的顺序相同。

所以题目要求的就是有多少个 $B$ 的循环同构串能达成以上的条件。

考虑通过 KMP 找到所有的字符串在 $B$ 中的所有匹配位置,并用一个 bool 数组 $cld$ 存储起来。

接下来对于所有 $B$ 循环同构串,我们可以通过 DP 来判断是否条件,但这样太慢了,复杂度不满足,所以我们要考虑如何加速。

用数组 $nxt_{i,j}$ 记录 $A$ 中的第 $i$ 个串,在 $B$ 中出现的第一个大于等于 $j$ 的位置,这个数组可以通过 $cld$ 计算求出。

然后对于每个同构串,我们就可以枚举 $A$ 中的每个串,判断在上一个串的位置之后是否有当前枚举的这个串,如果都有就 $ans$++。

值得注意的是,如果 $A$ 的第一个字符不是 "*",那么 $A$ 中的第一个串就必须出现在 $B$ 的开头。

同样,如果 $A$ 的最后字符不是 "*",那么 $A$ 中的最后一个串就必须出现在 $B$ 的末尾。

这道题就是这样了,时间复杂度 $O(nm)$,代码贴在下面,具体细节看代码(100pts):

#define N 105
#define M 200005
char a[N],b[M];
int n,m,lst,ans,cnt,len[N],Next[M],cld[N][M],nxt[N][M];
void get_next(char* p,int plen){//求字符串p的Next值,或者叫pi值
	Next[0]=Next[1]=0;
	for(int i=1;i<plen;i++){
		int j=Next[i];
		while(j&&p[i]!=p[j])  j=Next[j];
		Next[i+1]=(p[i]==p[j])*(j+1);
	}
}
void KMP(char* s,char* p,int slen,int plen,int pos){//在字符串s中匹配字符串p
	get_next(p,plen);
	for(int i=0,j=0;i<slen;i++){
		while(j&&s[i]!=p[j])  j=Next[j];
		if(s[i]==p[j])  j++;
		if(j==plen)	 cld[pos][i-plen+1]=1;
	}
}
int main(){
    scanf("%s%s",a,b),n=strlen(a),m=strlen(b),a[n]='*';
    for(int i=m;i<m+m-1;i++)  b[i]=b[i-m];
    for(int i=0;i<=n;i++){
        if(a[i]=='*'){
            if(i!=lst){
                KMP(b,a+lst,m+m-1,i-lst,++cnt);
                nxt[cnt][m+m-1]=m+m-1,len[cnt]=i-lst;
                for(int j=m+m-2;j>=0;j--){
                    nxt[cnt][j]=nxt[cnt][j+1];
                    if(cld[cnt][j])  nxt[cnt][j]=j;
                }
            }
            lst=i+1;
        }
    }
    for(int l=0;l<m;l++){
        int r=l+m-1,now=l;
        if(a[0]!='*'&&!cld[1][l])  continue;
        if(a[n-1]!='*'&&!cld[cnt][r-len[cnt]+1])  continue;
        for(int i=1;i<=cnt;i++){
            if(nxt[i][now]+len[i]-1>r)  goto cant;
            else now=nxt[i][now]+len[i];
        }
        ans++;cant:;
    }
    printf("%d\n",ans);
}

T4 IIIDX(本题题解是此文的 Latex 修复版本)

转载声明:本题题解是此文的 Latex 修复版本,也是全文唯一一篇转载题解,因为原作者写的是真的很好,所以选择了转载。

首先有一个比较明显的 60pts 贪心:

先把 $d$ 排好序,然后按从小到大的顺序贪心的给每个点选值,同等条件下优先编号大的,于是越小的值会越趋近于放在编号越大的上面。

但是这样在数字重复的情况下是不对的, 比如下面这组数据:

4 3.0
1 1 2 2

贪心会得到 1 1 2 2,而正确答案是 1 2 2 1

因此换个角度考虑,在什么情况下一个点可以取到一个值 $x$?

设这个点的子树大小为 $Size_i$,那么这个点可以取到值 $x$,当且仅当大于等于 $x$ 的还没被取的值的个数 $\geq Size_i$,原因应该是很好理解的,毕竟还要有 $Size_i-1$ 和比 $x$ 大的值放在 $i$ 的子树上才行。

因此我们先对 $d$ 从小到大排序去重,统计每个值的出现次数 $cnt_x$, 然后对于每个数统计一个 $f_x$ 表示大于等于x的还没被取的值的个数为 $f_x$.

假设我们给节点 $i$ 匹配上了一个值 $x$,那么这个策决策对小于等于 $x$ 的值的影响是确定的,因此将所有小于等于 $x$ 的值的 $f$ 数组都减小 $Size_i$,表示这些值的右边可以取的值又减小了 $Size_i$ 个。

我们将和这个操作称为“预定”,因为我们现在并不知道点 $i$ 的子树分别会选择哪些值,但我们知道它们要选几个值,所以我们相当于先告诉后面的人,这个节点 $i$ 已经坐到了 $x$ 这个值上,并且要求后面的人为它的子树留 $Size_i-1$ 个座位。

因为这个决策对大于 $x$ 的值的影响是不确定的,我们暂时没有修改它,但其实这个决策会对它产生影响,那么对于一个值 $k$,在取它之前的决策到底对它产生了什么样的影响呢?

对于一个值 $k$,它的真正的 $f_k$ 其实是 $\min_{i=1}^n{f_i}$,因为每个 $f$ 值都相当于一个后缀和,一个合法的值不能使得 $f_k$ 反而比它前面的 $f$ 值更大,因为前面的 $f$ 值要比 $f_k$ 统计了更多的数。

因为题目要求使得字典序最大,所以我们按照编号从小到大给节点分配值显然是最优的。

因此我们每次就是要在剩下的数上寻找一个最靠右的,并且使得 $\min_{i=1}^n{f_i} \geq Size_i$ 的 $k$。

因为涉及区间修改和查询,我们使用线段树来维护所有的 $f$ 值,每次选好一个值,我们就把一些已经确定的影响从线段树中删除(对一个区间进行 $-Size_i$ 的操作)。

对于每次选值,我们都可以在线段树上进行二分来查找满足上述粗体字要求的最靠右的值。

值得注意的是,在每次查询前,如果一个节点的父亲的影响还没有被撤销的话,应该要撤销它父亲的影响。(即把父亲给子树占的座给加回来 :给 $1$ 到父亲匹配值的 $f$ 值加上 $Size_{fa}-1$)

为什么呢?

想象一下,如果不撤销父亲的影响的话,岂不是相当于别人特意给你占了座,结果你自己还不能坐上去?

因为一个节点的儿子都是连续的,所以我们在撤销的父亲的影响后,会马上把不应该撤销的部分给补上(儿子的子树在不断的加上来),所以不用担心对之后的决策造成影响。

代码如下(代码是贴的我的代码,因为原作者码风有些许诡异,100pts):

#define N 500005
double k;
bool vis[N];
int n,tot,d[N],fa[N],sz[N],cnt[N],ans[N];
struct Segment_tree{//维护区间最小值
    int tr[N<<2],tag[N<<2];
    void build(int l,int r,int p){
        if(l==r)  return tr[p]=cnt[l],void();
        int mid=(l+r)>>1,ls=p<<1,rs=p<<1|1;
        build(l,mid,ls),build(mid+1,r,rs),tr[p]=min(tr[ls],tr[rs]);
    }
    void pushdown(int p,int ls,int rs){
        for(auto son:{ls,rs})  tr[son]+=tag[p],tag[son]+=tag[p];
        tag[p]=0;
    }
    void update(int sl,int sr,int x,int l,int r,int p){
        if(sl<=l&&r<=sr)  return tr[p]+=x,tag[p]+=x,void();
        int mid=(l+r)>>1,ls=p<<1,rs=p<<1|1;
        pushdown(p,ls,rs);
        if(sl<=mid)  update(sl,sr,x,l,mid,ls);
        if(sr>mid)  update(sl,sr,x,mid+1,r,rs);
        tr[p]=min(tr[ls],tr[rs]);
    }
    int query(int x,int l,int r,int p){//线段树二分第一个满足min(cnt[1],cnt[2]...cnt[i])>=x的点
        if(l==r)  return (tr[p]<x?l-1:l);
        int mid=(l+r)>>1,ls=p<<1,rs=p<<1|1;
        pushdown(p,ls,rs);
        if(tr[ls]>=x)  return query(x,mid+1,r,rs);
        return query(x,l,mid,ls);
    }
}SGT;
int main(){
    scanf("%d%lf",&n,&k);
    for(int i=1;i<=n;i++)  scanf("%d",d+i);
    sort(d+1,d+n+1);
    for(int i=1;i<=n;i++){
        if(d[i]!=d[i-1])  d[++tot]=d[i];//去重
        cnt[tot]++;
    }
    for(int i=tot;i>0;i--)  cnt[i]+=cnt[i+1];//cnt是>=d[i]的数
    SGT.build(1,tot,1);
    for(int i=n;i>0;i--)  sz[i]++,fa[i]=int(i/k),sz[fa[i]]+=sz[i];//处理父子关系
    for(int i=1;i<=n;i++){//下一行是为了解除预先占的位,注意每个结点的占位直接出一次,所以用了vis数组
        if(fa[i]&&!vis[fa[i]])  vis[fa[i]]=1,SGT.update(1,ans[fa[i]],sz[fa[i]]-1,1,tot,1);
        vis[fa[i]]=1,ans[i]=SGT.query(sz[i],1,tot,1),SGT.update(1,ans[i],-sz[i],1,tot,1);//占位及得出答案
    }
    for(int i=1;i<=n;i++)  printf("%d ",d[ans[i]]);
}

标签:cnt,int,题解,sum,becoder,ans,Size,dp,9.3
From: https://www.cnblogs.com/tkdqmx/p/18395279

相关文章

  • 算法专项—第十五届蓝桥杯程序设计题解
    前三道属于签到题目;后面才是有难度的!一、读书一本书共n页,小明计划第一天看x页,此后每一天都要比前一天多看y页,请问小明几天可以看完这本书?输入格式一行输入三个整数n,x,y(20≤n≤5000),(1≤x,y≤20),分别表示书的总页数、计划第一天看的页数以及此后每天都要比前一天多看的页数,整......
  • 『主席树』疯狂的颜色序列 题解
    又又又好久没写题解了青花-周传雄三月走过柳絮散落恋人们匆匆我的爱情闻风不动翻阅昨日仍有温度蒙尘的心事恍恍惚惚已经隔世遗憾无法说惊觉心一缩紧紧握着青花信物信守着承诺离别总在失意中度过记忆油膏反复涂抹无法愈合的伤口你的回头划伤了沉默那夜重......
  • SP1843 LEONARDO - Leonardo Notebook 题解
    题目传送锚点博客园食用更佳前置知识1前置知识2首先是判断有无解。先疯狂寻找完所有的环,然后判断是否是偶环,最后如果都是偶环,且偶环的路径数成双成对地出现,或全是奇环,就输出Yes,不然就直接pass掉,输出No。然后我们发现:这里竟然出现了置换群!!!为什么它是置换群呢?我们从群的定......
  • P10878 [JRKSJ R9] 在相思树下 III 题解
    Description给定一个长为\(n\)的序列\(a_{1\dotsn}\),需要对它进行两种操作共\(n-1\)次。对一个长度为\(l\)的序列\(b_{1\dotsl}\)进行一次操作将会把序列变为一个长为\(l-1\)的序列\(c_{1\dotsl-1}\):操作一中,\(\foralli\in[1,l),c_i=\max(b_i,b_{i+1})\);操作......
  • Capital许可管理常见问题解答
    在软件资产管理过程中,企业经常会遇到各种关于许可管理的问题。这些问题不仅影响软件的合规使用,还可能导致不必要的法律风险和成本浪费。作为专业的软件许可管理解决方案提供商,Capital致力于帮助企业轻松应对这些挑战。以下是Capital许可管理中常见的问题及其解答,助您更好地理解和......
  • 【树莓派开发】树莓派GeanyIDE和控制台下C/C++中文乱码问题解决方法
    文章目录情况说明1.设置VS,将文件保存为UTF8编码2.更改GeanyIDE编码设置3.更改树莓派系统设置情况说明之前使用树莓派的时候,遇到了中文乱码的问题。VS2019编译器下写的.c文件,里面的中文注释在树莓派ide上乱码树莓派控制台上,C语言代码输出中文时乱码这里需要调整三个设置来解决该......
  • BUUCTF Reverse题解:第二部分(持续更新)
    Welcomeagain,toC12AK'sRejournal!目录题目传送门前言1.[GWCTF2019]pyre题目传送门前言就是不想上课……Re比上课有意思多了qwq1.[GWCTF2019]pyre下载的文件是.pyc格式,它是.py经编译后的字节码文件,可以使用在线工具进行反编译。我们把文件放入,得到如下代码......
  • BUUCTF Reverse题解:第一部分(已完结)
    WelcometoC12AK'sRejournal!目录题目传送门前言1.easyre2.reverse13.reverse24.内涵的软件5.新年快乐6.xor7.reverse38.helloworld9.不一样的flag10.SimpleRev11.luck_guy12.Java逆向解密13.JustRE14.刮开有奖15.简单注册器结语题目传送门前言现在是......
  • CF2008场题解
    Sakurako'sExam算法:模拟,分类讨论。题意简述:给\(a\)个数字\(1\)和\(b\)个数字\(2\),问能否在每个数字前加上加减号使得原始值为\(0\)。考虑\(1\)的个数如果是奇数,那么一定不行。否则如果\(2\)的个数是偶数,一定可以。当\(2\)的个数为奇数且还可能可以时,判断是否存......
  • ABC369F F - Gather Coins 题解
    题目链接:https://atcoder.jp/contests/abc369/tasks/abc369_f题目大意:在一个\(H\timesW\)的二维迷宫中有\(N\)枚硬币,其中第\(i\)枚硬币在\((R_i,C_i)\)(本题中,我们用\((r,c)\)表示二维迷宫中从上到下第\(r\)行从左到右第\(c\)列的那个格子)。你一开始在迷宫的左......