还没改完题,先留个坑。

放一下AC了的代码,其他东西之后说…


改完了 快下课了先扔代码

跑了跑了 思路慢慢写


来补完了[x

刚刚才发现自己打错了标题

这次考试挺爆炸的XD除了T3老老实实打暴力拿了52分,T1T2都爆了个位数

因为我T1T2,这次没有打暴力…

T1想到了80分的思路,快乐打炸。T2想到了循环节一类的处理,然后也爆炸。之后发现其实有人和我的写法是一样的,但是他们最低拿了40分。

我还不如老老实实打暴力呢我这个只会暴力骗分与正解以及高分解法无缘的选手@#……¥#%&@@…

不知道自己的思维能力提升了没有,因为明显很多人都想到了。但是我的代码实现能力明显极差,这个…该怎么办呢,多刷题?

考完以后发现看不明白题解,好在隔空暴踩我们的大佬依然非常可靠,下午的时候博客就出来了。我跳过T2直接先去看T3,看不明白思路,大约理解代码理解了很久…之后自己敲代码,一些感觉有疑问的地方直接回去看了他的代码。这大约就是我代码能力差的原因?

抄题解看代码是不对的【大声】

于是T2…还是先理解了他的代码才自己写【因为实在不明白一些细节其实本来自己想是不是更珍贵一点】,卡在了只有我才会犯的问题上,发现了自己对于线段树理解的盲点。

怎么说呢——时间不多了,但我的问题依旧紧迫啊——

不是很安心。

T1矩阵游戏:

考试的时候想了个做法,把行列分开考虑,先处理行的影响,再处理列的影响。因为每一行每一列的和可以O(1)计算。然后对于行列的交点,再计算不足或多出的贡献。复杂度和操作次数k有关,最坏应该是500*500。

然后炸了…

正解是推出一个式子。根据题意,我们可以发现答案公式是:

∑ ri ∑ sj * [ (i-1) * m + j ]

(i-1)*m+j是原矩阵的每一项。

把后面拆开可以得到

∑ ri ∑ sj*(i-1)*m + sj*j

发现对于每一行i,我们要统计的j有同样的东西。即对于每一行,sj*j的和都是一样的。

我到这里还是有点懵,然后我继续拆

∑ ri * ∑ sj*j  +∑ ri *∑ sj*(i-1)*m

于是后面又可以把(i-1)*m提出来

∑ ri  ∑ sj*j  +∑ ri*(i-1)*m ∑ sj

于是发现对于每一行,sj的和也是同样的。

那么我们可以O(n)计算出每一列的sj*j以及统计sum1=∑sj*j,sum2=∑sj

然后O(n)计算每一行的贡献,ans=∑ri*sum1+ri*(i-1)*m*sum2

于是时间复杂度为O(n+n)=O(n),完美通过

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,k,x,y;
const int mod=;
long long h[],l[],tag1[],tag2[],sum1,sum2,sum;
char c[];
int main()
{
scanf("%d%d%d",&n,&m,&k);
while(k--){
scanf("%s%d%d",c,&x,&y);
if(c[]=='R'){
if(!tag1[x]){
tag1[x]=;
h[x]=;
}
h[x]=h[x]*y%mod;
}
else{
if(!tag2[x]){
tag2[x]=;
l[x]=;
}
l[x]=l[x]*y%mod;
}
}
for(int i=;i<=m;i++)sum1=(sum1+i*(tag2[i]==?:l[i])%mod)%mod,sum2=(sum2+(tag2[i]==?:l[i]))%mod;//sj*j
for(int i=;i<=n;i++){
sum=(sum+(tag1[i]==?:h[i])*sum1%mod+(tag1[i]==?:h[i])*m%mod*(i-)%mod*sum2%mod)%mod;
}
printf("%lld",sum%mod);
return ;
}

实际上考场上十几个人A了这题,想出正解的没有三十也有二十个人了。于是就让对于推理公式以及系统思维一窍不通的我显得非常智障。

还是没有清晰系统的思路,只会一通瞎搞。知识点也乱糟糟地混杂在一起。做题的时候思维发散而没有条理性,导致想到的东西都纠缠在一起,可能各种方法细节互相影响干扰又理不清,再往一个确定的方向推进就很难。

思维问题也很严重啊——

T2跳房子:

考试的时候看了一眼题面,第一反应是肯定有一个循环节。

然后就考虑能不能搞出这个循环节来处理较大的k。怎么办呢,对于每一个位置暴力求它进入循环的位置,走了几步,以及它能走到的循环的周期?

感觉是不是也可以类似记忆化搜索,存一部分内容来优化……尝试了一下,发现暴力求循环节都不会写,搞个ball的优化。

于是非常没有底气地开始敲代码,给出的样例都过了。手造小样例也过了。

由于我基本功极差不会对拍,我永远和大样例无缘…

考完以后果然发现自己的T2炸成了个位数XD

官方正解我没有仔细研究,和我们的做法好像有共通。我跟着大佬们写了类似于置换的东西。

考虑每一列每个位置到下一列的对应位置,这个过程很像置换,不过置换是一一对应的,而这里可以多对一转移。但是整个过程本身,仍然满足结合律。知道了这一列要到达的下一列的位置的下一个位置,就等于知道了这一列走两步的位置。

那么我们求出每一列到下一列的对应转移,然后合并起来。对于每一个询问操作,可以用快速幂处理k中走m步的整块,再处理零散的步数。

合并类置换,以及走零散的步数,这些过程很容易让人想到线段树。用线段树的子节点代表每一列到下一列的置换,向根递归的时候合并一小块一小块的置换,走不满m的步数的时候也可以在线段树上查询走尽量大的小块。

查询步数大于m的时候,先让当前位置走到第一列以方便使用快速幂调用最大的包含整个矩阵的置换。如果剩下的步数仍然大于m,可以用线段树节点b[1],即整个矩阵从第一列走一圈再走回第一列的置换,用类似快速幂的方式算出当前位置。剩下还有不足m的零散步数,直接在线段树上查询。然后对于当前所在列,如果大于m要及时减去。

置换里对应的其实是从当前位置会走到下一列的哪一行,而在代码里我是用y来表示的,回答的时候要反着输出x和y…

对于修改,会发现修改一个位置只可能会影响前一列左上,左,左下三个位置的转移。于是对于每一个修改操作,直接修改矩阵里存的值,然后从线段树的根一路走到前一列对应的叶子节点,类似于建树一样修改。这种修改只会影响子节点到根这一路上的父亲节点,于是也像建树时一样pushup合并一下。

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,q,k,sx=,sy=;
char c[];
int a[][];
struct node{
int l,r,nxt[];
}b[];
void pushup(int p){
node e1,e2;
for(int i=;i<=n;i++){
e1.nxt[i]=b[p*].nxt[i];
e2.nxt[i]=b[p*+].nxt[i];
}
for(int i=;i<=n;i++){
b[p].nxt[i]=e2.nxt[e1.nxt[i]];
}
}
void build(int p,int l,int r){
b[p].l=l,b[p].r=r;
if(l==r){
for(int i=;i<=n;i++){
int y=(l==m?:l+);
int x1=(i==?n:i-);
int x2=i;
int x3=(i==n?:i+);
int val=;
if(a[x1][y]>val){
val=a[x1][y];
b[p].nxt[i]=x1;
}
if(a[x2][y]>val){
val=a[x2][y];
b[p].nxt[i]=x2;
}
if(a[x3][y]>val){
val=a[x3][y];
b[p].nxt[i]=x3;
}
}
return;
}
int mid=(l+r)/;
build(p*,l,mid);
build(p*+,mid+,r);
pushup(p);
}
node query(int p,int l,int r){
if(l<=b[p].l&&b[p].r<=r){
return b[p];
}
int mid=(b[p].l+b[p].r)/;
if(r<=mid)return query(p*,l,r);
if(l>mid)return query(p*+,l,r);
node e1=query(p*,l,r);
node e2=query(p*+,l,r);
node e;
for(int i=;i<=n;i++){
e.nxt[i]=e2.nxt[e1.nxt[i]];
}
return e;
}
node work(node x,node y){
node e;
node e1,e2;
for(int i=;i<=n;i++){
e1.nxt[i]=x.nxt[i];
e2.nxt[i]=y.nxt[i];
}
for(int i=;i<=n;i++){
e.nxt[i]=e2.nxt[e1.nxt[i]];
}
return e;
}
node ks(node a,int k){
node num;
for(int i=;i<=n;i++)num.nxt[i]=i;
while(k){
if(k&)num=work(num,a);
a=work(a,a);
k>>=;
}
return num;
}
void change(int p,int l,int r){
if(l<=b[p].l&&b[p].r<=r){
for(int i=;i<=n;i++){
int y=(l==m?:l+);
int x1=(i==?n:i-);
int x2=i;
int x3=(i==n?:i+);
int val=;
if(a[x1][y]>val){
val=a[x1][y];
b[p].nxt[i]=x1;
}
if(a[x2][y]>val){
val=a[x2][y];
b[p].nxt[i]=x2;
}
if(a[x3][y]>val){
val=a[x3][y];
b[p].nxt[i]=x3;
}
}
return;
}
int mid=(b[p].l+b[p].r)/;
if(l<=mid)change(p*,l,r);
if(r>mid)change(p*+,l,r);
pushup(p);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++){
for(int j=;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
build(,,m);
scanf("%d",&q);
while(q--){
scanf("%s",c);
if(c[]=='m'){
scanf("%d",&k);
if(!k){
printf("%d %d",sy,sx);
continue;
}
if(sx+k-<=m){
sy=query(,sx,sx+k-).nxt[sy];
sx=sx+k;
if(sx>m)sx-=m;
}
else{
k=k-(m-sx+);
sy=query(,sx,m).nxt[sy];
sx=;
if(k/m!=){
sy=ks(b[],k/m).nxt[sy];
}
if(k%m!=){
k=k%m;
sy=query(,sx,sx+k-).nxt[sy];
sx=sx+k;
if(sx>m)sx-=m;
}
}
printf("%d %d\n",sy,sx);
}
else{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[x][y]=z;
change(,(y==?m:y-),(y==?m:y-));
}
}
return ;
}

我当时好像根本没有学好群论和置换,然后吃报应的时候来了

怎么说呢,要补的课又是一 大 堆。感谢最近的多次考试,让我不停地发现自己的知识盲区以及自己有多么垃圾orz

T3优美序列:

啊是一道要写一大堆的题…

考试的时候第一次瞅这题题面,一看数据范围,先送给我们50分。

当时起了放弃正解的念头【干什么】

最后发现我这个决定可能是正确的…第三题打炸就真的没分了。虽然我大概也想不到高分解法。

官方正解是分治,和奇袭那道题有点像。抱着锻炼自己代码能力的心思我鼓足一股气试着自己码这个分治,存递归到两边的询问就很智障地用结构体挣扎…也不知道这样对不对。结果越往后面写越不知道对于每一个中间的询问怎么确定它的答案,计算出中间的优美区间怎么更新询问答案,扫一遍然后复杂度爆炸?

emmmmm,一筹莫展。

于是打开了某学车大佬的博客,发现几分钟前他更新了一篇题解,是一道线段树优化建图的题。线段树优化建图?再看题目和本题题面没有半点联系,于是顺手打开洛谷试图求一波大佬的考试题解。

刚求完回去一刷新发现大佬已经更新了…打开往下翻,第三题请跳转另一道题题解。

嗯?这不就是刚刚那道?

线段树优化建图?????

这题解法不唯一。分治是一种,洛谷还有两位大佬用了扫描,然后统计无序二元组等信息或者别的什么的做法。

但我看到的这篇题解的做法是来自于另一个神仙的。主要思路是利用限制关系来点到区间连边建图,然后缩点。

对于原序列里相邻的两个数,如果它们存在于同一个优美序列里,可以得知它们数值之间的所有数都必须要出现。例如对于样例3 1 7 5 6 4 2,如果3 1在同一个优美序列里,那么2也要出现。如果1 7在同一个优美序列里,那么2 3 4 5 6都要出现。

如此一来限制关系就找到了。设每个点对应相邻的两个值,即设点i对应a[i-1]和a[i],那么点i如果出现在优美序列里,相应的一段区间也必须出现。例如,点2,即3 1出现,那么[1,7]这段区间就必须出现。

利用这个限制关系,可以点向区间连边。而这些限制关系可能是成环的,这个位置出现,那么另一个位置也要出现,而另一个位置可能又对应其他位置…一直到最后回到最开始的点,发现这些位置都需要同时出现。那么这些信息就可以利用缩点维护起来,维护对于一个成环的关系,最小会包含哪个区间,即要求哪个区间出现。

我们先对于每一个位置,即原序列里的下标,在线段树上找出对应的子节点。线段树优化建图,线段树里最下面的那一层叶子节点就是原序列,它们之后需要向上连更大的区间。

但是我们建图的限制关系和位置对应的数值有关。对于代表的值为1 3的这个节点来说,它要连向1-3这个连续数值区间对应的最小位置范围,那么我们就需要办法确定1-3这个连续数值区间所需的位置范围。

那么我们需要另一棵树,子节点对应的是数值而不是原序列的位置。把原序列每个数以及所在的位置下标传进树里,存下这个值对应的位置。然后线段树往上合并,就知道连续一段数值区间所需范围的左右端点。

然后设每个点对应相邻的两个数值,这个点的数值上下界就是这两个值,在刚刚的那棵树上找到它数值上下界所对应的位置区间。然后再在最开始下标为位置的那棵树上找到这个位置区间能包含的节点,使当前这个点向所有能包含的节点连边。

这就是建图过程。接下来缩点。

普通地跑一个tarjan缩点,对于每一个scc记录下它能到达的scc,并先用这个scc中的点更新自身能包含的位置范围。然后dfs从一个scc跑到能到达的其它scc,更新每个scc能到达的位置范围。

最后我们还需要一棵线段树,下标仍然是位置,存的是和数值那棵线段树一样的,每个节点对应的位置范围。这一次对于每个位置,存它所在的scc的位置范围。

最后对于每一个询问l,r,在这棵线段树上查询所需范围就可以了。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
int n,a[],m,pos[],N,dfn[],low[];
int stack[],st,tim,cnt,c[],vis[],vis1[];
vector<int>s1[],s2[];
struct node{
int l,r;
}t1[],t2[];
node work(node x,node y){
node e;
if(x.l>&&y.l>)e.l=min(x.l,y.l);
else if(x.l>)e.l=x.l;
else e.l=y.l;
e.r=max(x.r,y.r);
return e;
}
struct tree{
node t[];
void change(int p,int x,node y,int l,int r){
if(l==r){
t[p]=y;
return;
}
int mid=(l+r)/;
if(x<=mid){
change(p*,x,y,l,mid);
}
else{
change(p*+,x,y,mid+,r);
}
t[p]=work(t[p*],t[p*+]);
}
node query(int p,int l,int r,int L,int R){
if(l<=L&&R<=r){
return t[p];
}
int mid=(L+R)/;
if(l<=mid&&r>mid){
node e;
e=work(query(p*+,l,r,mid+,R),query(p*,l,r,L,mid));
return e;
}
else if(l<=mid)return query(p*,l,r,L,mid);
else if(r>mid)return query(p*+,l,r,mid+,R);
}
}seg[];
void build(int p,int l,int r){
if(l==r){
pos[l]=p;
return;
}
int mid=(l+r)/;
build(p*,l,mid);
build(p*+,mid+,r);
s1[p].push_back(p*);
s1[p].push_back(p*+);
}
void addedge(int p,int l,int r,int L,int R,int nod){
if(l<=L&&R<=r){
s1[nod].push_back(p);
return;
}
int mid=(L+R)/;
if(l<=mid)addedge(p*,l,r,L,mid,nod);
if(r>mid)addedge(p*+,l,r,mid+,R,nod);
}
void tarjan(int x){
dfn[x]=low[x]=++tim;
stack[++st]=x;
vis1[x]=;
for(int i=;i<s1[x].size();i++){
int y=s1[x][i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(vis1[y])low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
int p;
cnt++;
do{
p=stack[st--];
vis1[p]=;
c[p]=cnt;
}while(p!=x);
}
}
void dfs(int x){
if(vis[x])return;
vis[x]=;
for(int i=;i<s2[x].size();i++){
int y=s2[x][i];
dfs(y);
t2[x]=work(t2[x],t2[y]);
}
}
int main()
{
scanf("%d",&n);
N=*n+;
for(int i=;i<=n;i++)scanf("%d",&a[i]);
build(,,n);
for(int i=;i<=n;i++){
node y;
y.l=i,y.r=i;
seg[].change(,a[i],y,,n);
}
for(int i=;i<=n;i++){
int x=min(a[i-],a[i]),y=max(a[i-],a[i]);
t1[pos[i]]=seg[].query(,x,y,,n);
addedge(,t1[pos[i]].l+,t1[pos[i]].r,,n,pos[i]);
}
for(int i=;i<N;i++){
if(!dfn[i])tarjan(i);
}
for(int i=;i<N;i++){
for(int j=;j<s1[i].size();j++){
int y=s1[i][j];
if(c[i]!=c[y]){
s2[c[i]].push_back(c[y]);
}
}
}
for(int i=;i<N;i++){
t2[c[i]]=work(t2[c[i]],t1[i]);
}
for(int i=;i<=cnt;i++)dfs(i);
for(int i=;i<=n;i++){
seg[].change(,i,t2[c[pos[i]]],,n);
}
scanf("%d",&m);
while(m--){
int x,y;
scanf("%d%d",&x,&y);
if(x==y){
printf("%d %d\n",x,y);
continue;
}
node ans=seg[].query(,x+,y,,n);
printf("%d %d\n",ans.l,ans.r);
}
return ;
}

需要注意的有几个地方XD

首先大约是只有我自己才会犯的…有向图scc缩点是需要vis标记的,保证用栈里的点更新low值,不然可能会被横叉边更新QAQ

然后是这里由于每个点代表的是两个相邻的值,所以点向区间连边以及后面询问的时候左端点都要+1

最后别忘记优化建图的那棵线段树要从父亲到儿子连边

大约就是这样了XD

思维难度+,代码难度+++++?需要时常回来看一看这道题qwq

总之最近暴露出的问题非常多——还是继续努力吧,时间真的不多了,各种意义上

写这篇花了半个上午啦,滚去做题了,祝自己和诸位rp++

2019.8.5 NOIP模拟测试13 反思总结【已更新完毕】的更多相关文章

  1. 2019.8.1 NOIP模拟测试11 反思总结

    延迟了一天来补一个反思总结 急匆匆赶回来考试,我们这边大家的状态都稍微有一点差,不过最后的成绩总体来看好像还不错XD 其实这次拿分的大都是暴力[?],除了某些专注于某道题的人以及远程爆踩我们的某学车神 ...

  2. 2019.8.14 NOIP模拟测试21 反思总结

    模拟测试20的还没改完先咕着 各种细节问题=错失190pts T1大约三分钟搞出了式子,迅速码完,T2写了一半的时候怕最后被卡评测滚去交了,然后右端点没有初始化为n…但是这样还有80pts,而我后来还 ...

  3. 2019.8.9 NOIP模拟测试15 反思总结

    日常爆炸,考得一次比一次差XD 可能还是被身体拖慢了学习的进度吧,虽然按理来说没有影响.大家听的我也听过,大家学的我也没有缺勤多少次. 那么果然还是能力问题吗……? 虽然不愿意承认,但显然就是这样.对 ...

  4. 2019.7.29 NOIP模拟测试10 反思总结【T2补全】

    这次意外考得不错…但是并没有太多厉害的地方,因为我只是打满了暴力[还没去推T3] 第一题折腾了一个小时,看了看时间先去写第二题了.第二题尝试了半天还是只写了三十分的暴力,然后看到第三题是期望,本能排斥 ...

  5. 2019.8.3 NOIP模拟测试12 反思总结【P3938 斐波那契,P3939 数颜色,P3940 分组】

    [题解在下面] 早上5:50,Gekoo同学来到机房并表态:“打暴力,打暴力就对了,打出来我就赢了.” 我:深以为然. (这是个伏笔) 据说hzoi的人还差两次考试[现在是一次了]就要重新分配机房,不 ...

  6. 2019.8.12 NOIP模拟测试18 反思总结

    写个博客总是符合要求的对吧 回来以后第一次悄悄参加考试,昨天全程围观… 然后喜提爆炸120分wwwwwwwww T1用了全机房最慢的写法,导致改掉死循环T掉的一个点以后还是死活过不了最后一个点.T2全 ...

  7. 2019.8.10 NOIP模拟测试16 反思总结【基本更新完毕忽视咕咕咕】

    一如既往先放代码,我还没开始改… 改完T1滚过来了,先把T1T2的题解写了[颓博客啊] 今天下午就要走了,没想到还有送行的饯别礼,真是欣喜万分[并没有] 早上刚码完前面的总结,带着不怎么有希望的心情开 ...

  8. 2019.7.27 NOIP模拟测试9 反思总结

    先来整理题目 T1题目大意:给出n个数字和一个质数作为模数,一个变量x初始值为1.进行m次操作,每次让x随机乘上n个数中的一个,问m次操作以后x的期望值. 答案一定可以用分数表示,输出分子乘分母逆元的 ...

  9. 2019.8.7 NOIP模拟测试14 反思总结

    先扔代码 调完自闭网络流了,新一轮考试前看看能不能赶完…… 考得极其爆炸,心态也极其爆炸,真的是认识到自己能力上的不足 思维跑偏,代码能力差 就这样吧,再努力努力,不行就AFO T1旋转子段: 因为我 ...

随机推荐

  1. Python-进程(2)

    目录 进程互斥锁 队列 堆栈 IPC(进程间通信) 生产者与消费者模型 进程互斥锁 通过之前的学习,我们千方百计的实现了程序的异步,让多个任务可以同时在几个进程中并发处理 他们之间的运行没有顺序,一旦 ...

  2. Django 补充知识

    目录 Django基于配置文件的编程思想 初步实现 大佬实现 跨站请求伪造csrf 什么是csrf? 前端如何解决 ajax解决 csrf相关的装饰器 FBV方式装饰器 CVB方式装饰器 Django ...

  3. VS code 设置侧边栏字体大小

    1.代码改写,进入默认安装的如下路径 C:\Users\Administrator\AppData\Local\Programs\Microsoft VS Code\resources\app\out ...

  4. java定时器demo

    package cn.threadtest.thread; import java.util.Date; import java.util.Timer; import java.util.TimerT ...

  5. 【转载】unittest总结

    本文转载链接:http://www.cnblogs.com/yufeihlf/p/5707929.html unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执 ...

  6. Django之模板语言(三)------>自定义filter

    1.自定义filter: 1.在app01下面新建一个templatetags的python package包. 如果没有app01的话,可以通过命令行在manage中进行创建:python mana ...

  7. python格式化输出%,while else

    一.格式化输出% 需求:自我介绍模板 #格式化输出 # % s d %占位符,占个位置 s(字符串),d(数字)替换内容的类型 name = input('请输入姓名:') age = input(' ...

  8. 使用scrapy框架来进行抓取的原因

    在python爬虫中:使用requests + selenium就可以解决将近90%的爬虫需求,那么scrapy就是解决剩下10%的吗? 这个显然不是这样的,scrapy框架是为了让我们的爬虫更强大. ...

  9. Spring Boot配置公共的线程池

    内存资源很宝贵,线程池资源不宜过多的创建,同一个应用,尽量使用统一的线程池,并且相关参数需要设置适当,不造成资源的浪费,也不影响性能的提升. import java.util.concurrent.T ...

  10. osg::readPixels,glreadPixels截图,保存图片的alpha不对,总是255(1)

    这个函数最近折磨了我很久很久,因为需要用osg截图保存到本地,但是这个图片要具有alpha值,也就是背景的alpha值全为0,但是在公司上用_image->readPixels(448, 28, ...