码迷,mamicode.com
首页 > 其他好文 > 详细

Code+7 总结&题解

时间:2020-05-29 22:59:34      阅读:92      评论:0      收藏:0      [点我收藏+]

标签:遇见   第一个   ret   code   sqrt   衣服   过程   out   中国   

好不容易又遇见了一次Code+,好容易又没有拿到衣服。
实在是太菜了啊……


彩蛋题

整个机房集思广益,一起AK……
具体是什么就不赘述了,如果想知道的话去看gmh77的博客。


T1

很显然直接dfs,优先往字典序小的点走。
我做的是hard难度,所以当时没有做这题。于是没有代码。


T2

想到了一个相对于题解来说比较复杂的做法,在这里就不细说了。
讲题解做法。先放两个结论:

  1. 如果不看编号,那两只蚂蚁相撞之后反弹和相撞后穿过是没有分别的。太显然不证明。
  2. 如果看编号,那么一定存在一个分界点,在西边的蚂蚁从西边离开,在东边的蚂蚁从东边离开。证明可以考虑在进行的过程中,每次能够先离开的蚂蚁肯定是边上的蚂蚁。

根据结论1,可以知道最终多少个点往西边,多少个点往东边;再根据结论2,于是就可以求出分界点在哪里。
根据结论1,可以知道最终从西边离开的点离开的时间分别是什么时候;再根据结论2,可以给分界点西边的蚂蚁按照相对顺序分配相应的时间。
于是这题就做完了。

同样这题没有代码。


T3

比赛时手玩出了一个倒推的方法:
可以视作从全\(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;
}

T4

比赛绝大部分时间在刚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;
}

T5

比赛时脑抽连桶都没有想到过。
正解是打表。
考虑\(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;
}

T6

作为蒟蒻,题看都不想看。
正解不会。

Code+7 总结&题解

标签:遇见   第一个   ret   code   sqrt   衣服   过程   out   中国   

原文地址:https://www.cnblogs.com/jz-597/p/12989863.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!