老师说下午要让我们(来自开明的新高一同学)感受一下CF,于是下午2:20我们就集中到了机房。老师教我们用Educational Codeforces Round 46 (Rated for Div. 2开了一场Virtual participation,然后就让我们自己打了。

拿到题目先看A题,大意就是让你通过修改最少的字符(不能删除或添加),将A组字符串修改为B组(不考虑顺序,保证有解)。拿到题目我就傻眼了,一般CF的div2的题目AB都是比较的简单的呀,而这一道题拿到手尽然毫无思路,以至于丧失了理智,一上来先把A和B全都按字典序排了一下序,然后对应比较,然后成功地Wrong answer on test 7……

然后我看到红色的WA,冷静过来,开始理智地分析。因为这些字符串全是T恤衫的编号,所以一共只能有一下几种字符串:

L
M
S
XL
XS
XXL
XXS
XXXL
XXXS

题目又保证有解,所以每种长度的字符串在两组中的数量是一样的。可以发现,把通长度的一个字符串替换为另一个,代价只需要1。因此,我们只需要统计一组中每种字符串的出现次数,然后在把另一组的每一个字符串在A中找有没有相同串,有就把该串的数量-1,否则ans++。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<iostream>
#include<map>
using namespace std; const int maxn=100+7;
int n,ans;string a[maxn],b[maxn];
map<string,int>m; int main(){
ios::sync_with_stdio(false);cin>>n;for(register int i=1;i<=n;++i)cin>>a[i],m[a[i]]++;
for(register int i=1;i<=n;++i){cin>>b[i];if(m[b[i]])--m[b[i]];else ++ans;}
cout<<ans<<endl;
return 0;
}

然后又转过来看第二题,有一台灯,这个灯在时间为 0 时打开, m 时关闭,在 0 到 m 这段时间内有 n 个时间点灯的状态会改变(即开变关,关变开),现在可以在剩余的时间点选一个让灯的状态改变一次,求这个灯最大亮着的时间。一开始没注意到可以在剩余的时间点选一个改变灯的状态,所以按样例模拟,感觉有问题,回头看题目(orz我在也不看翻译了)才注意到。于是这道题就很简单啦。我们预处理一下s1[i]与s2[i],表示i次开关往前的奇数空隙的时间和和偶数空隙的时间和,然后对于每一次关灯,比较s1[i]+s2[n]-s2[i]-1的大小(在前面放和在后面放都是一样的),更新ans即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std; const int maxn=1e5+7,maxm=1e9;
int n,m,a[maxn],ans,la,s1[maxn],s2[maxn];bool flag=1; int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;++i)scanf("%d",&a[i]),ans+=i&1?a[i]-a[i-1]:0;if(!(n&1))ans+=m-a[n];a[++n]=m;
for(register int i=1;i<=n;++i)s1[i]=s1[i-1]+(i&1?a[i]-a[i-1]:0),s2[i]=s2[i-1]+(i&1?0:a[i]-a[i-1]);
for(register int i=1;i<=n;++i)if(i&1)ans=max(ans,s1[i]-1+s2[n]-s2[i]);printf("%d\n",ans);
}

然后开始看第三题。给你n个区间,求被这些区间覆盖层数为\(k(k<=n)\)的点的个数。一开始看到题目,一眼看出可以用差分做,对于一个区间[l,r],我们可以s[l]++,s[r]--,然后从前往后累加s,就可以表示出一个点的覆盖层数了。然后发现\((0<=l<=r<=10^{18})\),可能需要离散化一下。但我想起夏令营时的惨痛教训,对于这种可能需要统计有多少个点的题目,离散化时候需要当心。未来方便统计被离散化的点之间有多少空隙,我又把l+1和r+1也都加入了离散序列,然后统计答案时,对于一个在询问中出现的位置,直接ans[s[i]]++,否则ans[s[i]]+=s[i+1]-s[i-1]-1表示这边世界上是s[i+1]-s[i-1]个点。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll; const int maxn=2e5+7;
int n,tot,dis,p[maxn<<2];bool check[maxn<<2];ll l[maxn],r[maxn],a[maxn<<2],ans[maxn]; inline int Find(ll x){int l=1,r=dis;while(l<r){int m=(l+r)>>1;if(a[m]>=x)r=m;else l=m+1;}return l;}
inline void Discrete(){
sort(a+1,a+tot+1);for(register int i=1;i<=tot;++i)if(i==1||a[i]!=a[i-1])a[++dis]=a[i];
for(register int i=1;i<=n;++i)check[l[i]=Find(l[i])]=1,check[r[i]=Find(r[i])]=1;
} int main(){
scanf("%d",&n);
for(register int i=1;i<=n;++i){scanf("%I64d%I64d",&l[i],&r[i]);a[++tot]=l[i],a[++tot]=r[i],a[++tot]=l[i]+1,a[++tot]=r[i]+1;}
Discrete();for(register int i=1;i<=n;++i)p[l[i]]++,p[r[i]+1]--;
for(register int i=1;i<=dis;++i){
p[i]+=p[i-1];
if(check[i])ans[p[i]]++;else ans[p[i]]+=a[i+1]-a[i-1]-1;
}
for(register int i=1;i<=n;++i)printf("%I64d%c",ans[i],i==n?'\n':' ');return 0;
}

然后看D题。这道题可有意思了。如果一个数组$ [a_1,a_2,a_3,...,a_n]a_1=n-1\(并且\) a1>0a1>0 $,这个数组就被叫为好数组,如果一个序列能正好分为多个好数组,ta就被叫为好序列,现在给定一个序列,求这个序列有多少好子序列,答案对 998244353998244353 取模。

一开始以为子序列必须是连续的,所以我就把它抽象成了一棵树,做树形dp(其实直接dp也可以)。然后发现样例二死活过不去,随重新读题,看到样例二的解释,才知道子序列可不连续。于是随手把树形dp改成了一个“图形dp”,然后感觉这样很没意思,反正是个DAG,还不如直接dp好呢。于是写了一个普通的dp。附dp方程。

\[dp[i]:\text{表示在i..n的区间内,以i为开头的好子序列的个数(i位置必须选)}
\]

\[dp[i]=\sum \limits_{j=i+a[i]}^n C_{j-i-1}^{i-a[i]-1} \cdot (1+\sum \limits_{k=j+1}^n dp[k])
\]

解释一下:j枚举的是以i开头的好数组的尾坐标,由于i,j的位置已经固定,所以统计组合数的时候需要把\(i-a[i]+1\)以及\(j-i+1\)都减2。后面的\((1+\sum \limits_{k=j+1}^n dp[k])\),1表示如果该好数组后面没有继续接其他好数组,也就是只有一段的好子序列。\(\sum \limits_{k=j+1}^n dp[k]\)表示可以继续接在后面的好子序列。

然而我们发现这样做的时间复杂度为\(O(n^3)\),对于n<=1000的数据肯定会超时。于是,我们可以把dp[i..n]的和在计算dp值得时候先开一个sumv[i]数组处理一下。这样可以把时间降到二维的,1000就可以过了。最后我们的dp值还只是自己开头的好子序列的个数,我们需要把dp值求一下和。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std; const int maxn=1e3+7,MOD=998244353;
int n,a[maxn],dp[maxn],ans,sumv[maxn],c[maxn][maxn]; int main(){
scanf("%d",&n);
for(register int i=0;i<=n;++i){
c[i][0]=1;
for(register int j=1;j<=i;++j)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
}
for(register int i=1;i<=n;++i)scanf("%d",&a[i]);
for(register int i=n;i;--i){
for(register int j=i+a[i];a[i]>0&&j<=n;++j)
(dp[i]+=1ll*c[j-i-1][a[i]-1]*(1+sumv[j+1])%MOD)%=MOD;
(sumv[i]=sumv[i+1]+dp[i])%=MOD;(ans+=dp[i])%=MOD;
}
printf("%d\n",ans);
}

然后就是E题,当时在比赛里没写完,后来又订正好了。给定一个 n 个点 m 条边的无向图,找到两个点 s,t,使得 s 到 t 必须经过的边最多(一条边无论走哪条路线都经过ta,这条边就是必须经过的边),\(2<=n<=3*10^5,1<=m<=3*10^5\)。

拿样例1画个图:



图中红边就是从4到5必须经过的边。途中1,2,3构成了一个无向环(不知道这样叫恰不恰当,因为在无向图中其实只要有边就有环),可以发现,只要是环,环中的边就一定不是必须经过的。我们可以通过跑一遍Tarjan(因为这是无向图,如果直接跑Tarjan的话会发现整个图都是一个环,所以我们需要禁止从子节点跑到父节点(这不是一棵树,所以父节点指的是从哪个节点跑过来的)),找出所有的环,把它们都各自缩成一个点,然后这样的话,整个图就成了一棵树。那么问题就转化成了,在树中找两个点,使它们之间的路径经过的边最多。这不就是求树的直径吗?

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std; const int maxn=3e5+7;
int n,m,x,y; struct{int v,Next;}g[maxn<<1],p[maxn<<1];int head[maxn],head2[maxn],tot,tot2;
inline void addedge(int x,int y){g[++tot].v=y;g[tot].Next=head[x];head[x]=tot;}
inline void addedge2(int x,int y){p[++tot2].v=y;p[tot2].Next=head2[x];head2[x]=tot2;}//错误笔记:注意addedge2里面是tot2不是tot int scc[maxn],pre[maxn],low[maxn],dfn,stk[maxn],top,sccno;
inline void Tarjan_dfs(int x,int fa){
pre[x]=low[x]=++dfn;stk[++top]=x;
for(register int i=head[x];i;i=g[i].Next){
int y=g[i].v;if(y==fa)continue;
if(!pre[y]){Tarjan_dfs(y,x);if(low[y]<low[x])low[x]=low[y];}else if(!scc[y]&&pre[y]<low[x])low[x]=pre[y];
}
if(pre[x]==low[x]){++sccno;while(1){int y=stk[top--];scc[y]=sccno;if(x==y)break;}}
}
inline void Tarjan(){for(register int i=1;i<=n;++i)if(!pre[i])Tarjan_dfs(i,0);}
inline void Shrink(){for(register int i=1;i<=n;++i)for(register int j=head[i];j;j=g[j].Next)if(scc[i]!=scc[g[j].v])addedge2(scc[i],scc[g[j].v]);} int dp[maxn],ans;bool visit[maxn];
inline void dfs(int x){
visit[x]=1;
for(register int i=head2[x];i;i=p[i].Next){
int y=p[i].v;if(visit[y])continue;dfs(y);
ans=max(ans,dp[x]+dp[y]+1);
dp[x]=max(dp[x],dp[y]+1);
}
} int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;++i)scanf("%d%d",&x,&y),addedge(x,y),addedge(y,x);
Tarjan();Shrink();dfs(1);printf("%d\n",ans);
for()
}

F题的话,我一开始写了一个暴力,就是用莫队来维护,写一个treap来记录有哪些数。然后发现在第八个点就TLE了。

于是开始认真地想。总感觉这道题会跟[SDOI2009]HH的项链有点相似之处。于是我也像那道题一样,思考对于每一个点维护它的pre值,也就是这个点上的数上一次出现的位置。然后发现这样似乎不大好求出哪个数只出现一次?于是我用那道题的另一种做法来思考,先把询问按r来分开存储一下,然后把数组里面的每一个数一次加入到线段树里面去。那道题是维护的是“该位置是否是当前最后一次出现这个数”,是就是1,否则就是0,但是这样做似乎也不大行得通。想要判断一个数是否出现一次,应该还是跟pre值有点关系的。于是我试着去把两种做法结合起来:线段树维护到目前为止,1..i位置的pre值,然后统计最小的pre值是否小于l。然后为了防止前面重复的一个数的pre搞鬼,所以我们每加入一个数a[i],都要把pre[i]的位置在线段树里面清为INF,以防这个数<l,但是它并不是只出现1次的。这样的话,总结一下我们的做法:将所有询问按r坐标分类存储起来,然后从1到n,将每一个数的pre值放入线段树,同时将pre[pre[i]]清为INF,同时对于以i为r坐标的询问,我们统计l..r区间内最小的pre是否小于l,是我们就输出那个最小的pre的位置上的数值,否则输出0。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define lc o<<1
#define rc o<<1|1
using namespace std; const int maxn=5e5+7,INF=5e5+1;
int n,m,x,y,a[maxn],pre[maxn],lst[maxn]; struct{int v,Next,id,ans;}l[maxn];int head[maxn],tot;
inline void Add(int x,int y,int id){l[++tot].v=y;l[tot].id=id;l[tot].Next=head[x];head[x]=tot;} struct Node{int minv=INF,mink=INF;}t[maxn<<2];struct Pair{int a,b;inline bool operator<(const Pair&B)const{return a<B.a;}};
inline void Set(int o,int L,int R,int x,int k){
if(L==R)t[o].minv=k,t[o].mink=x;
else{
int M=(L+R)>>1;
if(x<=M)Set(lc,L,M,x,k);else Set(rc,M+1,R,x,k);
if(t[lc].minv<t[rc].minv)t[o].minv=t[lc].minv,t[o].mink=t[lc].mink;else t[o].minv=t[rc].minv,t[o].mink=t[rc].mink;
}
}
inline Pair Min(int o,int L,int R,int l,int r){
if(l<=L&&R<=r)return Pair{t[o].minv,t[o].mink};
else{
int M=(L+R)>>1;Pair ans={INF,INF};
if(l<=M)ans=min(ans,Min(lc,L,M,l,r));if(r>M)ans=min(ans,Min(rc,M+1,R,l,r));//错误笔记:把l<=M打成了x<=M
return ans;
}
} int main(){
scanf("%d",&n);for(register int i=1;i<=n;++i)scanf("%d",&a[i]),pre[i]=lst[a[i]],lst[a[i]]=i;
scanf("%d",&m);for(register int i=1;i<=m;++i)scanf("%d%d",&x,&y),Add(y,x,i);
for(register int i=1;i<=n;++i){
if(pre[i])Set(1,1,n,pre[i],n+1);Set(1,1,n,i,pre[i]);
for(register int j=head[i];j;j=l[j].Next){
int ql=l[j].v,qr=i;Pair k=Min(1,1,n,ql,qr);
if(k.a<ql)l[l[j].id].ans=a[k.b];else l[l[j].id].ans=0;
}
}
for(register int i=1;i<=m;++i)printf("%d\n",l[i].ans);
}

至于G题嘛,由于我比较傻,英语也不好,到现在连题目都没看懂,所以我就暂时留坑待补了,以后A掉了就来填坑。

洛谷的难度评级有毒啊,这次比赛哪有那么难,A能是普及/提高-?BC竟然是提高+/省选-?DE是省选/NOI-?F竟然能评到NOI难度?有毒吧!

这次CF给我的教训就是:一定要看清题目再思考!遇到不好做的题目时不能慌!冷静思考!学会用之前写过的类似题目的方法来辅助思考,尝试把多个题目的过个方法结合起来!嗯,还有,要努力提升英语水平!

记人生中第一场认真打的CF——CF1000(虽然是Virtual participation)的更多相关文章

  1. 职业生涯手记——记人生中第一次经历的产品上线——内测篇Day11

    2017/08/21 产品内测期Day11 说出来可能你不信,原定于9月15号结束的内测活动,今天居然被甲方投诉导致强制停止,原因是这个内测活动没有经过批准,并且有用户打了甲方所在公司的客服部门,增加 ...

  2. 我人生中的第一场Java面试

    1.说起我的第一次Java面试,我不禁回想起我大学时参加校园招聘的那段日子,那时候我还是本科生,由于不是科班出身,只学过一点点Java皮毛,所以那时候对于找Java工作并没有什么概念,只是以为上过Ja ...

  3. NOI.AC NOIP模拟赛 第一场 补记

    NOI.AC NOIP模拟赛 第一场 补记 candy 题目大意: 有两个超市,每个超市有\(n(n\le10^5)\)个糖,每个糖\(W\)元.每颗糖有一个愉悦度,其中,第一家商店中的第\(i\)颗 ...

  4. 2019年湖南多校第一场||2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018)

    第一场多校就打的这么惨,只能说自己太菜了,还需继续努力啊- 题目链接: GYM链接:https://codeforces.com/gym/101933 CSU链接:http://acm.csu.edu ...

  5. LeetCode 笔记系列16.3 Minimum Window Substring [从O(N*M), O(NlogM)到O(N),人生就是一场不停的战斗]

    题目:Given a string S and a string T, find the minimum window in S which will contain all the characte ...

  6. 记ByteCTF中的Node题

    记ByteCTF中的Node题 我总觉得字节是跟Node过不去了,初赛和决赛都整了个Node题目,当然PHP.Java都是必不可少的,只是我觉得Node类型的比较少见,所以感觉挺新鲜的. Nothin ...

  7. 校省选赛第一场C题解Practice

    比赛时间只有两个小时,我没有选做这题,因为当时看样例也看不懂,比较烦恼. 后来发现,该题对输入输出要求很低.远远没有昨天我在做的A题的麻烦,赛后认真看了一下就明白了,写了一下,一次就AC了,没问题,真 ...

  8. 计蒜之道 初赛第一场B 阿里天池的新任务(简单)

    阿里“天池”竞赛平台近日推出了一个新的挑战任务:对于给定的一串 DNA 碱基序列 tt,判断它在另一个根据规则生成的 DNA 碱基序列 ss 中出现了多少次. 首先,定义一个序列 ww: \displ ...

  9. Contest1585 - 2018-2019赛季多校联合新生训练赛第一场(部分题解)

    Contest1585 - 2018-2019赛季多校联合新生训练赛第一场 C 10187 查找特定的合数 D 10188 传话游戏 H 10192 扫雷游戏 C 传送门 题干: 题目描述 自然数中除 ...

随机推荐

  1. SQL Server系列之 删除大量数据

    一.写在前面 - 想说爱你不容易 为了升级数据库至SQL Server 2008 R2,拿了一台现有的PC做测试,数据库从正式库Restore(3个数据库大小夸张地达到100G+),而机器内存只有可怜 ...

  2. C#配置IIS站点

    一.源码特点       1.  一些基于ASP.NET应用产品,在用户环境中都无可避免的涉及到部署到目标环境的应用服务器上,而配置站点是此过程的核心步骤,此源码对过程进行了高度封装,从创建IIS所需 ...

  3. C++编译-链接错误集合

    1,无法解析的外部符号,链接错误,原因:没找到某个符号(变量或函数)的定义体,一般是对应函数没实现,或第三方库没有添加到工程设置中 2,重复链接链接错误,一个定义体(实现体)被多个CPPP文件包含,导 ...

  4. React Native商城项目实战09 - 个人中心自定义cell

    1.新建组件CommonMyCell.js /** * 个人中心自定义cell */ import React, { Component } from 'react'; import { AppReg ...

  5. something about motorcycle and automobile

    cycle: 循环, 周期, 自行车. 摩托车: motorcycle, motor cycle 轮胎 continent(al): 大陆的, (七)大洲的; 德国的大陆轮胎, 马牌轮胎; 如吉普的c ...

  6. mongotemplate 简单使用

    怎么说呢,工作需要,不可能给你慢慢学的时间,一切以先解决当前jira为前提, mondb 安装不说了网上一搜就有,推荐图形管理界面 robo3t 比较直观 1.多条件查询这个比较简单 有两种方法 1C ...

  7. python-异常处理总结

    一.异常处理 在程序运行的过程中,总会遇到各种各样的错误.程序一出错就停止运行了,下面的代码就不能运行了:这时候就需要捕捉异常,通过捕捉异常,再去做对应的处理. e.g: info = { " ...

  8. value_counts()函数

    value_counts函数用于统计dataframe或series中不同数或字符串出现的次数 ascending=True时,按升序排列. normalize=True时,可计算出不同字符出现的频率 ...

  9. postgresql集群的搭建

    目录 架构图 部署详情 postgresql的安装与配置 pgpool的安装与配置 写在安装前 postgresql是一款很强大的数据库,具体有多强大呢,请谷歌... 网上的相关资料不是很多,参考了大 ...

  10. 【ABAP系列】SAP VA02修改销售订单的BAPI举例

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP VA02修改销售订单的B ...