NOI.ac2020省选模拟赛2

比赛链接

A.旋转

problem

给出二维坐标系里的\(n\)个点。并且有\(Q\)次询问(操作)。
每次询问给出一个区间\([l,r]\)。你需要回答一次询问并进行一次操作。

  • 输出这个区间内所有的中心(中心就是所有点坐标的平均值)。
  • \([l,r]\)内的所有点绕中心逆时针旋转\(60^{\circ}\)

所有询问结束后,输出每个点的最终坐标

\(n=10^5,Q\le 10^5\)

solution

复数还是8大会啊。

复数相乘:两复数相乘就是幅角相加,模长相乘。

我们将每个点都看作是复数。

对于旋转操作,如果中心是原点的话,我们就可以让其乘上\(cos60^{\circ}+isin60^{\circ}\)

如果中心不是原点,那就先让所有点整体平移,使得中心位于原点位置,也就相当于将所有点的坐标减去中心坐标。然后在将每个点都乘上\(cos60^{\circ}+isin60^{\circ}\)。操作完之后在将所有点的坐标平移回去,也就是加上中心的坐标。求中心坐标就直接求区间复数的和,然后除以区间长度就行了。

发现上面过程只有3种操作,区间加,区间乘,区间求和。

所以用一棵线段树维护就行了。

code

/*
* @Author: wxyww
* @Date:   2020-06-02 07:59:07
* @Last Modified time: 2020-06-02 19:36:36
*/
#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 = 100010;
const double pi = cos(-1),eps = 1e-9;
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;
}
struct complex {
	double x,y;
};
complex zero,one;
complex operator + (const complex &A,const complex &B) {
	return (complex){A.x + B.x,A.y + B.y};
}
complex operator - (const complex &A,const complex &B) {
	return (complex){A.x - B.x,A.y - B.y};
}
complex operator * (const complex &A,const complex &B) {
	return (complex){A.x * B.x - A.y * B.y,A.x * B.y + A.y * B.x};
}

complex operator * (const complex &A,double t) {
	return (complex){A.x * t,A.y * t};
}
bool operator != (const complex &A,const complex &B) {
	return fabs(A.x - B.x) > eps || fabs(A.y - B.y) > eps; 
}

complex lazy1[N * 3],tree[N * 3],a[N],lazy2[N * 3];

void build(int rt,int l,int r) {
	if(l == r) {
		tree[rt] = a[l];return;
	}
	int mid = (l + r) >> 1;
	lazy2[rt] = one;
	build(rt << 1,l,mid);build(rt << 1 | 1,mid + 1,r);
	tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
}
void pushdown(int rt,int ln,int rn) {
	if(lazy2[rt] != one) {
		lazy2[rt << 1] = lazy2[rt << 1] * lazy2[rt];
		lazy2[rt << 1 | 1] = lazy2[rt << 1 | 1] * lazy2[rt];

		lazy1[rt << 1] = lazy1[rt << 1] * lazy2[rt];
		lazy1[rt << 1 | 1] = lazy1[rt << 1 | 1] * lazy2[rt];

		tree[rt << 1] = tree[rt << 1] * lazy2[rt];
		tree[rt << 1 | 1] = tree[rt << 1 | 1] * lazy2[rt];
		lazy2[rt] = one;
	}
	if(lazy1[rt] != zero) {
		lazy1[rt << 1] = lazy1[rt << 1] + lazy1[rt];;
		lazy1[rt << 1 | 1] = lazy1[rt << 1 | 1] + lazy1[rt];

		tree[rt << 1] = tree[rt << 1] + lazy1[rt] * ln;
		tree[rt << 1 | 1] = tree[rt << 1 | 1] + lazy1[rt] * rn;

		lazy1[rt] = zero;
	}
}

void update1(int rt,int l,int r,int L,int R,complex c) {
	if(L <= l && R >= r) {
		tree[rt] = tree[rt] + c * (r - l + 1);
		lazy1[rt] = lazy1[rt] + c;
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(rt,mid - l + 1,r - mid);

	if(L <= mid) update1(rt << 1,l,mid,L,R,c);
	if(R > mid) update1(rt << 1 | 1,mid + 1,r,L,R,c);
	tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
}
void update2(int rt,int l,int r,int L,int R,complex c) {
	if(L <= l && R >= r) {
		tree[rt] = tree[rt] * c;
		lazy2[rt] = lazy2[rt] * c;
		lazy1[rt] = lazy1[rt] * c;
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(rt,mid - l + 1,r - mid);
	if(L <= mid) update2(rt << 1,l,mid,L,R,c);
	if(R > mid) update2(rt << 1 | 1,mid + 1,r,L,R,c);
	tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
}

complex query(int rt,int l,int r,int L,int R) {
	if(L <= l && R >= r) return tree[rt];
	int mid = (l + r) >> 1;
	pushdown(rt,mid - l + 1,r - mid);
	complex ret = zero;
	if(L <= mid) ret = ret + query(rt << 1,l,mid,L,R);
	if(R > mid) ret = ret + query(rt << 1 | 1,mid + 1,r,L,R);
	return ret;
}
void Update1(int rt,int l,int r,int L,int R,complex c) {
	for(int i = L;i <= R;++i) a[i] = a[i] + c;
}
void Update2(int rt,int l,int r,int L,int R,complex c) {
	for(int i = L;i <= R;++i) a[i] = a[i] * c;
}
complex Query(int rt,int l,int r,int L,int R) {
	complex ret = zero;
	for(int i = L;i <= R;++i) ret = ret + a[i];
	return ret;
}
int main() {
	one.x = 1;
	int n = read(),Q = read();
	for(int i = 1;i <= n;++i) 
		a[i].x = read(),a[i].y = read();
	build(1,1,n);
	complex mul;
	mul.x = 0.5;mul.y = sqrt(3) / 2;
	// printf("!!%.6lf\n",mul.y);
	while(Q--) {
		int l = read(),r = read();
		complex ans = query(1,1,n,l,r);
		ans.x /= (r - l + 1);ans.y /= (r - l + 1);
		printf("%.10lf %.10lf\n",ans.x,ans.y);

		update1(1,1,n,l,r,zero - ans);
		update2(1,1,n,l,r,mul);
		update1(1,1,n,l,r,ans);
	
	}

	for(int i = 1;i <= n;++i) {
		complex ans = query(1,1,n,i,i);
		printf("%.10lf %.10lf\n",ans.x,ans.y);
	}

	return 0;
}
/*
10 10
-731 -593
215 -713
797 -44
758 274
-221 -84
-911 -393
109 -201
629 733
-632 -175
620 -259
5 7
1 7
3 4
6 9
10 10
3 6
2 10
7 7
7 10
2 7
*/

B.zyb的密码匹配

problem

给出一个长度为n的序列\(a\),满足\(a_i\le n\)。再给出一个残缺的排列\(p\)\(p\)中有一些位置为\(-1\)。复原这个排列,使得\(\sum\limits_{i=1}^n|a_i-p_i|\le M\)。问有多少种复原方法。

\(n\le 40,M \le n\times n\)

solution

将所有可操作的位置(即p中为-1的位置)对应的a以及所有没在排列中出现过的数字放到一起,从大到小排序。

然后从前往后dp,对于一个位置i,数值为x。

如果i这个位置是a中的元素,那么他可以选择从前面的某个没有安放的排列中的数中选择一个填到这里,贡献就是-x。也可以记录下来,在后面dp的时候再给他安放上,贡献就是+x。

如果i这个位置是排列中的元素,那么可以选择安放到前面记录的还没有安放的位置,贡献就是-x,或者选择记录下来安放到后面的元素中,贡献就是+x。

设状态\(f[i][j][k1][k2]\)表示前i个元素,和为j,有k1个还没有安放的排列中的数,有k2个还没有填的a中的位置的方案数。

对于一个a中的位置,对应的转移就是

f[i+1][j+x][k1][k2+1]+=f[i][j][k1][k2]
f[i+1][j-x][k1-1][k2]+=f[i][j][k1][k2]*k1

对于一个排列中的位置,对应的转移就是

f[i+1][j+x][k1+1][k2]+=f[i][j][k1][k2]
f[i+1][j-x][k1][k2-1]+=f[i][j][k1][k2]*k2

空间有点爆炸,滚动一下数组就行了。

code

/*
* @Author: wxyww
* @Date:   2020-06-02 10:27:24
* @Last Modified time: 2020-06-02 12:12:58
*/
#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 = 42,mod = 998244353;
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 f[2][N * N][N][N];
#define pi pair<int,int>
pi a[N * 2];
int m,b[N],flag[N];
void upd(int &x,int y) {
	x += y;
	x >= mod ? x -= mod : 0;
}
int main() {
	// freopen("1.in","r",stdin);
	int n = read(),M = read();
	for(int i = 1;i <= n;++i) b[i] = read();
	for(int i = 1;i <= n;++i) {
		int x = read();
		if(x != -1) {
			flag[x] = 1;
			M -= abs(b[i] - x);continue;
		}
		a[++m] = make_pair(b[i],1);
	}
	
	M = max(M,0);

	for(int i = 1;i <= n;++i)
		if(!flag[i]) a[++m] = make_pair(i,0);

	sort(a + 1,a + m + 1);

	f[0][0][0][0] = 1;

	// for(int i = 1;i <= m;++i) {
	// 	printf("%d ",a[i].second);
	// }
	// puts("");

	for(int i = m;i >= 1;--i) {
		int tmp = (i & 1),w = a[i].first;
		
		for(int j = 0;j <= n * n;++j) {
			for(int x = 0;x <= n;++x) {
				for(int y = 0;y <= n;++y) {
					if(!f[tmp][j][x][y]) continue;
					if(a[i].second) {//A中元素
						// f[tmp ^ 1][j + w][x + 1][y] += 1ll * f[tmp][j][x][y] * x % mod;

						upd(f[tmp ^ 1][j + w][x + 1][y],f[tmp][j][x][y]);
						if(y && j >= w) {
							// if(i == 2 && j == 1 && !x && y == 1) 
							// 	printf("%d\n",f[tmp][j][x][y]);

							upd(f[tmp ^ 1][j - w][x][y - 1],1ll * f[tmp][j][x][y] * y % mod);
						}

					}
					else {//B中元素

						// if(i == 1 && !j && !x && !y) {
						// 	printf("%d\n",f[tmp][j][x][y]);
						// }
						upd(f[tmp ^ 1][j + w][x][y + 1], f[tmp][j][x][y]);

						if(x && j >= w)
							upd(f[tmp ^ 1][j - w][x - 1][y],1ll * f[tmp][j][x][y] * x % mod);
					}
					f[tmp][j][x][y] = 0;
				}
			}
		}
					// if(i == 2) {
					// 	printf("!!%d\n",f[1][3][1][0]);
					// }
	}


	ll ans = 0;


	for(int i = 0;i <= M;++i) {
		ans += f[0][i][0][0];
		ans >= mod ? ans -= mod : 0;
	}
	cout<<ans<<endl;



	return 0;
}

/*

     x    y
A: 大(+) 小(-)
B: 小(-) 大(+)
每次插入最小的

*/

C.送分题

problem

给出一个长度为n的序列a,\(1\le a_i \le k\)\([1,k]\)中的每个数字在a中出现至少一次。
找出一个a的子序列,在满足\([1,k]\)中每个数字在该子序列中出现至少一次的前提下,求这个字典序最小的子序列。

\(1\le n\le 10^6,1\le k \le n\)

solution

降智严重,成功把送分题写成送命题

维护一个答案序列,然后从前往后扫a中的元素。如果一个元素x在答案序列中,就不要管他。否则当答案序列尾部的数字y比x大,且y在后面还出现过,那就把y弹出来。弹完之后,在把x放进去。

code

/*
* @Author: wxyww
* @Date:   2020-06-02 08:09:29
* @Last Modified time: 2020-06-02 18:52:38
*/
#include<cstdio>
#include<set>
#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 b[N],a[N],bz[N],vis[N];
int main() {
	int n = read(),K = read();
	int m = 0;
	for(int i = 1;i <= n;++i) {
		a[i] = read();bz[a[i]]++;
	}

	for(int i = 1;i <= n;++i) {
		bz[a[i]]--;
		if(vis[a[i]]) continue;
		while(m && bz[b[m]] && b[m] > a[i]) vis[b[m]] = 0,--m;
		vis[a[i]] = 1;
		b[++m] = a[i];
	}
	for(int i = 1;i <= m;++i) printf("%d ",b[i]);




	return 0;
}
posted @ 2020-06-02 21:11  wxyww  阅读(13)  评论(0编辑  收藏  举报