平常の刷题8

CF1603A

这个题目1300,非常简单,但是又不那么简单,这道题真的是用数学归纳法来证明结论的,证明方法其实非常的简单;

注意,我们可以把ai从1到i的位置擦掉,所以对于每个i,应该至少有一个从2到i+1的整数使ai不能被那个整数整除。如果对某个整数i不满足,那么就肯定没有解。否则,解总是存在的。
为什么?
我们可以用归纳法证明它。假设有可能删除包含n - 1个元素的前缀。作为一个可以抹去在某个位置从1到n(假设k),然后擦除n−1元素的前缀,当前缀包含k−1元素,然后一个k的位置,所以我们可以在这个位置把它擦掉,并相应地删除其余的序列。
所以我们只需要检查从1到n的所有整数i,如果ai不能被至少一个从2到i+1的整数整除。注意,如果ai能被所有从2到i+1的整数整除,那么它就意味着ai能被LCM(2,3,…,(i+1))整除。而当i=22时,LCM(2,3,…,23)>109>ai。对于i≥22,总会有一个从2到i+1的整数不能整除ai。所以我们不需要检查它们。对于i<22,使用bruteforce。

但是这题难就难在你怎么知道要去推导出这个结论,一开始我做的时候想到了一个八九不离十的结论,但是和正解所揭示出来的结论还有点区别,所以说我们做题的时候要注意你总结出来的性质本质是什么。简单观察样例,我们发现乱删并不能直接删完,那么我们可以从影响小的入手,也就是优先删除后面的内容,这一点启发我们,如果一个东西不能删,又不能变成被删的,那么这个东西就永远删不了了,然后我们就发现了这个东西;
还有一个是题目中的概念应该要清楚的去理解,题解中写到LCM,我做的时候搞成了FAC,所以A不了,挺遗憾的呀;

CF1603B

这题题目看错了,不然老早干出来了, x , y x,y xy 都是偶数,所以直接干很简单,但是这题目需要注意的一个点是,可以对这种题目进行分类讨论,或者是分类论证,从而达到更好解题的目的;

HHHOJ734

这个题目很巧妙,绕一圈回来的我们可以想到用极坐标来做,虽然我是看题解的,极坐标这种东西鬼想得到,然后我们就能确定topo顺序了,这一点和之前有一题 d a n c i n g   l i n e dancing \space line dancing line 那题挺像的,那题也是去找一个topo序,我想到了向量,那题总是能分解出一个方向向量,那么这题既然是一个环,想到极坐标那也是应当的。然后又一个点,就是说,因为他是最后要除去一个距离的代价,那么我们就可以转化为一个二分答案,多了个log而已不会怎样,如果说不用二分答案,理论上来说是做不了的,这一点我们在下面一道题目里面会给出相关证明,将其转化为二分答案之后,原本无法维护的信息,变成了可以线性叠加的信息,并且也随之具有了最优子结构的性质,于是可以DP解决,最后一步我也没看懂,所以没有代码了;

CF1603C

这题两千三的dp是吧,虽然我没做出来 O ( n ) O(n) O(n)的,用了一个我也不知道什么鬼一样的DP优化,因为题解都是英文所以我也没看懂,最有子结构应该还是很好找的,值得注意的一点是,转移方程真的应该把它给认真的写出来,不然调个半天;

CF1579E1

看到deque马上点了进去,因为这次的那个啥比赛就考了个deque,能想到的人寥寥无几,但是这个比较简单,虽然简单,但是我还是做错了,我随手想的做法假掉了,实在是疏忽大意,deque的题目倒着想是一个好想法,但是要注意细节,正解还是很显然的;

我们将一个一个地处理排列元素。
对于第一个元素,无论我们把它加到deque的哪一边,结果都是一样的——在deque中会有一个由一个元素组成的序列(等于第一个permutation元素)。
现在让我们考虑将排列的第i个元素添加到deque。首先考虑i=2,然后i=3,以此类推,直到i=n。让我们描述为每一步选择deque边的一般算法。注意,如果元素[d1,…,di−1]现在在deque中,那么deque中从当前状态获得的所有最终排列都可以分解成这种形式的对
[…,ai, d1,…,di−1…]
d1[…,…,di−1,ai…],
其中,隐藏在“…”后面的最终排列的开始和结束,是由下列所有选择的相同序列获得的,因此在第一和第二之间是相等的。
注意,当ai因此,不管下面的选项是什么,如果aid1,那么第一个排列永远不会最小。
这意味着我们可以只根据第i个元素与d1的关系来选择要添加到deque的哪一边:如果ai
时间复杂度为O(n)。可选的解决方案,也符合时间限制,涉及在反向的原始排列中找到一个字典序上最小的递增序列,如果考虑排列的定义,可以用O(nlogn)时间复杂度或O(n)时间复杂度来实现。

CF1579C

非常简单?的一个贪心加暴力题目, 1500 1500 1500 不过如此;
首先数据范围很小我们完全可以暴力解决,然后就是贪心的部分,我们考虑从左到右从上到下的贪心,我们看到一个钩子就直接往下搜就可以了,遇到可以有转折点的时候可以转一下,如果说转折点小于等于另一条边的长度那么对应的那条钩子就是成立的,如果说大于的话,我们可以给出一个证明,他高度大,在上一轮就已经被我们遍历过一遍了,如果说解里面有他,那么一定会被我们给搜到。然后就好了。证毕。

CF1607D

1300 1300 1300 分的大水题一道,虽然说是水题,但是我认为里面还是有值得思考的内容。就好比是两个人做两件事情,每个人都想做其中的某一件事并且这两件事情并不相同,那么你是让他们都做自己不喜欢的事情还是都做喜欢的事情呢,就是这个道理,假设数据完全随机,那么让那两个人都做自己喜欢做的事情,期望值是不是会变得更大呢;

CF1607G

一道贪心,但是还是挺难分析的,我没有分析出来全部内容,只能说有部分内容分析出来了;
这道题目里唯一的一个贪心的点,每个单元去取一些值,以此来凑到我们所需要的最终的值。这里直接贪心就可以了,可以取到的,没必要多此一举的进行排序;
这题的具体解法;

Let’s find how much meat and fish a taster can eat at most. Note that a taster can eat no more than min(ai,m) of fish from the i-th dish (since he can’t eat more than m or more than there is at all). Similarly, he can eat no more than min(bi,m) of meat. Let’s sum the obtained values over all i and denote the resulting sums by A and B —the maximum total amount of fish and meat that can be eaten.
Let’s denote by balance* T the value ∑i=1nai−∑i=1nbi, that is the balance without module. If the taster eats as much fish as possible, he will eat A of fish and nm−A of meat, and change the balance* by −A+(nm−A)=nm−2A. Similarly, if he eats the maximum amount of meat, the balance* will change by 2B−nm. Note that the taster can achieve any balance* between 2B−nm and nm−2A of the same oddity as both of these numbers. To do this, just take the way of eating the maximum fish and substitute eating a gram of fish for a gram of meat several times.
Thus, the final balance Tres can be found as min|x| over all x between T+(2B−nm) and T+(nm−2A) with same oddity. To do this, just check the boundaries of the resulting segment — if they have the same sign, then it’s the boundary with the smallest absolute value, otherwise we can take one of the numbers −1, 0, 1 present in said set (depending on the parity of nm).
All that remains is to find how much of what type to eat from each dish. Having obtained the answer in the previous paragraph (the final balance), we can reconstruct how much fish and how much meat the taster has to eat to achieve it. The expected amount of fish to be eaten can be found as eA=T+nm−Tres2.
Note that the taster must eat max(m−bi,0) of fish from the i-th dish, since if meat bi<m, then at least m−bi of fish is guaranteed to be eaten. Let’s break down eA into the sum of nm−B (how much total fish will have to be eaten anyway) and the remaining value gA=eA−(nm−B). Let’s go through all the dishes and collect the first summand as just the sum of max(m−bi,0) over all i, and the second summand with greedy algorithm, each time giving the taster as much fish beyond what he must eat anyway until the sum of such “additions” reaches gA. And knowing for each dish how much fish will be eaten from it, the amount of meat eaten can be calculated by subtracting the fish eaten from m.

大抵来说的解法是长这个样子的;
我认为能想到这个思路还是因为数学推导,因为我们推出了一个重要的结论,就是答案的奇偶性和最值,然后我们就可以找到之间的等量关系来列出相关的方程,列出方程之后有一个重要的内容,因为我们最后取得量是恒定的,然后将每个东西里面至少要取的表达出来,然后直接贪心取一下就好了;

CF1606E

这个题目挺难的,又是没有做出来的一道题,但是不妨碍我们看下题解是怎么做的,首先我的做法是构造出了一种序列,然后统计序列个数,DP写对了,但是方法是错的,因为我想出来的那种序列就是错的,而正确序列所具有的特征难以概括,就序列而言,它所包含的信息太多了,所以我们采用别的方法。首先这题比较明显的结构性特征是每次一回合就代表着一个阶段,然后我们分别枚举人数和已经扣除的血量,然后组合数操一下就好了,这就是本题的一个子结构,不能说最优,因为没有涉及到max或者是min;

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 510 , mod = 998244353;
int n, x;
int c[N][N], f[N][N];
signed main()
{
	scanf("%lld%lld",&n,&x);
	for(int i=0;i<=n;i++) {
		c[i][0] = c[i][i] = 1;
		for(int j=1;j<i;j++) 
			c[i][j] = (c[i-1][j] + c[i-1][j-1] ) % mod;
	}
	f[n][0] = 1;
	for(int i=n;i>1;i--) {
		for(int j=0;j<x;j++) {
			if(!f[i][j]) continue;
			int pw = 1, nj = min(x,j+i-1);
			for(int k=i;k>=0;k--) {
				f[k][nj] = (f[k][nj] + f[i][j] * c[i][k] % mod *pw % mod) % mod;
				pw = pw * (nj-j) % mod;
			}
		}
	}
	int ans = 0;
	for(int i=0;i<=x;i++) {
		ans = (ans + f[0][i] ) % mod;
	}
	printf("%lld\n",ans);
	return 0;
}

CF792C

听说两千分,可是做起来感觉没有两千分,其实很简单的,是3的倍数看和就可以了,然后每进一位的话看下能不能更新就行了。水的一比;

HHHOJ713

吐了,这题挺简单的,就是一个排序不等式,我们做的话就直接遵照排序不等式草一下就好了;
但是不知道为什么unsigned long long来代替long long会挂分,还他妈挂了70。
以后不能瞎勾巴乱写了;

HHHOJ715

这题真的挺可惜的,正确思路大部分都想到了,因为一个细节,代码结构也不一样了,因此最后只有那么几分暴力分。这题的一个模型很值得我们去探讨,我们发现乘法是通用的,就是说,你的那个乘法,不管乘在谁的头上都是一样的,因此我们分开处理,然后第一个那个增加到x的,纯粹就是拿来ex人的,完全可以当做加法操作,最后我们发现这些操作都能够回归乘法,这个过程我们不妨将其描述为一个找特征值的过程,“特征值”就是我们为一种操作或者是某个元素设计出来的一种值,可以纯粹的根据这个值上所含有的一些信息来进行排序,从而进行最优化的选择,特征值的类型难就难在怎么设计这个特征值像这题还是稍微简单一点的,因为乘法很显然是特征值。
那么这道题目为什么可以这么做我认为,其特征就在于给出的信息本质上是相同的,而且转化极其方便。
然后关于加法转化为特征值,我就是这里疏忽了,所以没草出来,原因是缺乏书面的数学推导与证明我们发现加法先加和后加,产生的效果是不一样的,前面我们说到特征值转化为一个乘数,然后推导一下乘数是怎样的,不能像我一样没推瞎想然后想错了,然后再用一下排序不等式发现规律,然后再草一下就能出答案了,非常明确的步骤;
虽然没有写出来,但是能做到这种好题还是很爽的;

HHHOJ716

虽然我写不出来这题的代码,但是看一下题解,这个模型抽象的还是很不错的,一道外表是计算几何的题目就被抽象成了一个数据结构题;非常妙;为什么说可以看成是一个区间呢,因为斜率,一个条过定点的直线和圆相交,斜率就那么一段,可以用区间来表示,非常巧妙;

P6569

伞兵卡常题,我TM。
注意到矩阵乘法,但是问题太多了直接快速幂会爆炸,而因为问题里面的答案我们都可以通过相似的信息处理步骤得出,于是我们考虑一下预处理,在采用二进制拆分的方法代替快速幂。
信息的相似性。
关于矩阵乘法的意义,我认为第二篇题解讲的非常好。

P3522

一道单调队列模型的题目,个人认为和CSP2021T3相似,但是简单了很多,但是我用了个什么狗屁做法写挂了,真的是他妈的,模型掌握还是不够透彻;
我很讨厌题解里面写出这种话,写出来专门给别人看的那种题解;

很显然,这一段的每一天的最高温度都必须大于这一天之前的最低温度最大值。

你显然你mb呢显然;
不过想一想我们先发现这个结论还是对的;
关键在于这一天之前的,我的做法把之后的也搞进去了,所以结果是很不对的;
做这种题还是要多思考一下;

P7530

这题重磅讲解,这是我第一道,自己想出正解的,用自己的理论指导而做出来的题目,首先我们发现这个题目里面很重要的一个点是那些数字出现的次数,于是我们马上想到莫队这一个非常巧妙的方法,莫队最好维护的信息的特点是,广泛性和难以合并性,广泛性就是指每个数据都有相关的信息,并且这个信息不能通过简单运算得出,而是与位置关系有关的;
莫队迅速解决40分的内容;
然后我们发现莫队为什么不能满分,因为这之间有信息大量的浪费了;
那么我们考虑从左到右,每次把增加的区间的信息给加上去就好了;
这中间我想了好久才想到一种用线段树的做法。
但是我写不出来,太难写了,想不到好的写法,然后中间有些步骤意义复杂;
发现,这种说只出现一次的内容,我们可以采用记录他上一次出现的位置这种方法帮助解决;
想清楚了之后还是很有帮助的;
有一个很重要的点就是,想要完成从 O ( N 2 ) O(N^2) O(N2) O ( N l o g N ) O(NlogN) O(NlogN) 的转变,必须要从思维上进行一个转变,也就是说,不能用之前的信息计算方法,而是用信息变化方法,或者说是寄存方法,存下来接着用,一个信息用了就别捡回来再用一遍了;
我在做的时候排除掉了一个很sb的树状数组做法,因为这个做法难以实现信息寄存;
剩下的就是看题解了,题解讲的还是比较清楚的,代码写的非常好;

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 2e5 + 10;
int n , b[N];
struct segtree{
	int t[MAXN<<2],sz[MAXN<<2];
	//  answer     size
	int val[MAXN<<2],tag[MAXN<<2];
	//  real         lazytag
	void pushup(int p){t[p]=t[p<<1]+t[p<<1|1];sz[p]=sz[p<<1]+sz[p<<1|1];}
	void pushdown(int p){
		if(!tag[p])return;
		val[p<<1]+=tag[p];val[p<<1|1]+=tag[p];
		t[p<<1]+=tag[p]*sz[p<<1];t[p<<1|1]+=tag[p]*sz[p<<1|1];
		tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];
		tag[p]=0;
	}
	void update(int p,int l,int r,int pos,int sgn) {
		if(l == r) {sz[p] += sgn; t[p] = sz[p] *val[p]; return;} 
		pushdown(p); int mid = (l + r) >> 1;
		if(pos <= mid) update(p<<1,l,mid,pos,sgn);
		else update(p<<1|1,mid+1,r,pos,sgn);
		pushup(p); 
	}
	void modify(int p,int l,int r, int L,int R, int sgn) {
		if(L > R) return;
		if(L <= l && r <= R) {
			tag[p] += sgn; val[p] += sgn;
			t[p] += sgn*sz[p];	return ;
		}pushdown(p); 	int mid = (l +r ) >> 1;
		if(L <= mid) modify(p<<1,l,mid,L,R,sgn);
		if(R > mid)  modify(p<<1|1,mid+1,r,L,R,sgn);
		pushup(p); 
	}
	int query(int p,int l,int r,int L,int R ) {
		if(L > R) return 0;
		if(L <= l&& r <= R) return t[p];
		pushdown(p);	int mid = (l + r) >> 1;
		if(R <= mid) return query(p << 1, l, mid, L, R);
		if(L > mid) return query(p<<1|1,mid+1,r,L,R);
		return query(p<<1,l,mid,L,R) + query(p<<1|1,mid+1,r,L,R);
	}
}T;
int pre[N], ppre[N], ans;
signed main() 
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
	for(int i=1;i<=n;i++) {
		int now = b[i];
		if(pre[now]) T.update(1,1,n,pre[now],-1);
		if(pre[now]) T.update(1,1,n,ppre[now] + 1,pre[now]- 1, - 1);
		ans += T.query(1,1,n,pre[now]+1,i-1);
		T.modify(1,1,n,pre[now] + 1,i-1,1); T.update(1,1,n,i,1);
		ppre[now] = pre[now]; pre[now] = i;
	}printf("%lld\n",ans);
}

好在一个点就是他用了一个专门的upd函数来维护每个节点的size;
真的是好久没有做到过的一道好题了;

P6187

NOI online 的一道题目,还是非常好的;
看到这个相乘,我便觉得这题可能会跟排序不等式有关系,然后我们将具体的内容来进行展开,然后经过一系列的数学操作,我们就可以发现这真 T M TM TM 的是一个排序不等式,然后我们将其草一下发现排列规律,然后就可以直接构造。但是,构造是 O ( N ) O(N) O(N) 的,而询问很多,考虑有几种可能的答案,我们发现只会有一个数的约数个数种,计算发现 1 0 5 10^5 105 之内的约数个数最多的数一共有 128 128 128 个约数,然后我们就可以直接预处理出来,询问虽然有很多种,但是我们所需要的那个询问,能给我们的有用的信息就只有这么几种。
这题里面包含了一个很巧妙的模型,就是有关最小公倍数和最大公约数的模型,是有关鸽巢原理和哈希的;
这个模型可以用一个命题来很好的概括;
对于任意的两个正整数 a a a b b b ,令 g c d ( a , b ) = d gcd(a,b) = d gcd(a,b)=d,令 k k k < = <= <= l c m ( a , b ) / d lcm(a,b) / d lcm(a,b)/d,且 k k k 为正整数,则 k ∗ a k*a ka m o d mod mod b b b = = = s s s s < = a s <= a s<=a s s s d d d 的倍数,且对于每个符合条件的 s s s 只会出现一次。
证明就是使用鸽巢原理,反证法证明一下就好了;
这个模型之所以被广泛拿来出题,我想是因为哈希的原因;

P6185

这原本是一道来自CF的伞兵题目,我 T M TM TM 还做过一次,这都能忘掉,我真是个伞兵;
不过里面的模型还是很值得抽象分析一下的;
第一步是将 a a a 序列和 b b b 序列进行一个作差,为什么,因为你之后要多次用到这个值,用到的次数远远大于信息的总数,也就是说每个信息被用到了不止一次,所以直接进行一次预处理出来就好了;
首先就是 2 2 2 操作,这个操作其实很简单,你可以理解为,这个操作可以将你所有的val都赶到一个点上面去,为什么可以这么理解,这个点我想了好久才想到,这不应该啊,就是说,val的传递其实就是几个加一和减一的操作叠加而成的,而你这两个操作本质上都是一样的所以可以这么理解,所以我们先把2边拿来使用,给他全部都连上去。 然后就是剩下1操作了,对于1操作,我们分为二分图和非二分图,很显然的,二分图的话,两边的差是不会变的,非二分图的话,总和的奇偶性是不会变的;
证明,二分图的话,你可以通过一种方式,使一边全部变成0,那么剩下一边就那样,该怎样怎样。非二分图,因为他联通,他可以先变成上一种方式,然后一边增加或减少,通过另一边传导回这一边,所以总和奇偶性不变;
然后就可以了;
有个方法还是很不错的,将两个操作分开来看,根据他们不同的信息本质。

P7469

今年三月份做的一道阴间题目,那时候刚学题面都看不太懂;
有个类 O ( N 2 ) O(N^2) O(N2) 的做法,为什么说类,数据随机的时候可以乱杀,第一点的构造数据直接变成几何级数。就是枚举下面一串的子段,然后对应上面,上面做一个链表一样的结构然后再搞;结合第一点的话大概会有60分以上的分数;
upd:
题目看错了,其实就是这样做的,或者说这样做描述的不是很清楚;
这位同学给出了一个很阳间的题解,可惜我没看懂;
你用一个很奇怪的结构就能搞出来了;
upd:
以上是我做这题之前的一个想法,写完了发现根本调不出来,我是真的在想屁吃;
最后还是得看题解来解决;
我们之前的做法一个很大的问题在于用 u n o r d e r e d unordered unordered − - m a p map map 给搞出来,但是这样子的话会M;所以考虑一个 l o n g l o n g long long longlong 的哈希;
然后枚举一下第二个字符串里面的子串,只需要 O ( N 2 ) O(N^2) O(N2) 的时间,同时维护一个指针指向第一个串里面的字符;然后把所有的哈希函数值给排序再去重即可,不愧是银牌选手的思路,非常简单清晰,很符合电脑运行的特性,而不是像我一样很生硬的做法;
真的非常值得借鉴;

#include<bits/stdc++.h>
using namespace std;
const int N = 3005, BASE = 51971;
const long long M = 2005091020050911;
int n;
char a[N] , b[N];
long long t[N*N];
int main() 
{
	scanf("%d %s %s",&n,a+1,b+1);
	for(int i=1;i<=n;i++) {
		long long v = 0;
		int p = 1;
		for(int j=i;j<=n;j++) {
			while(p <= n && a[p] != b[j]) p ++ ;
			if(p > n) break;
			p ++ ;
			v = (1ll * v * BASE + b[j] - 'a' + 1) % M;
			t[++t[0]] = v;
		}
	}
	sort (t+1,t+ t[0] + 1);
	printf("%d\n",unique(t+1,t+1+t[0]) - t - 1);
	return 0;
}

这题还有一个点,我记得在义乌集训的时候出现过一道类似的题目,关于枚举,你枚举出来的东西,两个不同的字符串里,他们的数量级是不一样的;在第一个子串里面就是 2 x 2^x 2x 的数量级,第二个子串里面搞出来之后是 x 2 x^2 x2 的数量级,考虑到我们要进行一个匹配;如果我们采取暴力枚举的方法,总要有一个是枚举出来的,如果我们枚举第一个,那么在2中枚举的复杂度与1不同,会添加额外的复杂度,因为2的约束条件多;而我们枚举一下2的子段,就会发现我们在1中枚举的时候相对不用进行那么多的信息筛选,因而可以做到不添加额外的复杂度;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值