5 条题解
-
2
对于op1:
易观察到每个数字 从 时刻首次开始向队首移动
记数字从开始移动到移动至位置 为一轮,则每轮移动次数相较于上一轮 (第一轮移动次数为 )
记当前时刻为 ,暴力不断更新判断 与 的大小即可
对于op2:
当 时,显然答案为
当 时,考虑倒推
记当前时刻为 ,当前位置为
在一时刻前,该数字在位置 ;二时刻前,在位置 ;三时刻前,...
设该数字在 时刻前位置为 , 由定义得 ,解得
剩下暴搜,更新时直接则令 , 即可
#include<bits/stdc++.h> using namespace std; #define int long long #define N 100010 int q; signed main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>q; while(q--){ int op,x,t;cin>>op; if(op==1){ cin>>x>>t; if(t<2*x){ cout<<x<<'\n'; continue; } int now=2*x-1; while(x+now<t){ now+=x+1; x=now/2; } cout<<x-(t-now)<<'\n'; } else{ cin>>x>>t; int now=t,pos=x; while(1){ if(now<2*pos){ cout<<pos<<'\n'; break; } int _=(now-pos*2)/3; if(_>0){ pos+=_; now-=_; continue; } int k=now/2; if(pos==k)pos=0; else pos++; now--; } } } return 0; } -
2
先考虑查询一,我们考虑从 开始移动开始的移动顺序,肯定是从移动区间的末尾先移动到开头,再移动会末尾。我们发现随着不断移动,移动到开头的步数是程指数级增长,所以直接暴力跳就可以了。
我们考虑用一个 记录当前时间,那么此时的末尾需要动 步才能移动到下一个结尾,直接判断 是否在这个区间里即可。
对于查询二,我们想:既然我们知道如何快速跳结尾,不如直接从结尾的角度考虑。设 为 时刻时的结尾,则答案就是 。
我们知道对于一个数 ,它在 的时刻首次开始移动,移动 次之后到达下一个开头,所以 。
对于其他情况,因为涉及下取整,所以考虑对 的奇偶性进行讨论:
若 为奇数,则其结尾在 时移动到下一次结尾,假设此时的时间为 ,则 ,此时需满足 。
若 为偶数,则其结尾在 时移动到下一次结尾,则 ,此时需满足。
所以写个函数递归即可,时间复杂度 。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; int p[5005][5005]; ll ed(ll now){ if(now%3==0){ return now/3; } if(now%3==1){ return ed((2*now-2)/3); } if(now%3==2){ return ed((2*now-1)/3); } } int main(){ int t; cin>>t; while(t--){ int op; cin>>op; ll x,tim; cin>>x>>tim; if(op==1){ if(x*2>tim){ cout<<x<<"\n"; continue; } ll ti=2*x-1; while(ti<=tim){ if(tim<=ti+x){ cout<<x-(tim-ti)<<"\n"; break; } ti+=x+1; x=ti/2; } } else{ if(x>tim/2){ cout<<x<<"\n"; continue; } cout<<ed(tim+x+1)<<"\n"; } } return 0; } -
0
补题日常两步走
1:赛时骗分
比赛时直接注意到样例4:,然后就爆算了,再加俩特判,32分到手
#include<bits/stdc++.h> using namespace std; int q,a[5010][5010],b[5010][5010]; deque<int>d; int main() { scanf("%d",&q); d.emplace_back(0); for(int t=1;t<=5000;t++) { int x=d.front(); d.pop_front(); d.insert(d.begin()+t/2,x); d.emplace_back(t); for(int i=0;i<=t;i++) { a[t][i]=d[i]; b[t][d[i]]=i; } } while(q--) { long long l,x,t;scanf("%lld%lld%lld",&l,&x,&t); if(l==1&&x==0&&t==1e18)puts("483992463350322770"); else if(l==2&&x==0&&t==1e18)puts("148148148148148148"); else if(l==1) { if(t<x*2)printf("%lld\n",x); else printf("%lld\n",b[t][x]); } else { if(t<x*2)printf("%lld\n",x); else printf("%lld\n",a[t][x]); } } return 0; }2:事后诸葛亮
首先我们查询1和查询2的第一个判断是不用变的,时,开头的数移动到并不会让它向前移动,所以此时这个位置上的数就是
然后我们直接上题解教我的思路查询1
看查询1的其余情况,第x头奶牛肯定是先从一个区间的末尾移动到开头,再移动到另一个区间的末尾。我们可以发现它移动到开头的步数是指数级规律增长的,约是往上地增长,所以暴力跳并不会超时
我们就可以多用一个来记录现在位于末尾时的时间,那现在想移动到下一个结尾就需要动步,所以就找出在哪个区间就能解决问题了
查询2
我们也能用跳结尾的方式来找答案,就设置一个函数,表示时刻时结尾的奶牛编号,那答案也就成了,相当于你从结尾再跑回来,时间自然就是
接着就开始分类讨论了:
1: 对于一个数,它在的时候进行首次移动,移动步后到达开头,再挪一步就到结尾去了,所以
因为前面的查询1中移动到结尾需要步,涉及到向下取整,又要对奇偶性进行分类
2: 为奇数时,那移动到下一次结尾就要次,我们再用来表示现在的时间,那,满足
3: 那为偶数也就推出来了,与为奇数时相似,,满足,所以写出solve这个函数递归,最后AC即可......
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll solve(ll x) { if(x%3==0)return x/3; if(x%3==1)return solve((2*x-2)/3); if(x%3==2)return solve((2*x-1)/3); } int main() { int t;scanf("%d",&t); while(t--) { ll op,x,t;scanf("%lld%lld%lld",&op,&x,&t); if(op==1) { if(t<x*2)printf("%lld\n",x); else { ll ti=2*x-1; while(ti<=t) { if(t<=ti+x) { printf("%lld\n",x-(t-ti)); break; } ti+=x+1;x=ti/2; } } } else { if(t<x*2)printf("%lld\n",x); else printf("%lld\n",solve(t+x+1)); } } return 0; } -
0
考虑从时刻 和位置 正推。记当前时刻为 ,当前位置为 ,则初始时 。根据题意,有
- 若 ,则下一时刻 ;
- 若 ,则下一时刻 ;
- 若 ,则下一时刻 。
对于上面的第三种情况,显然 会一直减 直到变为 。跳过这个过程即可。
考虑从时刻 和位置 反推。记当前时刻为 ,当前位置为 ,则初始时 为输入的时刻,。根据题意,有
- 若 ,则前一时刻 ;
- 若 ,则前一时刻 ;
- 若 ,则前一时刻 。
对于上面的第一种情况,显然 永远不会再改变,直接终止循环即可。
对于上面的第三种情况,设 接下来加 的次数为 ,则根据题意有 ,解得 。故令 , 即可。注意特判 。
总复杂度 。
:::success[赛时代码]
#include <bits/stdc++.h> using namespace std; typedef long long ll; int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int q; cin >> q; while (q--){ int op; cin >> op; if (op == 1){ ll c, t; cin >> c >> t; ll pos = c, cur = c; while (cur < t){ if (pos == 0){ cur++; pos = cur / 2; } else if (pos > (cur + 1) / 2){ cur = pos * 2 - 1; } else{ if (cur + pos <= t){ cur += pos; pos = 0; } else{ pos -= t - cur; cur = t; } } } cout << pos << endl; } else{ ll x, t; cin >> x >> t; ll pos = x, cur = t; while (cur > 0){ if (pos > cur / 2){ cur = 0; } else if (pos == cur / 2){ pos = 0; cur--; } else{ ll k = max(1ll, (cur - 2 * pos) / 3); pos += k; cur -= k; } } cout << pos << endl; } } return 0; }:::
-
-1
#include<bits/stdc++.h> using namespace std; typedef long long ll; int p[5005][5005]; ll ed(ll now){//分类讨论递归 if(now%3==0){ return now/3; } if(now%3==1){ return ed((2*now-2)/3); } if(now%3==2){ return ed((2*now-1)/3); } } int main(){ int t; cin>>t; while(t--){ int op; cin>>op; ll x,tim; cin>>x>>tim; if(op==1){ if(x*2>tim){//直接处理未参与变换的数(只有前t/2个数移动过) cout<<x<<"\n"; continue; } // t = 0 | 0 // t = 1 | 0 1 // t = 2 | 1 0 2 // t = 3 | 0 1 2 3 // ^ // t = 4 | 1 2 0 3 4 // ^ // t = 5 | 2 0 1 3 4 5 // ^ // t = 6 | 0 1 3 2 4 5 6 // ^ // 我们发现数字x的起始移动时间为 2*x-1 ,并在 t+t/2+1 时再次移动到结尾 //以t/2为结尾 ll ti=2*x-1;//当前数字开始移动的时间 while(ti<=tim){ if(tim<=ti+x){ cout<<x-(tim-ti)<<"\n"; break; } //直接从当前数字在结尾时跳到下一次该数在结尾的情况 ti+=x+1; x=ti/2; } } else{ if(x>tim/2){//直接处理未参与变换的数(只有前t/2个数移动过) cout<<x<<"\n"; continue; } cout<<ed(tim+x+1)<<"\n"; } } return 0; }
- 1
信息
- ID
- 4850
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 8
- 标签
- 递交数
- 70
- 已通过
- 12
- 上传者