Code+7 总结&题解

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


彩蛋题

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


T1

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


T2

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

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

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

同样这题没有代码。


T3

比赛时手玩出了一个倒推的方法:
可以视作从全0 00的序列开始,每次找到最前面的是0 00的位置i iia i a_iai变为i iia 1.. i − 1 a_{1..i-1}a1..i1减一。
记最后一个不为0 00的位置为n nn,于是一直这样暴力做到操作次数为n + k n+kn+k为止。
这样的暴力有了35 3535分。

比赛之后GMH、DYP等大佬表示他们几乎都切了这题。
考虑从后往前将整个序列构造出来。先二分n nn
很显然,a n = n a_n=nan=n
如果n > 2 n>2n>2,那么就有a n − 1 = n − 2 a_{n-1}=n-2an1=n2
能不能这样找到些什么规律呢?
考虑对于a i a_iai,它要被操作多少次。假设此时a i + 1.. n a_{i+1..n}ai+1..n都已经决定了,已经知道了它们要操作多少次,记它们一共要操作c cc次。
a i a_iai要操作x xx次,显然有a i + c − i x = 0 a_i+c-ix=0ai+cix=0,所以a i + c ≡ 0 ( m o d i ) a_i+c\equiv 0 \pmod iai+c0(modi)
可以看到,如果i ∣ c i|cic,那么a i a_iai有两种取值;否则a i a_iai只有一种取值。
对于后者,直接求出a i a_iai,然后算出x xx,再算出c cc,就可以继续做下去。
对于前者,处理就有点麻烦。不过出现这种情况的点不是很多,比较暴力的方法就是枚举钦定一下,聪明些的做法就是先确定它为0 00,然后后面遇到的决策点都钦定为i ii,然后比较总操作次数和k + n k+nk+n的大小关系,这样就可以O ( n ) O(n)O(n)地得出该选哪一个。
题解说有一档a i ≠ i a_i\neq iai=i的部分分给哪些想歪的选手,正好就是上面的这个做法。可是有些人就是这样过了。

再讲讲正解:
如果打了个表,可以发现a 1 a_1a1每隔一次就会被操作一次。
那么a 2 a_2a2呢……
考虑从前往后推,假如决定了a 1.. i − 1 a_{1..i-1}a1..i1操作了多少次,剩下的操作次数为r rr
a i a_iai会操作x xx次,可以列出个式子:0 ≤ a i = i x − ( r − x ) ≤ i 0\leq a_i=ix-(r-x)\leq i0ai=ix(rx)i
解出x xx,可以发现夹在范围之间的整数只有一个,于是x xx的取值唯一。
接着继续往后推就可以了。
然而这题卡时间。
打表可以发现,n 2 n + k \frac{n^2}{n+k}n+kn2的值随着n + k n+kn+k的增大,越来越大接近于π \piπ
所以在k kk比较大的时候,就可以根据近似值缩小二分的范围。

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(nlnn)
离线做法,对于O ( n ln ⁡ n ) O(n\ln n)O(nlnn)个区间,分别预处理出每个区间内第一次出现数字的时间。
然后将时间挂在操作上,操作前将挂在这个时间上的区间标记,顺便维护答案。
在线做法,对于每个除数,将它的第一个空区间挂到线段树上。
插入一个数时,找到它经过的所有空区间,将它们暴力往后移。移的过程中一个个O ( lg ⁡ n ) O(\lg n)O(lgn)判断区间有没有数,直到区间中没有数为止。

离线做法

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 pp为奇素数的情况,打表发现有如此结论:
p m o d 4 = 1 , x = 0 p\mod 4=1,x=0pmod4=1,x=0,解的个数为2 p − 1 2p-12p1
p m o d 4 = 1 , x ≠ 0 p\mod 4=1,x\neq 0pmod4=1,x=0,解的个数为p − 1 p-1p1
p m o d 4 = 3 , x = 0 p\mod 4=3,x=0pmod4=3,x=0,解的个数为1 11
p m o d 4 = 3 , x ≠ 0 p\mod 4=3,x\neq 0pmod4=3,x=0,解的个数为p + 1 p+1p+1
表示只会证明x = 0 x=0x=0的情况,其它的情况求路过的大佬慷慨相助。

根据中国剩余定理,p pp拆分成若干个奇素数,每个素数的答案之积就是最终的答案。
所以这题的算法就只有分解质因数了……

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

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


版权声明:本文为A1847225889原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。