TJU[2022冬季训练]个人自测赛(二)(上)
题目A CodeForces 731A
因为太麻烦了就不放问题了,放一个链接
题解
纯纯水题,,要是说一个算法的话,就算一个贪心吧,具体操作就是比较每次到底是顺时针转需要的步骤少还是逆时针转需要的步骤少,然后就完事了
(考场也是首切)
代码
#include<bits/stdc++.h> #define re register #define ll long long #define inc(i,j,k) for(re int i=j;i<=k;i++) #define dec(i,j,k) for(re int i=j;i>=k;i--) using namespace std; inline int read() { re 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^48); ch=getchar();} return x*f; } char s[500010]; int len; ll dis (char x,char y) { int a=x-'a'; int b=y-'a'; if(a<b) swap(a,b); int aa=a-b; int aaa=26-aa; return (ll)min(aa,aaa); } int main() { scanf("%s",s); len=strlen(s); ll ans=0; ans+=dis(s[0],'a'); inc(i,1,len-1) { ans+=dis(s[i],s[i-1]); } cout<<ans<<endl; }
题目B CodeForces 731B
B题也是水题,,在赛场本人也是首切,,,就是模拟,,,这里需要注意的就是,如果是一天买两个对后续没什么影响,唯一需要注意的就是,连续两天买一个才是可能会出现NO的本质问题所在,或者说,连续两天买一个才是唯一一点点难点,,,还有一点点难点是判断一下结束的时候是不是还有“半张”优惠券没用完就好了,代码放在下面了,也很好理解。水题没什么好说的
虽然是首切,但是还是错了两次的,这里说一下错了两次的原因,主要是因为之前考虑的0和2的天数太复杂了,后来考虑到既然不影响标签就不写一大推if语句去判断,
代码
#include<bits/stdc++.h> #define re register #define ll long long #define inc(i,j,k) for(re int i=j;i<=k;i++) #define dec(i,j,k) for(re int i=j;i>=k;i--) using namespace std; inline int read() { re 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^48); ch=getchar();} return x*f; } int n; int a[200010]; int main() { n=read(); inc(i,1,n) { a[i]=read(); } bool tag=0; bool zzt=0; inc(i,1,n) { if(a[i]%2) { if(!a[i+1]) { zzt=1; break; } a[i+1]--; } } if(a[n]%2!=0) zzt=1; if(zzt) cout<<"NO"<<endl; else cout<<"YES"<<endl; }
题目C CodeForces 1000B
这道题还是有一定思维难度的,,,果然不是我拿到首切(
其实大概的思路很好想到,因为关灯这个操作,其实是反转了后续的所有操作的时间,那么其实就需要预处理出每两个操作之间的差,而且题目给严格缩减了难度,只要求插入一个断点去反转一次,那么很好想到,新插入的断电为了让开灯时间尽可能长,就应该插在某个点相邻的点,如下图
蓝色为原本开灯时间的答案,显然,插入粉色位置比插入绿色位置好,所以我们要尽可能的插入到某个点相邻的点中,之后又因为只能插入一个点,所以我们只需要知道在一个点后面插入一个点和不插入一个点的两种情况的答案就可以了,然后在所有的答案中取一个最大值,通过前缀和的预处理,我们可以在O(n)的时间内解决这个问题。
#include<bits/stdc++.h> #define re register #define ll long long #define inc(i,j,k) for(re int i=j;i<=k;i++) #define dec(i,j,k) for(re int i=j;i>=k;i--) using namespace std; inline int read() { re 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^48); ch=getchar();} return x*f; } inline ll rread() { re ll 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^48); ch=getchar();} return x*f; } int n; ll m; ll a[100010]; ll cha[100010]; ll t[100010]; ll ans; int main() { n=read(); m=rread(); // a[0]=1; a[n+1]=m; inc(i,1,n) { a[i]=rread(); cha[i]=a[i]-a[i-1]; } cha[n+1]=a[n+1]-a[n]; inc(i,1,n+1) { if(i&1) { t[i]=t[i-1]+cha[i]; } else { t[i]=t[i-1]; } } // inc(i,0,n+1) cout<<t[i]<<" "; cout<<endl; inc(i,1,n+1) { ans=max(ans,t[i]+(m-a[i])-(t[n+1]-t[i])-1); } ans=max(ans,t[n+1]); cout<<ans<<endl; }
最开始我把a0设置为1,怎么都调不对,,,,后来重新看了一下题,
淦,,,我到现在我都不知道是怎么看错题的,,,,,唉,,,
题目D POJ 1988
到这里终于有点算法题的影子了(bushi
首先提到的就是所谓的合并操作,将包含某个元素的所有全部都移动到某一个堆的上部,很容易让人联想到并查集,,但是显然这样是不够的,因为并查集的路径压缩,会破坏掉原有的非根部的父子关系,也就是我们只知道爸爸和儿子都是爷爷的后代,但我们在路径压缩的过程中破坏掉了父子的关系。
但是其实既然我们只想知道某一个块下面有多少个块,其实可以分成两个部分,一是自己在自己曾经的那一坨方块中的高度,二是被自己压在身下的这个大坨大小,两个部分加在一起其实就是被这个方块压在下面的方块个数了(好怪。
那么就是一个简单的并查集的变式,,统计集合大小加上一个统计路径长度,两个并查集的简单延伸。
#include<cstring> #include<cstdio> #include<iostream> #include<ctime> #define re register #define ll long long #define inc(i,j,k) for(re int i=j;i<=k;i++) #define dec(i,j,k) for(re int i=j;i>=k;i--) using namespace std; inline int read() { re 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^48); ch=getchar();} return x*f; } int n; int t[300010],dis[300010],fa[300010],siz[300010]; inline int findfa(int x) { if(x==fa[x]) return fa[x]; int fax=fa[x]; fa[x]=findfa(fa[x]); dis[x]+=dis[fax]; return fa[x]; } inline void init() { inc(i,1,300000) { fa[i]=i; siz[i]=1; dis[i]=0; } } int main() { // srand(time(NULL)); n=read(); init(); char s; inc(zzt,1,n) { s=getchar(); while(s!='M'&&s!='C') s=getchar(); // cout<<s<<endl; if(s=='M') { int xx=read(); int yy=read(); int fax=findfa(xx); int fay=findfa(yy); // if(rand()&1) swap(fax,fay); fa[fay]=fax; dis[fay]=siz[fax]; siz[fax]+=siz[fay]; } else { int xx=read(); printf("%d\n",siz[findfa(xx)]-dis[xx]-1); } } }
这里再单独解释一下路径压缩的部分,其实这里运用了一个技巧,本身的dis数组记录的是自己距离自己父亲的距离,而从程序中我们可以看到,我们先记录下来了原本的父节点,为fax,之后将自己的父节点进行路径压缩,即fa[x]=findfa(fa[x]),在这个过程中,每一个自己的祖先节点都在做同样的操作,他们都在向上递归,等待这个fa[x]=findfa(fa[x])操作结束,等着操作结束后更新自己的dis数组,而这个操作最终会在根节点,也就是祖先节点,fa[x]=x时候结束,接下来,从祖先节点开始,执行更新dis的操作,然后祖先的操作进行完了,祖先的儿子进行所更新dis数组的操作,以对于每一个后代,他的父节点一定是在他之前更新好了dis数组的,于是每一个节点的dis数组最终都会记录好了,这个思想其实是基础的dfs思想,但是其实在我学并查集的时候,并没有理解到这个深度,是我在后来学树链剖分的时候,深入的学习了dfs之后才能清楚地理解的,(不过那些都是上高三之前了,,,,,,唉
这道题也是首切~
原本的题目E Luogu P1083 借教室
就在上一道题做完之后,,,
可以看到其实我是领先了很多的,当时想都没想直接开了E题,,没想到等我1h56min之后做完了E题之后才知道因为原本E题数据炸了所以,,,就白做了,,,,新的E题比这道题好写多了,,,,唉
不过言归正传,多做了一道题也是好的~
这道题一看到,我们可以把借教室看做一个区间减,然后去维护区间最小值,这样直到区间中最小值为负,就说明没法继续借教室了,那还想什么啊,赶紧线段树啊!!!!!!!!!!!!!!!
线段树就不多解释了,我这里加一个lazytag纯纯是给自己找事,,,完全没必要。。。
还是码力下降太严重了,,,这要是以前看到是线段树,,,,绝对就10分钟写完然后开下一题了,,,,他换题都来不及qwq
#include<bits/stdc++.h> #define re register #define ll long long #define ls p<<1 #define rs p<<1|1 #define inc(i,j,k) for(re int i=j;i<=k;i++) #define dec(i,j,k) for(re int i=j;i>=k;i--) using namespace std; inline int read() { re 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^48); ch=getchar();} return x*f; } inline ll rrread() { re ll 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^48); ch=getchar();} return x*f; } int n,m; ll rr[1000010]; struct tree { ll v,add; }st[4000010]; inline void pushup(int p,int l,int r) { st[p].v=min(st[ls].v,st[rs].v); } inline void build(int p,int l,int r) { st[p].add=0; if(l==r) { st[p].v=rr[l]; return; } else { re int m=(l+r)>>1; build(ls,l,m); build(rs,m+1,r); pushup(p,l,r); } } inline void pushdown(int p,int l,int r) { int m=(l+r)>>1; if(st[p].add) { st[ls].v+=st[p].add; st[ls].add+=st[p].add; st[rs].v+=st[p].add; st[rs].add+=st[p].add; st[p].add=0; } return; } inline void update(int p,int l,int r,int ql,int qr,ll val) { if(qr<l || ql>r) return; if(ql<=l && r<=qr) { st[p].v+=val; st[p].add+=val; return; } pushdown(p,l,r); int m=(l+r)>>1; update(ls,l,m,ql,qr,val); update(rs,m+1,r,ql,qr,val); pushup(p,l,r); } inline ll query(int p,int l,int r,int ql,int qr) { if(qr<l || ql>r) return 0; if(ql<=l && r<=qr) return st[p].v; pushdown(p,l,r); ll ans=1919810; int mid=(l+r)>>1; if(ql<=mid) ans=min(ans,query(ls,l,m,ql,qr)); if(qr>mid) ans=min(ans,query(rs,m+1,r,ql,qr)); return ans; } int main() { n=read(); m=read(); inc(i,1,n) rr[i]=rrread(); build(1,1,n); bool zzt=1; int num=-1; inc(i,1,m) { int aa,bb; ll cc; cc=rrread(); aa=read(); bb=read(); update(1,1,n,aa,bb,-cc); if(query(1,1,n,1,n)<0) { zzt=0; num=i; break; } } if(zzt) cout<<"0"<<endl; else { cout<<-1<<endl; cout<<num<<endl; } }
不要再说啦!我知道我的pushup写的变量多余了,,线段树不太熟练了,,,唉。
调了好久的原因,,,,因为我最开始带一个add标记,,就写着写着写成区间求和了,,,,因为add标记就是干这个的啊喂!真顺手了,,,,,唉。这不纯纯给自己找事呢吗。。。
新的题目E POJ1061
果然很水
纯纯一眼切,,,当时是看到E题换了之后心态炸了,就没做这道题,,,现在想起来是真的后悔,,,,,就是一个exgcd。。。。。。
赛后补题大改只用了十分钟就写完了。。。。
心态最关键!!!
不过既然是题解,那就认真一点写全吧,假设跳了q次相遇,那么两个青蛙的坐标就是 x+m*q , y+n*q 因为两个青蛙相遇,所以可以列出追及问题的等式
L | [(x+m*q)-(y+m*q)] . 对这个式子进行变形,就可以得到拓展欧几里得的形式 (n-m)*q - w*l = x-y 即 ax+by=c 。
但是!众所周知,拓展欧几里得算法求出来的是 ax+by=gcd(a,b)的解,所以问题就很清楚了,我们要判断gcd(a,b)是不是c的因子(包括1),然后等比例的把这个式子放大就是上述ax+by=c的解啦! 如果不能通过放大变成这个式子的话,那么就是无解的。 至于为什么无解,,,我也不清楚,我一个理学院的现在也只学过高等数学上册,,,(小猫咪能有什么坏心眼呢.jpg)
#include<iostream> #include<cstring> #include<cstdio> #define re register #define ll long long #define inc(i,j,k) for(re int i=j;i<=k;i++) #define dec(i,j,k) for(re int i=j;i>=k;i--) using namespace std; inline int read() { re 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^48); ch=getchar();} return x*f; } ll x,y,m,n,L; ll exgcd(ll a,ll b,ll &xx,ll&yy) { if(!b) { xx=1; yy=0; return a; } ll nx,ny; ll tmp=exgcd(b,a%b,nx,ny); xx=ny; yy=nx-(a/b)*ny; return tmp; } int main() { cin>>x>>y>>m>>n>>L; ll aa=n-m; ll bb=L; ll cc=x-y; ll ansx; ll ansy; ll gcd=exgcd(aa,bb,ansx,ansy); // cout<<gcd<<endl; if(cc%gcd) cout<<"Impossible"<<endl; else { ansx*=(cc/gcd); ansy*=(cc/gcd); // aa*ansx+bb*ansy=cc // x = ans + (bb/gcd)*t; // ll ans=ansx+(bb/gcd); /* while(ans>=0) { ans-=5*(bb/gcd); } while(ans<0) { ans+=(bb/gcd); } */ ll ans = -ansx*gcd/bb; ans=ansx+ans*bb/gcd; while(ans<0) ans+=bb/gcd; cout<<ans<<endl; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了