2019.8.13 一场智商勉强上线的考试【including permut,beautiful,subset

初见安~好久没有写考试总结了呢【因为前期几场都改不完QAQ】

1 permut

1.1 题目

求由 1 n 一共 n 个数字组成的所有排列中,逆序对个数为 k 的有多少个

1.2 输入

第一行为一个整数 T,为数据组数。

以下 T 行,每行两个整数 nk,意义如题目所述。

1.3 输出

对每组数据输出答案对 10000 取模后的结果

1.4 Sample Input

1

4 1

1.5 Sample Output

3

1.6 数据

对于 30% 的数据,满足 n 12

对于所有数据,满足 n 1000, k 1000,T 10

1.7 题解

这个题和洛谷的求逆序对是一个意思。所谓逆序对,就是指i < j,a[i]>a[j]这样的一对i,j。所以如果排列为3,2,1,那么就存在3个逆序对。

这个题求方案数啊。看到这种一般就能想到两种解决方法:数论组合数和dp。很明显用dp可以方便很多。

怎么dp呢?本狸是手写出来了4的所有排列并且凑出了各自的逆序对的个数,再结合dp的递推性,可以想到:假设现在有一个3的排列,你要把4插进去,放在最后,逆序对数不变;往前放一个位置,+1;再往前放一个位置,+2……以此类推,放在最前面就可以加3,也就是4-1。所以就可以设dp[i][j]表示用前i个数,逆序对数为j对的情况数量。

枚举每一种插入方式可得:dp[i][j] += dp[i - 1][j - p],p \in [0,i-1].

这就是本题的核心代码了。最后dp[n][k]即为所求。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 1005
const int mod = 10000;
using namespace std;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, k, T;
int dp[maxn][maxn];
signed main() {
	T = read();
	while(T--) {
		n = read(), k = read();
		//memset(dp, 0, sizeof dp);//其实递推都一样,可以不用memset……
		
		for(int i = 1; i <= n; i++) {
			dp[i][0] = 1;//初始化
			for(int j = 1; j <= k; j++) {
				for(int p = i - 1 ; p >= 0; p--)//这里正反均可,若要滚动则需如此
					if(j >= p) dp[i][j] = (dp[i][j] + dp[i - 1][j - p]) % mod;
//				printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
			}
		}
		
		printf("%d\n", dp[n][k]);
	}
}

第一题还是比较水【然而我就因为没有判定if j >= p导致丢了70分……】

2 beautiful

2.1 题目

一个长度为 n 的序列,对于每个位置 i 的数 ai 都有一个优美值,其定义是:找到序列中最

长的一段 [l, r],满足 l i r,且 [l, r] 中位数为 ai(我们比较序列中两个位置的数的大小时,

以数值为第一关键字,下标为第二关键字比较。这样的话 [l, r] 的长度只有可能是奇数),r - l + 1 就是 i 的优美值。

接下来有 Q 个询问,每个询问 [l, r] 表示查询区间 [l, r] 内优美值的最大值。

2.2 输入

第一行输入 n 接下来 n 个整数,代表 ai 接下来 Q,代表有 Q 个区间接下来 Q 行,每行

两个整数 l, r(l r),表示区间的左右端点

2.3 输出

对于每个区间的询问,输出答案

2.4 Sample Input

8
16 19 7 8 9 11 20 16
8
3 8
1 4
2 3
1 1
5 5
1 2
2 8
7 8

2.5 Sample Output

7
3
1
3
5
3
7
3

2.6 数据

对于 30% 的数据,满足 nQ 50

对于 70% 的数据,满足 nQ 2000

对于所有数据,满足 n 2000, Q 100000,ai 200

2.7 题解

超开心的!!这个题自己想出来了做法还过了!!!

但是题目描述太恶心了。看了好久才看懂。

就是说给你一个长度为n的序列,第i个数字值为\large a_i,同时又有一个概念名为优美值,就是能以\large a_i为中位数的最大的区间的长度,就是i的优美值。给你Q个询问,每个询问包含一个区间,问你这个区间内最大的优美值是多少。

很明显,关于询问,就是一个普通的RMQ,线段树维护一下就行了【当然方法很多】。关键在于——如何处理出每个点的优美值呢?我们知道,作为中位数的意思就是这个区间内大于\large a_i的数和小于\large a_i的数的数量一样多。所以我们可以想到:从i向左右两边发散,定义一个cnt,遇到大于\large a_i的就+1,小于的就-1,然后两边相互匹配就行了。

具体操作就是:向其中一边发散过去,用map记录值为cnt时的下标位置之类的;而后再往另一边发散,再次统计cnt的时候顺便看看之前走的那边有没有可匹配为0的。这样的话大概就是\large O(n^2)的预处理【map的常数暂时忽略……

所以思路就分为这两部分……当然对于第二部分也有用差分解决的,这里就不讲啦~

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#define maxn 2005
using namespace std;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, a[maxn], cnt, be[maxn];
int mx[maxn << 2];
map<int, int> mp;
void build(int p, int l, int r) {//建树
	if(l == r) {mx[p] = be[l]; return;}
	int mid = l + r >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
}

int ask(int p, int l, int r, int ls, int rs) {//查询
	if(ls <= l && r <= rs) return mx[p];
	int mid = l + r >> 1, ans = 0;
	if(ls <= mid) ans = max(ans, ask(p << 1, l, mid, ls, rs));
	if(rs > mid) ans = max(ans, ask(p << 1 | 1, mid + 1, r, ls, rs));
	return ans;
}

signed main() {
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	
	for(int i = 1; i <= n; i++) {
		be[i] = 1;
		mp.clear(); cnt = 0;
		for(int j = i - 1; j; j--) {
			if(a[j] <= a[i]) cnt--;//往左边走
			else cnt++; mp[cnt] = i - j;
			if(!cnt) be[i] = max(be[i], i - j + 1);//这里其实我处理的比较麻烦……
		}
		
		cnt = 0;
		for(int j = i + 1; j <= n; j++) {
			if(a[j] >= a[i]) cnt++;
			else cnt--; 
			if(!cnt || mp[0 - cnt]) be[i] = max(be[i], j - i + mp[0 - cnt] + 1);
            //这里有更优的处理方式的。
		}
	}
	
	build(1, 1, n);//线段树
	int Q = read(), l, r;
	while(Q--) {
		l = read(), r = read();
		printf("%d\n", ask(1, 1, n, l, r));
	}
	return 0;
}
/*
9
3 7 9 10 1 2 0 3 4
9
1 1
2 2
3 3
4 4 
5 5
6 6
7 7
8 8
9 9
*/

 

3 subset

3.1 题目

一开始你有一个空集,集合可以出现重复元素,然后有 Q 个操作

1. add s

在集合中加入数字 s

2. del s

在集合中删除数字 s。保证 s 存在

3. cnt s

查询满足 a&s = a 条件的 a 的个数

3.2 输入

第一行一个整数 Q 接下来 Q 行,每一行都是 3 个操作中的一个

3.3 输出

对于每个 cnt 操作输出答案

3.4 Sample Input

7
add 11
cnt 15
add 4
add 0
cnt 6
del 4
cnt 15

3.5 Sample Output

1
2
2

3.6 数据

对于 30% 的数据满足:1 n 1000

对于 100% 的数据满足,1 n 200000 , 0 < s < 2 ^ 16

3.7 题解

这个题要拿30分的暴力很简单,就不说了……数组,vector都行。

正解说是分块思想。【先直接放官方题解:

然而事实上并不怎么好理解出如何实际操作。

对于add和delete,相当于是固定了读入的x的前缀,也就是前8位,去看后8位。因为cnt语句求的是&上给出的值过后原值不变的数的个数,所以我们对于add和delete就要反过来——用已有的数x去对应枚举的值i,如果&过后x还是x,那么对u这个值得答案的贡献就有了1。

总之就是……两类操作,一个枚举前缀,固定后缀,一个固定后缀,枚举前缀,只要两种操作相反就可以了。我写的就是和题解相反的那种……

这个题有点说不清楚哎。但思路也就这样了。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 1 << 8;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int x, n, sum[maxn][maxn];
char op[maxn];
void add(int x) {
	int a = x >> 8, b = x - (a << 8);//a是前8位,b是后8位
	for(int i = 0; i < maxn; i++) if((i & a) == a) sum[i][b]++;//用a去对应得到贡献的i
}

void cut(int x) {
	int a = x >> 8, b = x - (a << 8);
	for(int i = 0; i < maxn; i++) if((i & a) == a) sum[i][b]--;//同理
}

int find(int x) {
	int ans = 0, a = x >> 8, b = x - (a << 8);
	for(int i = 0; i < maxn; i++) if((i & b) == i) ans += sum[a][i];//以i为主体了。
	return ans;
}

signed main() {
	n = read();
	for(int i = 1; i <= n; i++) {
		scanf("%s %d", op, &x);
		if(op[0] == 'a') add(x);
		else if(op[0] == 'd') cut(x);
		else printf("%d\n", find(x));
	}
	return 0;
}

QwQ迎评

——End——

 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值