2019/11/1 CSP模拟

写在前面的反思

该拿的部分分还是得拿完啊,因为懒+动作慢没有写最后一道题的菊花图和链的情况,其实这两个点并不难。。
虽然只有\(10pts\),但是已经足够往上爬一截了啊,额外的\(10pts\)在今天大众分\(210pts\)的背景下显得好重要
另外\(T2\)下来发现最后判断的地方假了,所幸好像它能跑得动的数据范围内都没出问题,但要卡还是很好卡,只是这次运气好没被卡而已,下次写的时候还是要注意,正式比赛不一定就不卡了

T1

\(sb\)
对于\(b==1\),判断所有总数的\(gcd\)能不能整除\(a\)
对于\(b == 2\),判断总数中最小的一个开方是不是严格大于\(a\)
\(sqrt()\)的返回值是浮点数救我一命,差点想乘起来了,发现会爆\(long\ long\)

T2

思路

桶套桶

记录标本串每个字母出现的次数(第一层),设每个字母出现的次数为\(cnt_i\),那么第二层以\(cnt_i\)为下标,统计每个字母出现次数的次数,记为\(cnt1_i\)

举个例子

原标本串

\(aabbbcccd\)

第一层桶,下标对应字母:\(2\ 3\ 3\ 1\)

第二层桶,下标对应第一层桶中的值:\(1\ 1\ 2\)

考虑记录一个\(tot\)总和,当我们当前枚举到的子串标本串的第二层桶的各项差的绝对值之和为\(0\)时,这两者第二层桶中的每个值一一对应,即可以通过变换得到

因为每次挪动区间类似滑动窗口,所以我们只需要处理最开始的区间,之后的区间减去左边挪掉的端点,加入右端新进入的端点即可继续统计答案

代码:

#include<bits/stdc++.h>
#define N (2000 + 10)
using namespace std;
inline int read() {
	int cnt = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
	while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
	return cnt * f;
}
int T, n, Q, cnt[30], cnt1[30], g[N], f[N], q[N][30], l, r, ans, tmp, len, tot;
bool vis[N];
char ch; int s[N];
void add(int x, int sig) {
	/*
		因为挪动不关标本串的事,所以一切处理都在cnt1和f数组上,g只拿来比较 
	*/ 
	if (f[cnt1[x]] > g[cnt1[x]]) --tot; else ++tot;  //因为要减去字母x原来出现次数的影响,所以同样影响到tot,把tot被影响的部分减掉 
	--f[cnt1[x]]; //外层桶减去字母x原来出现次数的影响 
	cnt1[x] += sig; //+1或-1,看是删除还是添加,更新内层桶 
	if (f[cnt1[x]] >= g[cnt1[x]]) ++tot; else --tot; //更新外层桶,和上面减的地方类似 
	++f[cnt1[x]]; //外层桶加上字母x新出现次数的影响 
}
int main() {
	T = read();
	while (T--) {
		n = read(), Q = read();
		tmp = 0;
		for (; !isalpha(ch); ch = getchar());
		for (; isalpha(ch); ch = getchar()) s[++tmp] = ch - 'a' + 1;
		for (register int i = 1; i <= n; ++i) {  //整个串,每个字母出现次数的前缀和统计  
			for (register int j = 1; j <= 26; ++j) q[i][j] = q[i - 1][j]; //把前一个的答案继承过来,每个字母都要继承  
			++q[i][s[i]]; //把这个字母的答案统计进去 
		} while (Q--) {
			/*
				cnt: 标本串的内层桶 cnt2:枚举区间的内层桶
				g:标本串的外层桶 f:枚举区间的外层桶 
			*/
			for (register int i = 1; i <= 26; ++i) cnt[i] = cnt1[i] = 0;  //内层桶初始化 
			l = read(), r = read();
			len = r - l + 1;
			//当前在处理区间[1, len]这个子串(当前枚举的区间),即对于每个询问枚举的第一个区间 
			for (register int i = 1; i <= 26; ++i) {
				cnt[i] = q[r][i] - q[l - 1][i]; //处理标本串的每个字母的出现次数,前缀和减一下 
				cnt1[i] = q[len][i];  //处理当前子串的每个字母的出现次数 
				++g[cnt[i]];  //对标本串字母出现次数的出现次数(外层桶)进行统计 
				++f[cnt1[i]]; //对当前子串...............进行统计(中间同上) 
			}
			tot = 0;  //总和,初始化为0 
			for (register int i = 1; i <= 26; ++i) 
				if (!vis[cnt[i]]) vis[cnt[i]] = true, tot += g[cnt[i]];  
				/*
					这里先把所有标本串的外层桶的值累加到tot上是为了防止包含的情况,如下例子
					g(标本串): 1 1 2 3
					f(当前区间) : 1 1 2
					这实际上是一个不合法情况,不能统计进答案,但如果我们直接枚举在子串中出现的每个字母的出现次数就会出现这样的问题,所以先在前面加好
					其实和处理完之后把剩下没有被枚举到的在标本串的内层桶中出现的元素累加上tot是一个效果,但这样需要多维护一个vis数组 
				*/ 
			for (register int i = 1; i <= 26; ++i) vis[cnt[i]] = false;
			for (register int i = 1; i <= 26; ++i) 
				if (!vis[cnt1[i]]) {
					vis[cnt1[i]] = true;
					tot -= g[cnt1[i]];   //如果遇到在标本串和子串中同时出现的元素,先把这个减掉,防止影响后面统计答案 
					if (f[cnt1[i]] > g[cnt1[i]]) tot += (f[cnt1[i]] - g[cnt1[i]]);
					else tot += (g[cnt1[i]] - f[cnt1[i]]);
					//上面两行就是取绝对值 
				}
			for (register int i = 1; i <= 26; ++i) vis[cnt1[i]] = false;
			ans = 0;
			if (!tot) ++ans;  //如果总和是0,增量答案 
			//处理完第一个区间,开始向后挪动,下面i枚举的是右端点 
			for (register int i = len + 1; i <= n; ++i) {
				add(s[i], 1);  //加入新右端点 
				add(s[i - len], -1);  //删除原左端点 
				if (!tot) ++ans;  //统计答案 
			} printf("%d\n", ans);
			for (register int i = 1; i <= 26; ++i) --g[cnt[i]], --f[cnt1[i]];  //初始化,相当于前面统计g和f的过程反过来 
		}
	}
	return 0;
}

T3

待补档,博主正在写

posted @ 2019-11-01 21:40  kma_093  阅读(201)  评论(0编辑  收藏  举报