中山纪中集训Day5&Day6游记+8.5模拟赛题解(部分)

Part.I Day5游记

今天又是自闭的一天。。。

上午第一题写出了正解但调不出来(估计是逆元写多了QAQ…),只好交暴力勉强有40分。。。

第二题分析了一会打100分,结果。。。还是调不出来。。。(我今天是怎么了。。。),然后又打暴力交,结果爆零。。。

第三题以为自己用线段树搞出了正解,结果。。。爆零。。。

Part.II Day6游记

今天上午听课,是纪中的一个女学姐讲的课,她一上午就讲了Splay,树链剖分,LCT,点分治,Treap,非旋Treap,替罪羊树,可持久化线段树,可持久化平衡树,分块,块状链表,各种莫队…(然而没有题)tql orz。但是听完后一头雾水。。。

下午好像是从纪中毕业的几个大佬来讲,先讲了一个很废的KD-tree,然后讲起了Splay和LCT。还好有题可以讨论,不然就真离线了。

上午上课时看见本校ywk大佬看《上海堡垒》,于是要过来看。似乎是个写得挺不错的科幻,于是中午就把它要来自己开始看了。。。

Part.III 题解

A.矩阵游戏

题目

题目描述

输入

输出

样例输入

Sample Input1
3 4 4
R 2 4
S 4 1
R 3 2
R 2 0

Sample Input2
2 4 4
S 2 0
S 2 3
R 1 5
S 1 3

样例输出

Sample Output1
94

Sample Output2
80

样例解释

数据范围

分析

对于80分,不难想到对于中间的行列相交的地方,提取出中间的点,然后暴力地更新这些点,不难证明这些点远小于 K 2 K^2 K2的。

那么对于100分,我们考虑只对行或者列进行计算,这样难免会让它们的交点少算的,设 s u m sum sum为加上行列多的部分的新的方阵和, R i R_i Ri为第 i i i行扩大的倍数, C i C_i Ci为第 j j j列扩大的倍数,设第 i i i行第 j j j列的数为 a i , j a_{i,j} ai,j,不难证明 a i , j = ( i − 1 ) M + j a_{i,j}=(i-1)M+j ai,j=(i1)M+j

考虑列式子: a n s = s u m + ∑ R i ∑ C j [ R i C j − ( R i − 1 ) − ( C j − 1 ) ] × a i , j = s u m + ∑ ( R i − 1 ) [ ( i − 1 ) M ∑ ( C j − 1 ) + ∑ j ( C j − 1 ) ] ans=sum+\sum_{R_i}\sum_{C_j}{[R_iC_j-(R_i-1)-(C_j-1)]\times a_{i,j}}=sum+\sum(R_i-1)[(i-1)M\sum(C_j-1)+\sum j(C_j-1)] ans=sum+RiCj[RiCj(Ri1)(Cj1)]×ai,j=sum+(Ri1)[(i1)M(Cj1)+j(Cj1)]

我们可以预处理出 M ∑ ( C j − 1 ) + ∑ j ( C j − 1 ) M\sum(C_j-1)+\sum j(C_j-1) M(Cj1)+j(Cj1)。这样就可以算答案了。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
const int Maxn=1e6;
const ll Mod=1e9+7;

ll N,M;
ll R[Maxn+5],C[Maxn+5];

inline ll getnum(int x,int i) {return (1LL*(i-1)*M%Mod+x%Mod)%Mod;}

int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	int K;
	scanf("%lld %lld %d\n",&N,&M,&K);
	for(int i=1;i<=N;i++)
		R[i]=1;
	for(int j=1;j<=M;j++)
		C[j]=1;
	for(int i=1;i<=K;i++) {
		char op;int x;ll y;
		scanf("%c %d %lld\n",&op,&x,&y);
		if(op=='R')R[x]=R[x]*y%Mod;
		else C[x]=C[x]*y%Mod;
	}
	ll sum=0,d=0,s=0;
	for(int i=1;i<=N;i++)
		s=(s+getnum(1,i)*R[i]%Mod)%Mod,d=(d+R[i])%Mod;
	for(int i=1;i<=M;i++)
		sum=(sum+C[i]*s%Mod)%Mod,s=(s+d)%Mod;
	printf("%lld\n",sum);
	return 0;
}

B.跳房子

题目

题目描述

输入

输出

样例输入

4 4
1 2 9 3
3 5 4 8
4 3 2 7
5 8 1 6
4
move 1
move 1
change 1 4 100
move 1

样例输出

4 2
1 3
1 4

数据范围

分析

不难发现经过最多 N M NM NM步后,路径会形成一个环。

我们先构造出一个数组jump,表示从第 i i i行第一列出发,向右走 M M M步到达的行数,那么每次我们只要在第一列,我们就可以跳 M M M步。

对于移动操作,我们若在第一列,就直接通过jump数组每 M M M步往前跳;若不在就暴力到第一列。跳到不足 M M M步时就暴力向前走;若我们走到了重复的点上就求出当前的环长度,暴力减掉环长即可。

对于修改操作,我们要重构jump数组,这时就有事了。

但我们可以发现,对于一个节点,总有第一列连续的一段区间的点出发的路径经过它,就像这样:
其中这个大区间又被细分为三个小区间,那么我们就可以发现,修改这个节点只会影响其后面三个点的走的状况,但不会影响这三个点之后的情况,即它可能会走到被修改节点的上面和下面各两个节点之内的任意3个,所以我们暴力跑这后面三个点即可。

常数有点大,要开氧气才能过。

参考代码

#pragma GCC optimize(2)
#include<cstdio>
#include<algorithm>
using namespace std;

const int Maxn=2000;
const int Maxq=5000;
const int INF=0x3f3f3f3f;

int N,M;
int A[Maxn+5][Maxn+5];

struct Interval {
	int l,r;
	Interval(int a=INF,int b=-INF):l(a),r(b){}
	bool empty(){return l>r;}
	bool in_interval(int x){return l<=x&&x<=r;}
	int size(){return r-l+1;}
};

inline int f(int x,int lim) {
	while(x<0)x+=lim;
	return x%lim;
}
inline int NextRow(int r,int c) {
	int ret=-1,maxx=-1;
	for(int i=r-1;i<=r+1;i++) {
		int val=A[f(i,N)][f(c+1,M)];
		if(val>maxx)
			maxx=val,ret=i;
	}
	return ret;
}
inline int JumptoTail(int r,int c) {
	do r=f(NextRow(r,c++),N);
	while(c<M);
	return r;
}

int jump[Maxn+5];

void Modify(int x,int y) {
	int end_r=JumptoTail(x,y);
	Interval now(x,x);
	while(y>0) {
		y--;
		Interval nxt;
		for(int i=now.l-1;i<=now.l+1;i++)
			if(now.in_interval(NextRow(i,y))) {
				nxt.l=i;
				break;
			}
		for(int i=now.r+1;i>=now.r-1;i--)
			if(now.in_interval(NextRow(i,y))) {
				nxt.r=i;
				break;
			}
		if(nxt.empty())return;
		now=nxt;
	}
	if(now.size()>=N) {
		for(int i=0;i<N;i++)
			jump[i]=end_r;
	} else {
		for(int i=now.l;i<=now.r;i++)
			jump[f(i,N)]=end_r;
	}
}
int x=0,y=0;
int id=0,lastid[Maxn+5],lastk[Maxn+5];
void Walk(int k) {
	++id;
	if(k>=M-y) {
		k-=(M-y);
		x=JumptoTail(x,y),y=0;
	}
	while(k>=M) {
		x=jump[x],k-=M;
		if(lastid[x]==id) {
			int cirlen=lastk[x]-k;
			if(k>=cirlen) {
				int tmp=k/cirlen;
				k-=(tmp*cirlen);
			}
		}
		lastid[x]=id;
		lastk[x]=k;
	}
	while(k--)
		x=f(NextRow(x,y++),N);
}

int main() {
//	#ifdef LOACL
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
//	#endif
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	scanf("%d %d",&N,&M);
	for(int i=0;i<N;i++)
		for(int j=0;j<M;j++)
			scanf("%d",&A[i][j]);
	for(int i=0;i<N;i++)
		jump[i]=JumptoTail(i,0);
	int Q;
	scanf("%d",&Q);
	for(int i=1;i<=Q;i++) {
		char s[10];
		scanf("%s",s);
		if(s[0]=='m') {
			int k;
			scanf("%d",&k);
			Walk(k);
			printf("%d %d\n",x+1,y+1);
		} else {
			int x,y,val;
			scanf("%d %d %d\n",&x,&y,&val);
			x--,y--;
			A[x][y]=val;
			for(int i=x-1;i<=x+1;i++)
				Modify(f(i,N),f(y-1,M));
		}
	}
	return 0;
}

C.优美序列

题目

题目描述

输入

输出

样例输入

Sample Input1
7
3 1 7 5 6 4 2
3
3 6
7 7
1 3
Sample Input2
10
2 1 4 3 5 6 7 10 8 9
5
2 3
3 7
4 7
4 8
7 8

样例输出

Sample Output1
3 6
7 7
1 7
Sample Output2
1 4
3 7
3 7
3 10
7 10

分析

这道题卡常,线段树似乎是我们目前讨论出的解法。。。

据说可以用析合树做,但我不会QAQ…

不难发现一个区间是完美区间当且仅当区间最大值减最小值等于右端点减左端点。

对于一个查询区间,我们查出它的最小值和最大值在排序后所处的位置,这表明我们所选取的区间至少要比它大,然后我们比较两个区间,若符合条件就退出循环,否则继续更新。

参考代码

#pragma GCC optimize(2)
#include<cstdio>
#include<algorithm>
using namespace std;

const int Maxn=1e5;
const int INF=0x3f3f3f3f;

int N,A[Maxn+5];
int pos[Maxn+5];

inline void read(int &sum) {
    char ch = getchar();
    int tf = 0;
    sum = 0;
    while((ch < '0' || ch > '9') && (ch != '-')) ch = getchar();
    tf = ((ch == '-') && (ch = getchar()));
    while(ch >= '0' && ch <= '9') sum = sum * 10+ (ch - 48), ch = getchar();
    (tf) && (sum =- sum);
}

struct SegmentTree {
	struct Segment {
		int mn,mx;
		int mn_pos,mx_pos;
	};
	Segment t[Maxn*4+5];
	void build(int rt,int l,int r) {
		if(l==r) {
			t[rt].mx=t[rt].mn=A[l];
			t[rt].mx_pos=t[rt].mn_pos=pos[l];
			return;
		}
		int mid=(l+r)>>1;
		build(rt<<1,l,mid),build(rt<<1|1,mid+1,r);
		t[rt].mn=min(t[rt<<1].mn,t[rt<<1|1].mn);
		t[rt].mx=max(t[rt<<1].mx,t[rt<<1|1].mx);
		t[rt].mn_pos=min(t[rt<<1].mn_pos,t[rt<<1|1].mn_pos);
		t[rt].mx_pos=max(t[rt<<1].mx_pos,t[rt<<1|1].mx_pos);
	}
	int query_max_val(int rt,int l,int r,int ql,int qr) {
		if(ql<=l&&r<=qr)return t[rt].mx;
		int mid=(l+r)>>1,ret=1;
		if(ql<=mid)ret=max(ret,query_max_val(rt<<1,l,mid,ql,qr));
		if(qr>=mid+1)ret=max(ret,query_max_val(rt<<1|1,mid+1,r,ql,qr));
		return ret;
	}
	int query_min_val(int rt,int l,int r,int ql,int qr) {
		if(ql<=l&&r<=qr)return t[rt].mn;
		int mid=(l+r)>>1,ret=INF;
		if(ql<=mid)ret=min(ret,query_min_val(rt<<1,l,mid,ql,qr));
		if(qr>=mid+1)ret=min(ret,query_min_val(rt<<1|1,mid+1,r,ql,qr));
		return ret;
	}
	int query_max_pos(int rt,int l,int r,int ql,int qr) {
		if(ql<=l&&r<=qr)return t[rt].mx_pos;
		int mid=(l+r)>>1,ret=1;
		if(ql<=mid)ret=max(ret,query_max_pos(rt<<1,l,mid,ql,qr));
		if(qr>=mid+1)ret=max(ret,query_max_pos(rt<<1|1,mid+1,r,ql,qr));
		return ret;
	}
	int query_min_pos(int rt,int l,int r,int ql,int qr) {
		if(ql<=l&&r<=qr)return t[rt].mn_pos;
		int mid=(l+r)>>1,ret=INF;
		if(ql<=mid)ret=min(ret,query_min_pos(rt<<1,l,mid,ql,qr));
		if(qr>=mid+1)ret=min(ret,query_min_pos(rt<<1|1,mid+1,r,ql,qr));
		return ret;
	}
};
SegmentTree tree;

int main() {
//	#ifdef LOACL
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
//	#endif
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	int N;
	read(N);
	for(register int i=1;i<=N;i++) {
		read(A[i]);
		pos[A[i]]=i;
	}
	tree.build(1,1,N);
	int q;
	read(q);
	for(int i=1;i<=q;i++) {
		int l,r;
		read(l),read(r);
		int tl=0,tr=0;
		int first=0;
		while(l!=tl||r!=tr) {
			if(first)l=tl,r=tr;
			int t1=tree.query_min_val(1,1,N,l,r),t2=tree.query_max_val(1,1,N,l,r);
			tl=tree.query_min_pos(1,1,N,t1,t2),tr=tree.query_max_pos(1,1,N,t1,t2);
			first=1;
		}
		printf("%d %d\n",l,r);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值