3月份题解截至3.15

https://codeforces.com/contest/1855/problem/C2

很好的一道题

注意到操作次数有限 我们先把题目放缩下 假设没有负数 全是正数 我们怎么搞
很显然最多19次操作即可 那么31-19=12 也就是说12次我们就要把他全转为负数
这里我们需要思考 全转为正数也可以全为负数然后再利用19次也是一样的

12次要让数列正负一样 我们该怎么办 观察到n最多20 我们可以分情况讨论

如果正数已经有12个那么最多负数只有8个 此时如果maxn正数大于最大负数绝对值
我们8次即可搞定 那如果小于呢?怎么办

很显然 我们可以花费12的代价全转为负数即可

那好如果负数有12个 正数有8个怎么搞 如果此时maxn正还是大于负数 怎么办
还是一样负数全变为正数
小于的话 同理
所以只要最多的一类<=12其实我们都可以这么操作

那么如果说
我maxn正大于负数 但是正数的个数非常少 假设只有1个 负数19个那么 12次操作是不够的呀
那怎么办呢 这个是本题的重难点
此时应该想到 我们可以把正数转化为负数 那么我们可以让最大负数变得更大 1 2 4 6 16 32 的变
那么最多5次操作就可以了 所以临界情况就是正数cnt<=7 那么此时我们就该这么操作了
同理负数碰到也是
代码就不放了 比较长

https://codeforces.com/contest/1853/problem/B


有点逆天了 上次做出来了(不过思考的时间更长)这次简单思考了没想出来。。
讲下我当时的思路
设第一个数是A 第二个是B 第三个则是A+B A+2B 然后统计出最后一项的系数然后枚举
如果能%为0 ans++
计算最后一项系数

	for (int i = 3; i <= m; i++) {
		newx = x + xx;
		x = xx;
		xx = newx;
		newy = y + yy;
		y = yy;
		yy = newy;
	}
	int c = n / (newx + newy);最后一项除了系数的最大值 
	for (int i = c; i >= 0; i--) {
		if ((n - newx * i) % newy == 0)ans++;
	}枚举答案 

https://codeforces.com/contest/1853/problem/C

这是一道思维量很足的题
考虑到直接模拟是不现实的 我们可以想到反正是求最小的元素 从删除改为添加
每次添加在1的前面 删几个加几个
如果a1不是1 那么直接输出 1即可

然后我们开始删除 以我自己弄的样例 1 5 为例子

第一天删除1 留下了 2 实际上相当于1前补了一个数字 1+1=2
然后来到第二天注意到 此时应该删除2 然后第三天怎么处理1 3 中3删除的数字呢

这里面我们就要想到ai和i的关系了 如果说此时我们1的位置可以来到ai-i那么说明我们此时
要开始带上ai删除的数字了 把样例模拟下就能想清楚这个过程了
比如第四天结束时候下一个其实是4(还未输出) a2 =5 那么 我此时来到第四天
删除了4后其实5其实也早就删除了 不过我此时才可以考虑5的删除之前都不能 如果k比较小的话

然后45这次一块删除 下一个就是6没删除了 如果k满足的话下一次 就删除67留8 这个过程是很神奇的
我离做出这种题还差一个境界我感觉 其实ai-i就可以把等差数列转化为相等 这是一个trick 不过和这个题其实没啥联系 因为你下一次如果能删ai这个数 那么你此时临界情况就是说
假设你此时是sum表示1这次来到的数字 比如到6了 但是6还是没有删除的然后 但1-5已经没了
那么你如果-1(6没删除)可以等于8-3(假设样例 1 3 8 )证明你刚好到了要删除8的地步
下一次你就可以删除3个数字了 至少删除ai-i个数字了 下一次才能删到你 因为下一次删除i个数字
刚好到i
1 2 3 4 5 6 7 8 9 10 11 12 13

2 4 5 6 7 9 10 11 12 13 第一次完毕 sum=2 3-2=1 此时一次删了2个数字

4 6 7 9 10 12 13 14 第二次完毕 此时sum=4

6 9 10 12 13 15 16 17第三次结束 sum=6

9 12 13 15 16 18 19 20 第四次结束 sum=9 此时一次删除3个

这题就完毕了 希望下一次能秒出

https://codeforces.com/contest/1848/problem/C

重新做了下这个题目 这个题 是一个辗转相除法
然后关键是求每一对aibi 到gcd的步骤数
然后求到了其实是这样的变化的 (0,gcd) (gcd,gcd) (gcd,0) (0,gcd) 就是三次三次一个循环

也就是说你三次求到gcd和我九次最终一样的 都可以变成0 a这样

关键是求步骤
假设a<b 则设成
a ka+p 这样 然后开始变化

	 a ka+p
		 ka+p (k-1)a+p 1
		(k-1)a+p   a  	2
		 a a(k-2)+p    3

三次k-2 于是我们可以发现对k取模2 即可 大大降低复杂度 也不影响答案 k=1的话 可以花两步
(a,a+p)→(a+p,p)→(p,a)
为0直接处理a p

a>b的话 就k=1 b+p,b)→(b,p)一步就行 否则直接b p 处理

最终就做完了 特判全是0!

int cal(int x, int y) {

	if (x < y) {
		if (x == 0)return 0;
		int k = y / x;
		int temp=k/2;
		int kk = k % 2;
		int rem = y % x;
		/*
		 a ka+p
		 ka+p (k-1)a+p 1
		(k-1)a+p   a  	2
		 a a(k-2)+p    3
		*/
		if (kk == 1)
//		return cal(x,x+p);
		return 3*temp +2+ cal(rem, x);
		else return 3*temp +cal(x, rem) ;
	} else {
		if (y == 0)return 1;
		int k = x / y;
		int temp=k/2;
		int kk = k % 2;
		int rem = x % y;
		if (kk == 1)return 3*temp+1+cal(y, rem);
		else return 3*temp+cal(rem, y);
	}
}
	set<int>st;
	for (int i = 1; i <= n; i++) {
		if (!a[i] && !b[i])continue;
		int  w = cal(a[i], b[i]);
//		cout << w << endl;
		st.insert((cal(a[i], b[i])) % 3);
	}
	if (st.size() == 1||st.size()==0) {
		cout << "YES" << endl;
	} else {
		cout << "NO" << endl;
	}

https://codeforces.com/contest/1848/problem/B

直接简单说下题意 看翻译是看不懂的

就是说 一座桥 有很多个颜色板子铺成 你过桥只能选相同颜色过 然后两块桥的间距要计算存起来
问你最终过桥的间距最大值最小是多少 因为你有这么多个颜色 每个颜色都有一个最大值间距 问你最小
然后你可以有一次操作把一块木板颜色改了 说白了就是间距/2

然后这题就是一个简单的预处理 想讲的是关于set的使用

	map<int, int>ma;
	for (int i = 1; i <= n; i++) {
		s[a[i]].insert(i - ma[a[i]] - 1);
		ma[a[i]] = i;
	}
	map<int, bool>maa;
	for (int i = k; i >= 1; i--) {
		s[i].insert((n + 1) - ma[i] - 1);
	}

接下来 我们要知道 s的最大值 int tempp = s[i].rbegin();
如果我们还要知道次大值 因为可能次大值比最大值除2还大 这个次大值 不要写成
s[i].rbegin()-1;
这是错的!!!要这么写

	multiset<int>::reverse_iterator it=s[i].rbegin();
		it++;
	   temp = *it;		
		w = max(tempp / 2, temp);
		ans = min(ans, w);

千万别写成it-- 我们是反着的 比如倒数第一个 最大 那次大就是倒数第二个 是it++!!!!

multiset<int>::reverse_iterator it=s[i].rbegin(); 注意这里使用reverse_iterator!

https://codeforces.com/contest/1847/problem/C

可以发现其实本题就是求最大子段异或和
这题可以用01trie来写的 也可以用dp
01tire


int a[range];
int ch[range][2];//看值多大开多大
int idx;
int cnt[range];
int sum[range];
void insert(int x) {
	int p = 0;
	int res = 0;
	for (int i = 30; i >= 0; i--) {
		int u = (x >> i) & 1;
		if (!ch[p][u]) {
			ch[p][u] = ++idx;
		}
		p = ch[p][u];
		cnt[p] += 1;
	}
	return ;
}
int query(int x) {
	int p = 0;
	int res = 0;
	for (int i = 30; i >= 0; i--) {
		int u = (x >> i) & 1;
		if (cnt[ch[p][!u]]) {
			res += 1 << i;
			p = ch[p][!u];
		} else p = ch[p][u];
	}
	return res;
}
void solve(int t ) { //多测
	cin >> n ;
	//
	for (int i = 1; i <= n; i++)cin >> a[i];
	for (int i = 1; i <= n; i++) {
		sum[i] = sum[i - 1] ^ a[i];
	}
	idx = 0;
	int ans = 0;
	insert(0);
	for (int i = 1; i <= n; i++) {
		insert(sum[i]);
		ans = max(ans, query(sum[i]));
	}
//	debug
//	for(int i=1;i<=10;i++){
//		cout<<ch[i][0]<<" "<<ch[i][1]<<endl;
//	}
//	cout<<ch[0][0]<<" "<<ch[0][1]<<endl;
//	cout<<cnt[0]<<endl;
//	cout<<sum[0]<<endl;
	cout << ans << endl;
//	for (int i = 1; i <= 2e4; i++) {
//		ch[i][0] = ch[i][1] = 0;
//		cnt[i] = 0;
//		sum[i]=0;
//	}
	for (int i = 0; i <= 2e4; i++) {
		ch[i][0] = ch[i][1] = 0;
		cnt[i] = 0;
		sum[i]=0;
	}
	
}

介绍下dp写法 其实就是一个背包 不过这里可以优化成滚动 不过数据都是放过的

for (int i = 1; i <= n; i++) {
	for (int j = 0; j <= (1 << 8); j++) {
		dp[i%2][j]=0;
	}
	for (int j = 0; j <= (1 << 8); j++) {

		if (dp[(i - 1) % 2][j ^ a[i]]) {
			dp[i % 2][j] |= dp[(i - 1) % 2][j^a[i]];
			ans = max(ans, j);
		}
	}
	dp[i % 2][a[i]] = 1;
	ans = max(ans, a[i]);
}
滚动vector
	for (int i = 1; i <= n; i++) {				
		int siz=v[(i-1)%2].size();
		for(int j=1;j<=siz;j++)
		{	
		if(!dp[i%2][v[(i-1)%2][j-1]^a[i]])
		v[i%2].push_back(v[(i-1)%2][j-1]^a[i]);	
		dp[i%2][v[(i-1)%2][j-1]^a[i]]|=dp[(i - 1) % 2][v[(i-1)%2][j-1]];
		ans=max(ans,v[(i-1)%2][j-1]^a[i]);
		dp[(i - 1) % 2][v[(i-1)%2][j-1]]=0;			
		}
		if(!dp[i%2][a[i]])
		v[i%2].push_back(a[i]);
		ans=max(ans,a[i]);
		dp[i%2][a[i]]=1;
		v[(i-1)%2].clear();
	}

https://codeforces.com/contest/1834/problem/C

这个题操作次数就不讲了 仔细想想蛮简单的

想讲的是一个让我a不了的点
我们令一个字符串A 一个字符串B
B翻转了 与A进行逐位比较 设为cnt2

然后A翻转了 B不翻转 然后比较 设为cnt3

cnt3是等于cnt2的 而不是不相等

我潜意思认为不等

为什么相等 其实 证明下
一开始是A[I]与B[n-i+1] 后面是A[n-i+1] B[i] 设计上 都是一样的 i取1前者1 n 后者i取n就能 1 n
比较了 所以最终cnt是一样的 切记!!!

https://codeforces.com/contest/1839/problem/C

an为1输出no 因为没有任何操作能使an=1
考虑倒着做 初始序列为空 我们可以进行一步步的操作从后往前填 某一位为0的话 我们直接补0即可 刚好是对应的 仔细模拟这一个过程即可体会

如果此位是1 我们设置一个计数器存储然后输出0 直到i-1为0时 输出cnt 表示 将此时的序列翻转 因为这一段序列后面的哪一个数字是0 插入0相当于弥补此次插1

	if(a[n]==1){
		cout<<"NO"<<endl;return ;
	}
	cout<<"YES"<<endl;
	int num=0;
	for(int i=n;i>=1;i--)
	{
		if(a[i]==0){
			cout<<0<<" ";
		}
		else {
			num++;
			if(a[i-1]==0){
				cout<<num<<" ";num=0;
			}
			else cout<<0<<" ";
		}
	}

https://www.luogu.com.cn/problem/CF1838C

此题容易想到
1 2 3 4
m+1...m+m+1 放是最好的 间距是1 行间距是m
如果m非质数那么 就可以了
如果m是质数的话那怎么办呢

可以联想到 我们对于行间距可以*2 就变成了
1 3 5 7 这样的了 然后不能超出 一旦超出了就
0 2 4 6 这样就肯定可以完全放完
而且也不是质数

这样做是最简单的写法 不过这边还有一个偏移坐标写法
当时昆明区域赛的一个签到题就可以用这个写法

先考虑m是偶数的情况下
1 2 3 这样就行了
4 5 6

n是偶数就需要

1 n+1 2n+1 ...(m-1)n+1
2 n+2 2n+2 ...(m-1)n+2
. . . ...
n 2n 3n nm

此时间距为n

如果n m 均为奇数 就需要对
1 2 3 这种数列进行循环移位 这是一个很常见的trick
4 5 6

    for(int i=1;i<=n;i++)
            {
                int op = (i-1) % m + 1;
                for(int j=op;j<=m;j++)
                {
                    cout<<((i-1) * m + j)<<" ";
                }
                for(int j=1;j<op;j++)
                {
                    cout<<((i-1) * m + j)<<" ";
                }
                cout<<'\n';
            }

https://codeforces.com/contest/1825/problem/A

用set去存 然后遍历一半 set内的数量就是答案

https://codeforces.com/contest/1825/problem/B

贪心的去想 我们可以让最大的放1 1 然后次小 小 在1 2 2 1
也可以让最小的放1 1 最大2 1 次大 1 2 我们默认n是最大的 那么很显然 最大的就应该放2 1
ans2=(nm-1)-1mini+maxn(n-1)m+cmaxn(m-1);eg

https://codeforces.com/contest/1825/problem/C


这个题很有意思 说下思路吧 我们贪心的肯定希望有确定位置的人能得到座位去坐

然后再分配 对于每一个人来说 拆点思考 分为左右两侧思考 我们先设所有有具体位置的人为cnt
那么对于一个数来说 他左侧可以放一些-1的和左侧具体位置的 右侧可以放右侧具体位置的和-2 然后要和剩下的位置取个min 别超了

	ans=cnt+max(num1,num2);
		ans=min(ans,m);
		int temp=0;
		int cntt=1;
		int siz=v.size();
		for(auto i:v)
		{
			temp=1;
			temp+=min(num1+cntt-1,i-1);
			temp+=min(num2+(siz-cntt),m-i);
			ans=max(ans,temp);
			cntt++;
		}

https://codeforces.com/contest/1826/problem/A

这题是一个很有意思的题目
如果实际说谎的人是x 那么小于x的都算对 那么我们该怎么统计呢 大于x的都是错的 设为cnt 如果cnt等于
x 那么就是对的 还挺绕的

	for (int i = 0; i <= n; i++) {
		int cnt=0;
		for(int j=n;j>=1;j--)
		{
			if(l[j]>i)
			cnt++;
		}
		if(i==cnt){
			cout<<cnt<<endl;return ;
		}
		
	}

https://codeforces.com/contest/1826/problem/D

重新做 简直是秒出
bi1+bi2+bi3−(r−l)
我们可以把他拆成 b1+l b2 b3-r 那么我们可以对每一个bi求出b1+i b1-i 然后拆点
哇 我都不知道多少次说拆点这个词语了 说明他真的很重要 于是 我们可以单调栈求出左右最大的那个
bi+i bi-i 那么取个max 即可

	int top = 0;
	for (int i = n; i >= 1; i--) {
		if (top)r[i] = stk[1];
		while (top && a[stk[top]] - stk[top] < a[i] - i) {
			top--;
		}
		stk[++top] = i;
	}
      for(int i=1;i<=n;i++)
		  stk[i]=0;
	  
	for (int i = 1; i <= n; i++) {
		if (top)l[i] = stk[1];
		while (top && a[stk[top]] + stk[top] < a[i] + i) {
			top--;
		}
		stk[++top] = i;
	}
	int maxn=0; 注意2-n-1
	for (int i = 2; i <= n-1; i++) {
		int ans=a[l[i]]+l[i]+a[r[i]]-r[i]+a[i];
		maxn=max(ans,maxn);
	}
	cout<<maxn<<endl;

https://codeforces.com/contest/1818/problem/C

这个题是一个拆点思考的 我们思考中间的那个y 我们什么时候删除他 就是当x y z 递减的时候就该删除y了
反正不可能留的 那么z能不能删就看后面的值了 这个贪心显然是正确的 于是我们可以将其转化为一个前缀和存储 然后直接输出

	pre[0] = 0;
	pre[1] = 0;
	for (int i = 2; i <= n - 1; i++) {
		bool flag = 0;
		if (a[i - 1] >= a[i] && a[i] >= a[i + 1])
			flag = 1;
		pre[i] = pre[i - 1] + flag;
	}
	pre[n] = pre[n - 1];
	for (int i = 1; i <= q; i++) {
		int l, r;
		cin >> l >> r;
		cout << r - l + 1 - max((pre[r - 1] - pre[l]), 0 * 1LL) << endl;
	}

https://codeforces.com/contest/1818/problem/B

这个题是猜出来的 不过还是说下为什么n是奇数就不说了包不行的
n是偶数还是可以的 因为 n*(n+1)/2 只会是2的倍数 不一定是4的倍数这样
首先不能取任何连续的三个数字 这是肯定的 三个连续的一定是散的倍数
那么同时两两不能被整除 这也是一定的 所以我们可以得出 一定是 3 4 1 2 这样放才行

	int cnt=n;
	for(int i=1;i<=n/2;i++)
	{
		cout<<cnt-1<<" "<<cnt<<" ";
		cnt--;
		cnt--;
	}
posted @ 2025-04-05 22:34  LteShuai  阅读(20)  评论(0)    收藏  举报