初见安~好久没有写考试总结了呢【因为前期几场都改不完QAQ】
1 permut
1.1 题目
求由 1 到 n 一共 n 个数字组成的所有排列中,逆序对个数为 k 的有多少个
1.2 输入
第一行为一个整数 T,为数据组数。
以下 T 行,每行两个整数 n,k,意义如题目所述。
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。所以如果排列为3,2,1,那么就存在3个逆序对。
这个题求方案数啊。看到这种一般就能想到两种解决方法:数论组合数和dp。很明显用dp可以方便很多。
怎么dp呢?本狸是手写出来了4的所有排列并且凑出了各自的逆序对的个数,再结合dp的递推性,可以想到:假设现在有一个3的排列,你要把4插进去,放在最后,逆序对数不变;往前放一个位置,+1;再往前放一个位置,+2……以此类推,放在最前面就可以加3,也就是4-1。所以就可以设表示用前i个数,逆序对数为j对的情况数量。
枚举每一种插入方式可得:.
这就是本题的核心代码了。最后即为所求。
上代码——
#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% 的数据,满足 n,Q ≤ 50
对于 70% 的数据,满足 n,Q ≤ 2000
对于所有数据,满足 n ≤ 2000, Q ≤ 100000,ai ≤ 200
2.7 题解
超开心的!!这个题自己想出来了做法还过了!!!
但是题目描述太恶心了。看了好久才看懂。
就是说给你一个长度为n的序列,第i个数字值为,同时又有一个概念名为优美值,就是能以为中位数的最大的区间的长度,就是i的优美值。给你Q个询问,每个询问包含一个区间,问你这个区间内最大的优美值是多少。
很明显,关于询问,就是一个普通的RMQ,线段树维护一下就行了【当然方法很多】。关键在于——如何处理出每个点的优美值呢?我们知道,作为中位数的意思就是这个区间内大于的数和小于的数的数量一样多。所以我们可以想到:从i向左右两边发散,定义一个cnt,遇到大于的就+1,小于的就-1,然后两边相互匹配就行了。
具体操作就是:向其中一边发散过去,用map记录值为cnt时的下标位置之类的;而后再往另一边发散,再次统计cnt的时候顺便看看之前走的那边有没有可匹配为0的。这样的话大概就是的预处理【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——