Code+7 总结&题解

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


彩蛋题

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


T1

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


T2

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

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

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

同样这题没有代码。


T3

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

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

再讲讲正解:
如果打了个表,可以发现 a 1 a_1 a1每隔一次就会被操作一次。
那么 a 2 a_2 a2呢……
考虑从前往后推,假如决定了 a 1.. i − 1 a_{1..i-1} a1..i1操作了多少次,剩下的操作次数为 r r r
a i a_i ai会操作 x x x次,可以列出个式子: 0 ≤ a i = i x − ( r − x ) ≤ i 0\leq a_i=ix-(r-x)\leq i 0ai=ix(rx)i
解出 x x x,可以发现夹在范围之间的整数只有一个,于是 x x x的取值唯一。
接着继续往后推就可以了。
然而这题卡时间。
打表可以发现, n 2 n + k \frac{n^2}{n+k} n+kn2的值随着 n + k n+k n+k的增大,越来越大接近于 π \pi π
所以在 k k 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(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 p p为奇素数的情况,打表发现有如此结论:
p m o d    4 = 1 , x = 0 p\mod 4=1,x=0 pmod4=1,x=0,解的个数为 2 p − 1 2p-1 2p1
p m o d    4 = 1 , x ≠ 0 p\mod 4=1,x\neq 0 pmod4=1,x=0,解的个数为 p − 1 p-1 p1
p m o d    4 = 3 , x = 0 p\mod 4=3,x=0 pmod4=3,x=0,解的个数为 1 1 1
p m o d    4 = 3 , x ≠ 0 p\mod 4=3,x\neq 0 pmod4=3,x=0,解的个数为 p + 1 p+1 p+1
表示只会证明 x = 0 x=0 x=0的情况,其它的情况求路过的大佬慷慨相助。

根据中国剩余定理, p p 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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值