NOI.ac2020省选模拟赛11

比赛链接

A.B

problem

在一个 \(N \times N\) 的水池四周,有 \(4N\) 个人想要在这里钓鱼,他们围着水池站了一圈,水池四边每个格子都有一个人。
一个人在钓鱼的时候,他需要把钓竿垂直于他所在的水池边线放置,并且他的钓竿不能与其他人的钓竿有交叉。
每个人的钓竿长度不一定相同,但是有趣的是,处于同一边的人的钓竿长度是有序的,从左到右可能是升序,也可能是降序。同时,相对的两个人的鱼竿长度和是不超过 \(N\) 的。
现在,他们想要知道最多能够允许多少人同时钓鱼。

solution

因为相对的两个人肯定不会交叉,所以可以把上下放到一侧,左右放到一次,在会交叉的点之间连边。然后\(4N\)-最大匹配就是答案。

这样显然边数太多,考虑优化。注意到题目中的性质,同一侧的人鱼竿长度是有序的。也就是说对于上下这一侧的每个点,再往左右这一侧连边是,连得都是一个区间,所以直接线段树优化简图。边数就变成了\(nlogn\)

网络流用和不用当前弧优化时间差别很大!!

code

/*
* @Author: wxyww
* @Date:   2020-06-11 12:08:22
* @Last Modified time: 2020-06-11 22:04:43
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 2000010,INF = 1e9,M = 10010;
int read() {
	int x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
struct node {
	int v,nxt,w;
}e[N];
int head[N],ejs = 1,cur[N];
void add(int u,int v,int w) {
	e[++ejs].v = v;e[ejs].nxt = head[u];head[u] = ejs;e[ejs].w = w;
	e[++ejs].v = u;e[ejs].nxt = head[v];head[v] = ejs;e[ejs].w = 0;
}

int a[M],b[M],c[M],d[M],S,T;

struct NODE {
	int id,ls,rs,mx,mn;
}TR[N];
int n;
int tot,cnt;
void build(int &rt,int l,int r) {
	if(!rt) {
		rt = ++tot;
		TR[rt].id = ++cnt;
	}
	if(l == r) {
		if(l <= n)
			TR[rt].mx = TR[rt].mn = c[l];
		else TR[rt].mx = TR[rt].mn = d[l - n];

		add(TR[rt].id,T,1);
		return;
	}

	int mid = (l + r) >> 1;
	build(TR[rt].ls,l,mid);
	build(TR[rt].rs,mid + 1,r);
	add(TR[rt].id,TR[TR[rt].ls].id,INF);
	add(TR[rt].id,TR[TR[rt].rs].id,INF);
	TR[rt].mn = min(TR[TR[rt].ls].mn,TR[TR[rt].rs].mn);
	TR[rt].mx = max(TR[TR[rt].ls].mx,TR[TR[rt].rs].mx);
}

void query(int rt,int l,int r,int L,int R,int x,int s) {//s向大于等于x的点连边
	if(!rt) return;
	if(TR[rt].mn >= x && L <= l && R >= r) {
		// printf("%d %d\n",l,r);
		add(s,TR[rt].id,1);
		return;
	}
	if(TR[rt].mx < x) return;
	int mid = (l + r) >> 1;
	if(L <= mid) query(TR[rt].ls,l,mid,L,R,x,s);
	if(R > mid) query(TR[rt].rs,mid + 1,r,L,R,x,s);
}
int dep[100000];
queue<int>q;
int bfs() {
	memset(dep,0,sizeof(dep));
	dep[S] = 1;q.push(S);
	while(!q.empty()) {
		int u = q.front();q.pop();
		for(int i = head[u];i;i = e[i].nxt) {
			int v = e[i].v;
			if(!dep[v] && e[i].w) {

				dep[v] = dep[u] + 1;
				q.push(v);
			}
		}
	}
	return dep[T];
}
int dfs(int u,int now) {
	if(u == T) return now;
	int ret = 0;
	for(int &i = cur[u];i;i = e[i].nxt) {
		int v = e[i].v;
		if(dep[v] == dep[u] + 1 && e[i].w) {
			int k = dfs(v,min(now - ret,e[i].w));
			e[i].w -= k;e[i ^ 1].w += k;
			ret += k;
			if(ret == now) return ret;
		}
	}
	return ret;
}
int dinic() {
	int ret = 0;
	while(bfs()) {
		memcpy(cur,head,sizeof(cur));
		ret += dfs(S,INF);
	}

	return ret;
}
int root;
int main() {
	n = read();
	
	S = ++cnt,T = ++cnt;

	for(int i = 1;i <= n;++i) a[i] = read();
	for(int i = 1;i <= n;++i) b[i] = read();
	for(int i = 1;i <= n;++i) c[i] = read();
	for(int i = 1;i <= n;++i) d[i] = read();

	build(root,1,n + n);
	
	for(int i = 1;i <= n;++i) {
		++cnt;
		add(S,cnt,1);
		query(root,1,n + n,1,a[i],i,cnt);
		query(root,1,n + n,n + 1,a[i] + n,n - i + 1,cnt);
	}
	for(int i = 1;i <= n;++i) {
		++cnt;
		add(S,cnt,1);
		query(root,1,n + n,n - b[i] + 1,n,i,cnt);
		query(root,1,n + n,n - b[i] + 1 + n,n + n,n - i + 1,cnt);
	}

	cout<<(n * 4) - dinic();

	return 0;
}

B.array

problem

初始时有一个长度为 \(n\) 的数组。
一共有 \(Q\) 次操作,操作分为两种:① 将第 \(x\) 个数换成 \(y\);② 给出一个区间 \([l,r]\) 和一个常数 \(k\),求一个最大的 \(m\) 使得序列 \(k,k+1,\dots,k+m\) 是这个区间的子序列。

\(subtask1:n,Q\le 5000\)

\(subtask2:\)数据随机生成

\(subtask3:\)不存在修改操作

对于全部数据\(n,Q\le 10^6\)

solution

只写了前三个\(subtask\),LCT还是不大会啊。。

对于第一个\(subtask\),直接暴力即可。
对于第二个\(subtask\),因为数据随机,所以答案不会很大。对于每个数字x维护一个set,里面存x出现的所有位置,查询的时候,就从k开始找他的set里面大于等于l的最小的位置p,然后再从\(k+1\)里找大于等于p的最小的位置\(\cdots\),直到能找到的位置比r大就停止。
对于第三个\(subtask\),不存在修改操作,那对于第i个位置就向\(a[i]+1\)下一次出现的位置j连一条边,这样就形成了一棵树的结构。然后每次查询的时候就倍增网上跳就行了。

code

/*
* @Author: wxyww
* @Date:   2020-06-11 10:50:44
* @Last Modified time: 2020-06-11 18:54:38
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 1000010;
int read() {
	int x = 0,f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}
int n,Q;
namespace BF1 {
	const int N = 5010;
	int a[N];
	void main() {
		for(int i = 1;i <= n;++i)
			a[i] = read();

		while(Q--) {
			int opt = read();
			if(opt == 1) {
				int x = read(),y = read();
				a[x] = y;
			}
			else {
				int l = read(),r = read(),k = read();
				int p = k;
				for(int i = l;i <= r;++i) {
					if(a[i] == p) ++p;
				}
				printf("%d\n",p - k - 1);
			}
		}

	}
}
int a[N];
struct node {
	int opt,x,y,K;
}que[N];
namespace BF2 {
	const int logN = 21;
	vector<int>e[N];
	int lst[N],lca[N][logN + 1];
	vector<int>s[N];
	void dfs(int u) {
		for(int i = 1;i <= logN;++i) 
			lca[u][i] = lca[lca[u][i - 1]][i - 1];
		for(vector<int>::iterator it = e[u].begin();it != e[u].end();++it) {
			lca[*it][0] = u;
			dfs(*it);
		}
	}
	int get(int x,int to) {
		for(int i = logN;i >= 0;--i) {
			if(lca[x][i] <= to && lca[x][i]) x = lca[x][i];
		}
		return a[x];
	}
	void main() {
		for(int i = 1;i <= n + 1;++i) lst[i] = n + 1;
		for(int i = 1;i <= n;++i) s[i].push_back(n + 1);
		for(int i = n;i >= 1;--i) {
			e[lst[a[i] + 1]].push_back(i);
			lst[a[i]] = i;
			s[a[i]].push_back(i);
		}

		for(int i = 1;i <= n;++i) reverse(s[i].begin(),s[i].end());
	// cerr<<"!!!"<<endl;

		dfs(n + 1);

		for(int i = 1;i <= Q;++i) {
			int l = que[i].x,r = que[i].y,k = que[i].K;

			int t = *lower_bound(s[k].begin(),s[k].end(),l);

			if(t > r) {
				puts("-1");continue;
			}
			printf("%d\n",get(t,r) - k);
		}
	}
}
namespace BF3 {
	set<int>s[N];
	void main() {

		for(int i = 1;i <= n;++i) {
			s[i].insert(n + 1);
			s[a[i]].insert(i);
		}
		s[n + 1].insert(n + 1);

		for(int i = 1;i <= Q;++i) {
			int opt = que[i].opt;
			if(opt == 1) {
				int x = que[i].x,y = que[i].y;
				s[a[x]].erase(x);
				a[x] = y;
				s[y].insert(x);
			}
			else {
				int l = que[i].x,r = que[i].y,k = que[i].K;
				int p = l;
				for(int j = k;j <= n + 1;++j) {
					p = *s[j].lower_bound(p);
					if(p > r) {
						printf("%d\n",j - k - 1);break;
					}
				}
			}
		}
	}
}
int main() {
	// freopen("1.in","r",stdin);
	// freopen("1.out","w",stdout);
	n = read(),Q = read();
	if(n <= 5000 && Q <= 5000) {
		BF1::main();return 0;
	}
	for(int i = 1;i <= n;++i) a[i] = read();
	int flag = 0;
	for(int i = 1;i <= Q;++i) {
		que[i].opt = read();
			que[i].x = read(),que[i].y = read();
		if(que[i].opt == 2) que[i].K = read();
		else flag = 1;
	}
	if(!flag) {
		BF2::main();return 0;//无修改
	}
	BF3::main();return 0;//随机
	return 0;
}
/*
6 9
1 1 1 2 3 2
2 1 4 1
2 1 5 1
2 1 4 1
2 1 6 1
2 1 4 3
2 1 4 2
2 6 6 2
2 2 6 2
2 1 5 2

1
2
1
2
-1
0
0
1
1
*/

C.铍配

problem

给出两个整数n和m\((n\le m)\),将\([0,n-1]\)\([m,m+n-1]\)内的数字两两配对。使得对于每一对数\([0,n-1]\)内取的数都是\([m,m+n-1]\)内的数的子集。

solution

代码一看就懂了,我也不知道为啥是对的\(QAQ\)

code

/*
* @Author: wxyww
* @Date:   2020-06-11 08:12:14
* @Last Modified time: 2020-06-11 20:16:17
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 2000010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}

int main() {
	int n = read() - 1,m = read();
	
	while(n >= 0) {
		int x = m;
		while((x & n) != n) ++x;
		for(int i = x;i >= m;--i) printf("%d %d\n",n--,i);
		m = x + 1;
	}
	return 0;
}
posted @ 2020-06-14 10:15  wxyww  阅读(11)  评论(0编辑  收藏  举报