标签:遇见 第一个 ret code sqrt 衣服 过程 out 中国
好不容易又遇见了一次Code+,好容易又没有拿到衣服。
实在是太菜了啊……
整个机房集思广益,一起AK……
具体是什么就不赘述了,如果想知道的话去看gmh77的博客。
很显然直接dfs,优先往字典序小的点走。
我做的是hard难度,所以当时没有做这题。于是没有代码。
想到了一个相对于题解来说比较复杂的做法,在这里就不细说了。
讲题解做法。先放两个结论:
根据结论1,可以知道最终多少个点往西边,多少个点往东边;再根据结论2,于是就可以求出分界点在哪里。
根据结论1,可以知道最终从西边离开的点离开的时间分别是什么时候;再根据结论2,可以给分界点西边的蚂蚁按照相对顺序分配相应的时间。
于是这题就做完了。
同样这题没有代码。
比赛时手玩出了一个倒推的方法:
可以视作从全\(0\)的序列开始,每次找到最前面的是\(0\)的位置\(i\),\(a_i\)变为\(i\),\(a_{1..i-1}\)减一。
记最后一个不为\(0\)的位置为\(n\),于是一直这样暴力做到操作次数为\(n+k\)为止。
这样的暴力有了\(35\)分。
比赛之后GMH、DYP等大佬表示他们几乎都切了这题。
考虑从后往前将整个序列构造出来。先二分\(n\)。
很显然,\(a_n=n\)
如果\(n>2\),那么就有\(a_{n-1}=n-2\)
能不能这样找到些什么规律呢?
考虑对于\(a_i\),它要被操作多少次。假设此时\(a_{i+1..n}\)都已经决定了,已经知道了它们要操作多少次,记它们一共要操作\(c\)次。
设\(a_i\)要操作\(x\)次,显然有\(a_i+c-ix=0\),所以\(a_i+c\equiv 0 \pmod i\)
可以看到,如果\(i|c\),那么\(a_i\)有两种取值;否则\(a_i\)只有一种取值。
对于后者,直接求出\(a_i\),然后算出\(x\),再算出\(c\),就可以继续做下去。
对于前者,处理就有点麻烦。不过出现这种情况的点不是很多,比较暴力的方法就是枚举钦定一下,聪明些的做法就是先确定它为\(0\),然后后面遇到的决策点都钦定为\(i\),然后比较总操作次数和\(k+n\)的大小关系,这样就可以\(O(n)\)地得出该选哪一个。
题解说有一档\(a_i\neq i\)的部分分给哪些想歪的选手,正好就是上面的这个做法。可是有些人就是这样过了。
再讲讲正解:
如果打了个表,可以发现\(a_1\)每隔一次就会被操作一次。
那么\(a_2\)呢……
考虑从前往后推,假如决定了\(a_{1..i-1}\)操作了多少次,剩下的操作次数为\(r\)。
设\(a_i\)会操作\(x\)次,可以列出个式子:\(0\leq a_i=ix-(r-x)\leq i\)
解出\(x\),可以发现夹在范围之间的整数只有一个,于是\(x\)的取值唯一。
接着继续往后推就可以了。
然而这题卡时间。
打表可以发现,\(\frac{n^2}{n+k}\)的值随着\(n+k\)的增大,越来越大接近于\(\pi\)
所以在\(k\)比较大的时候,就可以根据近似值缩小二分的范围。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
#define N 20000010
const double PI=acos(-1);
inline void output(int x){
static int st[20];
if (x==0)
putchar(‘0‘);
else{
int k=0;
for (;x;x/=10)
st[k++]=x%10;
while (k)
putchar(‘0‘+st[--k]);
}
}
ll k;
int a[N];
inline int judge(int n){
ll r=n+k;
for (int i=1;i<=n;++i){
ll x=(r+i)/(i+1);
a[i]=(i+1)*x-r;
r-=x;
}
return a[n]==0?1:(r>0?-1:0);
}
int main(){
scanf("%lld",&k);
double _n=(PI+sqrt((PI+4*k)*PI))/2;
// printf("%lf\n",_n);
int l,r;
if (k<=1e6)
l=1,r=max(100ll,k);
else
l=0.999*_n,r=1.001*_n;
while (l<=r){
int mid=l+r>>1;
int tmp=judge(mid);
if (tmp==1)
r=mid-1;
else if (tmp==-1)
l=mid+1;
else{
output(mid),putchar(‘\n‘);
for (int i=1;i<=mid;++i)
output(a[i]),putchar(‘ ‘);
break;
}
}
return 0;
}
比赛绝大部分时间在刚T3,T4一时脑抽没有想到比较好的做法。
正解离线和在线:
首先都要基于一个简单性质:每个除数的答案的总的改变次数不超过\(O(n\ln n)\)
离线做法,对于\(O(n\ln n)\)个区间,分别预处理出每个区间内第一次出现数字的时间。
然后将时间挂在操作上,操作前将挂在这个时间上的区间标记,顺便维护答案。
在线做法,对于每个除数,将它的第一个空区间挂到线段树上。
插入一个数时,找到它经过的所有空区间,将它们暴力往后移。移的过程中一个个\(O(\lg n)\)判断区间有没有数,直到区间中没有数为止。
离线做法
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
#define M 1000010
#define ll long long
#define INF 0x7f7f7f7f
int n,m;
struct Oper{
bool ty;
int x,y;
} o[M];
int mn[N*4];
void insert(int k,int l,int r,int x,int c){
mn[k]=min(mn[k],c);
if (l==r)
return;
int mid=l+r>>1;
if (x<=mid)
insert(k<<1,l,mid,x,c);
else
insert(k<<1|1,mid+1,r,x,c);
}
int query(int k,int l,int r,int st,int en){
if (st<=l && r<=en)
return mn[k];
int mid=l+r>>1,res=INF;
if (st<=mid)
res=min(res,query(k<<1,l,mid,st,en));
if (mid<en)
res=min(res,query(k<<1|1,mid+1,r,st,en));
return res;
}
int id[N];
struct Oper2{
int d,x,t;
} q[N*20];
int cnt;
bool cmpq(Oper2 a,Oper2 b){return a.t<b.t;}
bool f[N*20];
int ans[N];
ll t[N];
void add(int x,int c){
for (;x<=n;x+=x&-x)
t[x]+=c;
}
ll qsum(int x){
ll r=0;
for (;x;x-=x&-x)
r+=t[x];
return r;
}
int main(){
scanf("%d%d",&n,&m);
memset(mn,127,sizeof mn);
for (int i=1;i<=m;++i){
int ty;
scanf("%d",&ty);
if (ty==1){
scanf("%d",&o[i].x),o[i].ty=0;
insert(1,1,n,o[i].x,i);
}
else
scanf("%d%d",&o[i].x,&o[i].y),o[i].ty=1;
}
for (int i=1;i<=n;++i)
id[i]=(n-1)/i+1;
for (int i=1;i<=n;++i)
id[i]+=id[i-1];
for (int i=1;i<=n;++i)
for (int j=0;j*i+1<=n;++j)
q[++cnt]={i,j,query(1,1,n,j*i+1,min(j*i+i,n))};
sort(q+1,q+cnt+1,cmpq);
for (int i=1,j=1;i<=m;++i){
for (;j<=cnt && q[j].t<=i;++j){
int d=q[j].d;
f[id[d-1]+1+q[j].x]=1;
int tmp=ans[d];
while (id[d-1]+1+tmp<=id[d] && f[id[d-1]+1+tmp])
tmp++;
if (tmp!=ans[d]){
add(d,tmp-ans[d]);
ans[d]=tmp;
}
}
if (o[i].ty==1)
printf("%lld\n",qsum(o[i].y)-qsum(o[i].x-1)+o[i].y-o[i].x+1);
}
return 0;
}
比赛时脑抽连桶都没有想到过。
正解是打表。
考虑\(p\)为奇素数的情况,打表发现有如此结论:
\(p\mod 4=1,x=0\),解的个数为\(2p-1\)。
\(p\mod 4=1,x\neq 0\),解的个数为\(p-1\)。
\(p\mod 4=3,x=0\),解的个数为\(1\)。
\(p\mod 4=3,x\neq 0\),解的个数为\(p+1\)。
表示只会证明\(x=0\)的情况,其它的情况求路过的大佬慷慨相助。
根据中国剩余定理,\(p\)拆分成若干个奇素数,每个素数的答案之积就是最终的答案。
所以这题的算法就只有分解质因数了……
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 3200
#define ll long long
int pri[N+10],np;
bool inp[N+10];
int p,x;
int calc(int p,int x){
if (p%4==1)
return x%p==0?2*p-1:p-1;
return x%p==0?1:p+1;
}
int main(){
for (int i=2;i<=N;++i){
if (!inp[i])
pri[++np]=i;
for (int j=1;j<=np && i*pri[j]<=N;++j){
inp[i*pri[j]]=1;
if (i%pri[j]==0)
break;
}
}
int T;
scanf("%d",&T);
while (T--){
scanf("%d%d",&p,&x);
ll ans=1;
for (int i=1;i<=np && pri[i]*pri[i]<=p;++i)
if (p%pri[i]==0){
p/=pri[i];
ans*=calc(pri[i],x);
}
if (p!=1)
ans*=calc(p,x);
printf("%lld\n",ans);
}
return 0;
}
作为蒟蒻,题看都不想看。
正解不会。
标签:遇见 第一个 ret code sqrt 衣服 过程 out 中国
原文地址:https://www.cnblogs.com/jz-597/p/12989863.html