Codeforces Round 934 (Div. 2)(A,B,C,D)

没时间,等有时间了再看看能不能补掉D 3.28号更新,补上了D题。

感觉应该好像可能不难。最近全是div 2,而且难度好像还变大了,快打出ptsd了。

比赛链接

ABC都是思维题,想通了其实难度不大。


A Destroying Bridges

题意:

共有 n n n 个岛屿,编号为 1 , 2 , … , n 1, 2, \ldots, n 1,2,,n 。最初,每一对岛屿都由一座桥连接。因此,总共有 n ( n − 1 ) 2 \frac{n (n - 1)}{2} 2n(n1) 个桥。

Everule住在岛上 1 1 1 ,喜欢通过桥梁游览其他岛屿。主宰者拥有最多摧毁 k k k 座桥的能力,以最大限度地减少Everule使用(可能是多座)桥所能到达的岛屿数量。

找出在主宰者以最佳方式摧毁桥梁的情况下,Everule可以访问的岛屿(包括岛屿 1 1 1 )的最小数量。

思路:

拆桥策略显然是把与 1 1 1 号岛屿相连的桥先拆掉,这样可以防止 1 1 1 号岛屿的人跑出来。

因为与 1 1 1 号岛屿相连的桥一共就 n − 1 n-1 n1 座,所以如果拆桥次数大于等于 n − 1 n-1 n1,答案就是 1 1 1。否则就关不住 1 1 1 号岛屿的人,答案就是 n n n

code:

#include <iostream>
#include <cstdio>
using namespace std;

int T,n,k;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		if(k>=n-1)cout<<1<<endl;
		else cout<<n<<endl;
	}
	return 0;
} 

B. Equal XOR

题意:

给定一个长度为 2 n 2n 2n 的数组 a a a ,它由从 1 1 1 n n n 的每个整数正好出现两次

您还会得到一个整数 k k k ( 1 ≤ k ≤ ⌊ n 2 ⌋ 1 \leq k \leq \lfloor \frac{n}{2} \rfloor 1k2n )。

您需要找到两个长度分别为 2 k \mathbf{2k} 2k 的数组 l l l r r r ,以便:

  • l l l [ a 1 , a 2 , … a n ] [a_1, a_2, \ldots a_n] [a1,a2,an] 的子集 † ^\dagger

  • r r r [ a n + 1 , a n + 2 , … a 2 n ] [a_{n+1}, a_{n+2}, \ldots a_{2n}] [an+1,an+2,a2n] 的子集

  • l l l 数组的元素按位异或值等于 r r r 数组。换句话说, l 1 ⊕ l 2 ⊕ … ⊕ l 2 k = r 1 ⊕ r 2 ⊕ … ⊕ r 2 k l_1 \oplus l_2 \oplus \ldots \oplus l_{2k} = r_1 \oplus r_2 \oplus \ldots \oplus r_{2k} l1l2l2k=r1r2r2k

可以证明至少有一对 l l l r r r 总是存在的。

如果有多个解决方案,您可以输出其中的任何一个。

† ^\dagger 序列 x x x 是序列 y y y 的子集,那么 x x x 可以通过删除几个(可能没有或全部)得到 y y y 的元素,并以任意顺序重新排列这些元素。例如, [ 3 , 1 , 2 , 1 ] [3,1,2,1] [3,1,2,1] [ 1 , 2 , 3 ] [1, 2, 3] [1,2,3] [ 1 , 1 ] [1, 1] [1,1] [ 3 , 2 ] [3, 2] [3,2] [ 1 , 1 , 2 , 3 ] [1, 1, 2, 3] [1,1,2,3] 的子集,但 [ 4 ] [4] [4] [ 2 , 2 ] [2, 2] [2,2] 不是 [ 1 , 1 , 2 , 3 ] [1, 1, 2, 3] [1,1,2,3] 的子集。

思路:

比较容易想到这种策略:如果可以的话,我们会比较想两边选同样的数。不过有可能每种数的两个被拆开放在了两边,因为相同的数异或值后是 0 0 0,那么我们一边就可以用两个同样的数来凑数,另一边同时选上另一对相同的数。不过不知道另一边会不会存在一对相同的数。

如果我们假设一种数的两个分开放在两边的种数有 x x x 个,那么就有 n − x n-x nx 种两个数都在一边,因为两边的位置是相同的,都是 n n n,所以可以保证两边一定有 x x x 个单个的数和 n − x 2 \dfrac{n-x}2 2nx 对数,所以一定存在这对相同的数。所以只要左边能选出来一种方案,右边也可以选出来对应的方案,左边一定有解,所以本体一定有解。

因为单个数的数量有限,所以我们左边先选一对一对选相同的数,剩下的都是单个的数,再去选单个的数。

code:

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
using namespace std;
const int maxn=1e5+5;

int T,n,k;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		map<int,int> mp;
		int t=2*k;
		vector<int> b,c;
		for(int i=1,x;i<=n;i++){
			cin>>x;
			mp[x]++;
			if(mp[x]==2){
				if(t>1){
					t-=2;
					b.push_back(x);
					b.push_back(x);
				}
				mp.erase(x);
			}
		}
		while(t){
			b.push_back(mp.begin()->first);
			c.push_back(mp.begin()->first);
			mp.erase(mp.begin());
			t--;
		}
		mp.clear();
		t=2*k;
		for(int i=n+1,x;i<=2*n;i++){
			cin>>x;
			mp[x]++;
			if(mp[x]==2){
				if(t>1){
					t-=2;
					c.push_back(x);
					c.push_back(x);
				}
				mp.erase(x);
			}
		}
		
		for(auto x:b)cout<<x<<" ";
		puts("");
		for(auto x:c)cout<<x<<" ";
		puts("");
	}
	return 0;
}

C. MEX Game 1

题意:

Alice和Bob在大小为 n n n 的数组 a a a 上玩另一个游戏。Alice从空数组 c c c 开始。两个玩家轮流玩,爱丽丝先开始。轮到Alice时,她从 a a a 中选择一个元素,将该元素附加到 c c c ,然后从 a a a 中删除该元素。

轮到Bob时,他从 a a a 中选择一个元素,然后从 a a a 中删除该元素。

当数组 a a a 为空时,游戏结束。

游戏的得分定义为 c c c M E X MEX MEX † ^\dagger 。Alice想要最大化分数,而Bob想要最小化分数。如果两个玩家都发挥最佳,则找出游戏的最终得分。

† ^\dagger MEX ⁡ \operatorname{MEX} MEX (minimum excludant)

整数数组的 M E X MEX MEX 定义为不出现在数组中的最小非负整数。例如:

  • [ 2 , 2 , 1 ] [2,2,1] [2,2,1] M E X MEX MEX 0 0 0 ,因为 0 0 0 不属于数组。

  • [ 3 , 1 , 0 , 1 ] [3,1,0,1] [3,1,0,1] M E X MEX MEX 2 2 2 ,因为 0 0 0 1 1 1 属于数组,而 2 2 2 不属于数组。

  • [ 0 , 3 , 1 , 2 ] [0,3,1,2] [0,3,1,2] M E X MEX MEX 4 4 4 ,因为 0 0 0 1 1 1 2 2 2 3 3 3 属于数组,而 4 4 4 不属于数组。

思路:

直接想Alice的策略很困难,我们可以考虑从Bob入手,考虑到Bob想要让MEX值最小,所以只要他能让取出来的数出现断层就可以了,这样Alice无论之后选什么,答案都不会变大了。

显然如果一开始就有断层,那么答案一定不会大于它。如果某个小数只有一个,而且没有被选进 c c c 数组的话,Bob选走它就会出现断层。而如果某个小数有多个,Bob如果选它,并且这个数就剩一个了,还没被选入 c c c 数组,那么Alice接下来一定会选它,防止出现断层。这样Alice一定不会亏,Bob一定亏了,因为他花了回合数,却没能阻止答案进一步变大,因此这时Bob一定不会选它。

回到Alice视角,因此Alice可以留着所有出现多次的数,然后抢在Bob之前把最小的只出现了一次的数拿到手,之后Bob会拿走次大的只出现了一次的数,这时候答案就死次大的只出现了一次的数与本来的 M E X MEX MEX 值取较小值即可。

code:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

int T,n;

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		map<int,int> mp;
		for(int i=1,x;i<=n;i++){
			cin>>x;
			mp[x]++;
		}
		for(int i=0,c=0;;i++){
			if(mp[i]==0){
				cout<<i<<endl;
				break;
			}
			else if(mp[i]==1){
				c++;
				if(c>1){
					cout<<i<<endl;
					break;
				}
			}
		}
	}
	return 0;
}

D. Non-Palindromic Substring

题意:

如果至少存在一个长度为 k k k 的子串 † ^\dagger 不是回文 ‡ ^\ddagger ,则称字符串 t t t k − g o o d k-good kgood 的。让 f ( t ) f(t) f(t) 表示所有 k k k 的值之和,使得字符串 t t t k − g o o d k-good kgood 的。

给你一个长度为 n n n 的字符串 s s s 。您需要回答以下问题中的 q q q 个:

  • 给定 l l l r r r ( l < r l \lt r l<r ), 求 f ( s l s l + 1 … s r ) f(s_ls_{l + 1}\ldots s_r) f(slsl+1sr) 的值。

† ^\dagger 字符串 z z z 的子串是来自 z z z 的连续字符段。例如,“ d e f o r \mathtt{defor} defor” 、 “ c o d e \mathtt{code} code” 和 “ o \mathtt{o} o” 都是 “ c o d e f o r c e s \mathtt{codeforces} codeforces” 的子串,而 “ c o d e s \mathtt{codes} codes” 和 “ a a a \mathtt{aaa} aaa” 不是。

‡ ^\ddagger 回文字符串是指前后读法相同的字符串。例如,字符串 “ z \texttt{z} z”、 “ aa \texttt{aa} aa” 和 “ tacocat \texttt{tacocat} tacocat” 是回文字符串,而 “ codeforces \texttt{codeforces} codeforces” 和 “ ab \texttt{ab} ab” 不是。

思路:

借鉴的这个大佬的

如果一个串不是 k − g o o d k-good kgood 的,那么这个串的所有长度 = k =k =k 子串一定都是回文的。我们手玩一下看看能找到什么性质。

假设 k = 5 k=5 k=5 的话,那么前五个字符是回文的,第五个字符对应着第一个字符。当向后移动一位时,第五个字符对应着第三个字符,图示如下:
a ˙ a a a a ˙ ‾ a a a a \underline{\dot aaaa\dot a}aaaa a˙aaaa˙aaaa a a a ˙ a a ˙ a ‾ a a a a\underline{a\dot aa\dot aa}aaa aaa˙aa˙aaaa如果 k = 7 k=7 k=7 的话,那么第七个字符对应着第一个字符,当向后移动一位时,第七个字符对应着第三个字符,再向后移动一位,第七个字符对应着第五个字符,类推,图示如下:
a ˙ a a a a a a ˙ ‾ a a \underline{\dot aaaaaa\dot a}aa a˙aaaaaa˙aa a a a ˙ a a a a ˙ a ‾ a a\underline{a\dot aaaa\dot aa}a aaa˙aaaa˙aa a a a a a ˙ a a ˙ a a ‾ aa\underline{aa\dot aa\dot aaa} aaaaa˙aa˙aa

我们发现某个奇数位置上的字符会和所有奇数位置上的字符对应一遍,同理偶数位置上的字符会合所有偶数位置上的字符对应一遍,这意味着所有奇数位置上的字符完全相同,偶数位置上的字符完全相同。而这种对应只要满足 k k k 是奇数,并且(下划线)可以向后移动 就可以了。

再研究 k k k 为偶数的情况,不难发现。某个奇数位置上的字符会和所有偶数位置上的字符对应一遍,同理偶数位置上的字符会合所有奇数位置上的字符对应一遍,这意味着所有奇数位置上的字符与偶数位置字符完全相同,偶数位置上的字符与奇数位置上的完全相同,也就是整个串只由一种字符组成。同理,这种对应只要满足 k k k 是偶数,并且(下划线)可以向后移动 就可以了。

因为我们上面的对应需要在多个子串同时回文的前提下(也就是下划线可以向后移动)才能保证奇数位置和偶数位置上的字符有这种关系。而回文的判定不能向后移动时只有可能 k k k 等于区间长度时才会发生,所以当 k k k 等于区间长度时,只存在一个子串,子串也就不存在什么奇数位置和偶数位置上的字符的关系。这时只要整个串为回文串就不是 k − g o o d k-good kgood 的了。

另外因为长为 1 1 1 的子串一定是回文的,所以不存在 1 − g o o d 1-good 1good 的。

综合上面的讨论,可以得到:

  1. 如果一个串全由一个字符组成,那么结果是 0 0 0
  2. 如果一个串奇数位置上的字符两两相同,偶数位置字符相同,那么结果就是 2 + 4 + ⋯ + ( l e n − l e n % 2 ) 2+4+\dots+(len-len\%2) 2+4++(lenlen%2)
  3. 如果一个串是回文串,则额外减去 k = l e n k=len k=len 的情况。
  4. 什么都不是的串的结果是 2 + 3 + ⋯ + l e n = ( l e n + 2 ) ∗ ( l e n − 1 ) 2 2+3+\dots+len=\dfrac{(len+2)*(len-1)}2 2+3++len=2(len+2)(len1)

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int maxn=2e5+5;

int T,n,q;
string s;
int l1[maxn],l2[maxn];

vector<int> manacher(string s){
	int n=s.length(),nn=2*n+1;
	string ss="#";
	for(auto c:s){
		ss+=c;
		ss+="#";
	}
	vector<int> p(nn);
	
	for(int i=0,id=0,r=0,len;i<nn;i++){
		len=(r>i)?min(r-i,p[2*id-i]):1;
		while(i-len>=0 && i+len<nn && ss[i-len]==ss[i+len])len++;
		if(i+len>r){
			r=i+len;
			id=i;
		}
		p[i]=len;
	}
	
	return p;
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>q>>s;
		vector<int> p=manacher(s);
		for(int i=n-1;i>=0;i--){
			if(i+1<n && s[i]==s[i+1])l1[i]=l1[i+1];
			else l1[i]=i+1;
			
			if(i+2<n && s[i]==s[i+2])l2[i]=l2[i+2];
			else l2[i]=i+2;
		}
		
		while(q--){
			int l,r,len;
			cin>>l>>r;
			l--;
			len=r-l;
			if(l1[l]>=r)cout<<0<<endl;
			else if(l2[l]>=r && l2[l+1]>=r){
				len-=len%2;
				cout<<1ll*len*(len+2)/4<<endl;
			}
			else {
				if(p[l+r]-1>=len)len--;
				cout<<1ll*(len+2)*(len-1)/2<<endl;
			}
		}
	}
	return 0;
} 
  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值