Codeforces Round #741 (Div. 2) CF1562

这篇博客探讨了数学思维在编程问题解决中的重要性,包括分类讨论、暴力求解、构造法等策略。文章通过实例展示了如何利用这些方法解决二进制字符串处理、前缀和计算以及最长上升子序列等问题,强调了数学在信息技术领域的核心地位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A.思维

是一个小思维题

我们知道 2 b − 1   m o d   b = b − 1 2b-1\ mod\ b=b-1 2b1 mod b=b1

这也是最大的

因此,对于 [ l , r ] [l,r] [l,r] 我们找到 b b b使得 2 b − 1 2b-1 2b1 r r r 或者为 r − 1 r-1 r1

但是当 b < l b<l b<l时,我们便只能取 l l l

#include <bits/stdc++.h>
using namespace std;
 
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int l,r;
        scanf("%d%d",&l,&r);
        int wtl=r%2?(r+1)/2:r/2+1;
        if(l<=wtl){
            printf("%d\n",r%wtl);
        }else{
            printf("%d\n",r%l);
        }
    }
    return 0;
}

B.分类讨论+暴力

首先我们可以确定的一点是:如果所给的字符串中含有非素的个位数,那么我们直接输出这个个位数即可

其次,如果,含有某一相同的数字出现两次,那么我们就输出本数字两个即可

好的,去除了上面的情况我们可以发现,剩余的情况就是

所给字符串是由 2 , 3 , 5 , 7 2,3,5,7 2,3,5,7构成,且每一个数字最多只出现了一次

我们 d f s dfs dfs找到所有的可能组成的数字,然后逐一检查即可

#include<bits/stdc++.h>
using namespace std;
int a[55];
int n;
vector<int> vec;
void dfs(int pos,int num)
{
	if (pos>n)
	{
		if (num!=0)vec.push_back(num);
		return ;
	}
	dfs(pos+1,num*10+a[pos]);
	dfs(pos+1,num);
}
int getlen(int num)
{
	int ans=0;
	while (num)
	{
		++ans;
		num/=10;
	}return ans;
}
bool check(int num)
{
	if (num==1)return false;
	for (int i=2;i<num;++i)if (num%i==0)return false;
	return true;
}
int cnt[10];
int main()
{
	ios::sync_with_stdio(0);
	int t;cin>>t;
	while(t--)
	{
		memset(cnt,0,sizeof(cnt));
		cin>>n;
		for (int i=1;i<=n;++i)
		{
			char ch;cin>>ch;
			a[i]=(int)(ch-'0');
		}
		bool f = false;
		for (int i=n;i>=1;--i)
		{
			cnt[a[i]]++;
			if (a[i]!=2&&a[i]!=5&&a[i]!=3&&a[i]!=7)
			{
				cout<<"1\n"<<a[i]<<"\n";
				f=true;
				break;
			}
		}if (f)continue;
		for (int i:{2,3,5,7})
		{
			if (cnt[i]>=2){
				cout<<"2\n"<<i<<i<<"\n";
				f=true;
				break;
			}
		}if (f)continue;
		vec.clear();
		dfs(1,0);
		sort(vec.begin(),vec.end());
		for (int num:vec)
		{
			if (!check(num))
			{
				cout<<getlen(num)<<"\n"<<num<<"\n";
				break;
			}
		}
	}
}

C.思维+构造

分类讨论

对于所给二进制字符 s s s 长度为 l e n len len

  1. 如果存在索引 i i i s [ i ] = = 0 s[i]==0 s[i]==0 i > ⌊ l e n 2 ⌋ i>\lfloor \frac{len}{2}\rfloor i>2len

    那么我们便可以选取 [ 1 , i ] [1,i] [1,i] [ 1 , i − 1 ] [1,i-1] [1,i1] 前者是后者的左移

  2. 否则一定对于 i > ⌊ l e n 2 ⌋ i>\lfloor \frac{len}{2}\rfloor i>2len s [ i ] = = 0 s[i]==0 s[i]==0

    1. s [ ⌊ l e n 2 ⌋ ] = = 0 s[\lfloor \frac{len}{2}\rfloor]==0 s[2len]==0: 选择 [ ⌊ l e n 2 ⌋ , l e n ] , [ ⌊ l e n 2 ⌋ + 1 , l e n ] [\lfloor \frac{len}{2}\rfloor,len],[\lfloor \frac{len}{2}\rfloor+1,len] [2len,len],[2len+1,len]
    2. s [ ⌊ l e n 2 ⌋ ] = = 1 s[\lfloor \frac{len}{2}\rfloor]==1 s[2len]==1:选择 [ ⌊ l e n 2 ⌋ , l e n − 1 ] , [ ⌊ l e n 2 ⌋ + 1 , l e n ] [\lfloor \frac{len}{2}\rfloor,len-1],[\lfloor \frac{len}{2}\rfloor+1,len] [2len,len1],[2len+1,len]
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e4+100;
int a[maxn];
int n;
int main()
{
	ios::sync_with_stdio(0);
	int t;cin>>t;
	while (t--)
	{
		cin>>n;
		for (int i=1;i<=n;++i)
		{
			char ch;cin>>ch;
			a[i]=(int)(ch-'0');
		}
		bool f = false;
		for (int i=n/2+1;i<=n;++i)
		{
			if (a[i]==0)
			{
				cout<<1<<" "<<i<<" "<<1<<" "<<i-1<<"\n";
				f=true;
				break;
			}
		}if (f)continue;
		if (a[n/2]==1)cout<<n/2<<" "<<n-1<<" "<<n/2+1<<" "<<n<<"\n";
		else cout<<n/2<<" "<<n<<" "<<n/2+1<<" "<<n<<"\n";
	}
}

D1.思维

不是很难

我们可以通过维护两个前缀和来求出所有 [ l , r ] [l,r] [l,r]区间的 1 , − 1 1,-1 1,1的和

p r e 1 pre1 pre1:偶数位置乘 − 1 -1 1

p r e 2 pre2 pre2:奇数位置乘 − 1 -1 1

那么对于 [ l , r ] [l,r] [l,r]

  1. l l l为奇数, p r e 1 [ r ] − p r e 1 [ l − 1 ] pre1[r]-pre1[l-1] pre1[r]pre1[l1]
  2. 否则: p r e 2 [ r ] − p r e 2 [ l − 1 ] pre2[r]-pre2[l-1] pre2[r]pre2[l1]

得知, [ l , r ] [l,r] [l,r]之后呢 记其为 s u m ( l , r ) sum(l,r) sum(l,r)

  1. a b s ( s u m ) abs(sum) abs(sum)为奇数答案为 1 1 1
  2. a b s ( s u m ) abs(sum) abs(sum)为偶数答案为 2 2 2

先来看情况 1 1 1 还是比较好理解的

我们找到 l ≤ i d ≤ r l\le id\le r lidr 使得 s u m ( l , i d ) = s u m ( l , r ) / 2 + 1 sum(l,id)=sum(l,r)/2+1 sum(l,id)=sum(l,r)/2+1 s u m ( l , i d − 1 ) = s u m ( l , r ) / 2 sum(l,id-1)=sum(l,r)/2 sum(l,id1)=sum(l,r)/2

那么 s u m ( i d + 1 , r ) = s u m ( l , r ) / 2 sum(id+1,r)=sum(l,r)/2 sum(id+1,r)=sum(l,r)/2

那么当我们抽走 i d id id时, s u m ( i d + 1 , r ) = − s u m ( l , r ) sum(id+1,r)=-sum(l,r) sum(id+1,r)=sum(l,r)

那么 s u m ( l , i d − 1 ) + s u m ( i d + 1 , r ) = 0 sum(l,id-1)+sum(id+1,r)=0 sum(l,id1)+sum(id+1,r)=0

至于偶数呢?

我们可以通过抽掉 l l l将其变为奇数的情况

通过操作,转化为其他的种类也是一种经典的套路

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+100;
int pre1[maxn],pre2[maxn];
int n,m;
int main()
{
	ios::sync_with_stdio(0);
	int t;cin>>t;
	while (t--)
	{
		int tmp=1;
		cin>>n>>m;
		for (int i=1;i<=n;++i)
		{
			char ch;cin>>ch;
			pre1[i] = tmp*(ch=='+'?1:-1);
			pre2[i]=-pre1[i];
			pre1[i]+=pre1[i-1];
			pre2[i]+=pre2[i-1];
			tmp=-tmp;
		}cout<<endl;
		while (m--)
		{
			int l,r;cin>>l>>r;
			int res=0;
			if (l&1)res=abs(pre1[r]-pre1[l-1]);
			else res=abs(pre2[r]-pre2[l-1]);
			if (res&1)cout<<1<<endl;
			else if (res==0)cout<<0<<endl;
			else cout<<2<<endl;
		}
	}
}

D2.二分

已经没有什么好说的了

关键就是如何找到应该要的索引了

记为函数 s o l v e ( l , r ) solve(l,r) solve(l,r)

找到 [ l , r ] [l,r] [l,r]中, i d id id s u m ( l , i d ) = s u m ( l , r ) / 2 + 1 sum(l,id)=sum(l,r)/2+1 sum(l,id)=sum(l,r)/2+1 s u m ( l , i d − 1 ) = s u m ( l , r ) / 2 sum(l,id-1)=sum(l,r)/2 sum(l,id1)=sum(l,r)/2

我们已知了 s u m ( l , i d ) sum(l,id) sum(l,id) 那么我们也就是知道了 p r e ( i d ) pre(id) pre(id)

因此,我们将每一个 p r e ( i ) pre(i) pre(i)的值开一个 v e c t o r vector vector

然后进 v e c t o r [ p r e ( i d ) ] vector[pre(id)] vector[pre(id)]中找第一个 > = l >=l >=l的作为 i d id id

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+100;
vector<int>G1[maxn<<1],G2[maxn<<1];
int pre1[maxn],pre2[maxn];
int a[maxn];
char s[maxn];
int n,q;
int solve(int l,int r)
{
	if (l&1)
	{
		int val = pre1[r]-pre1[l-1];
		int tar=abs(val)/2+1;
		if (val<0)tar *= -1;
		tar+=n+pre1[l-1];
		return *lower_bound(G1[tar].begin(),G1[tar].end(),l);
	}
	else
	{
		int val = pre2[r]-pre2[l-1];
		int tar=abs(val)/2+1;
		if (val<0)tar *= -1;
		tar+=n+pre2[l-1];
		return *lower_bound(G2[tar].begin(),G2[tar].end(),l);
	}
}
int main()
{
	int t;scanf("%d",&t);
	while (t--)
	{
		scanf("%d%d",&n,&q);
		for (int i=0;i<=n*2;++i)G1[i].clear(),G2[i].clear();
		scanf("%s",s);
		for (int i=1;i<=n;++i)
			a[i] = (s[i-1]=='+'?1:-1);
		for (int i=1;i<=n;++i)
		{
			if (i&1)
			{
				pre1[i]=pre1[i-1]+a[i];
				pre2[i]=pre2[i-1]-a[i];
				G1[pre1[i]+n].push_back(i);
				G2[pre2[i]+n].push_back(i);
			}
			else
			{
				pre1[i]=pre1[i-1]-a[i];
				pre2[i]=pre2[i-1]+a[i];
				G1[pre1[i]+n].push_back(i);
				G2[pre2[i]+n].push_back(i);
			}
		}
		while (q--)
		{
			int l,r;scanf("%d %d",&l,&r);
			if (l&1)
			{
				if (pre1[r]-pre1[l-1]==0)
				{
					printf("0\n");
				}
				else if ((pre1[r]-pre1[l-1])&1)
				{
					printf("1\n%d\n",solve(l,r));
				}
				else
				{
					printf("2\n%d ",l);
					printf("%d\n",solve(l+1,r));
				}
			}
			else
			{
				if (pre2[r]-pre2[l-1]==0)
				{
					printf("0\n");
				}
				else if ((pre2[r]-pre2[l-1])&1)
				{
					printf("1\n%d\n",solve(l,r));
				}
				else
				{
					printf("2\n%d ",l);
					printf("%d\n",solve(l+1,r));
				}
			}
		}
	}
}

E.思维+后缀数组

首先要意识到一点重要的结论:

如果[l,r]在最长上升子序列中,那么[l,r+1],[l,r+2], … \ldots [l,n]也在序列中!!

证明,假设 [ l ′ , r ′ ] [l',r'] [l,r]为最长上升子序列中 [ l , r ] [l,r] [l,r]的下一个

  1. 如果存在 s l ′ + i > s l + i s_{l'+i}>s_{l+i} sl+i>sl+i那么 [ l , r + 1 ] < [ l ′ . r ′ ] [l,r+1]<[l'.r'] [l,r+1]<[l.r]
  2. 否则, [ l , r ] [l,r] [l,r]就是 [ l ′ , r ′ ] [l',r'] [l,r]的前缀!

因此,记 f [ i ] f[i] f[i]为以后缀 i i i为结尾的最长上升子序列的长度

那么我们转移方式就是 f [ i ] = m a x ( f [ j ] + n − l c p [ i ] [ j ] − i + 1 ) ∣ s [ i + l c p [ i ] [ j ] ] < s [ j + l c p [ i ] [ j ] ] f[i]=max(f[j]+n-lcp[i][j]-i+1)|s[i+lcp[i][j]]<s[j+lcp[i][j]] f[i]=max(f[j]+nlcp[i][j]i+1)s[i+lcp[i][j]]<s[j+lcp[i][j]]

#include <bits/stdc++.h>
using namespace std;
const int N=10010;
int Q,n,ans,f[N],lcp[N][N];
char s[N];

int main()
{
	scanf("%d",&Q);
	while (Q--)
	{
		scanf("%d%s",&n,s+1);
		for (int i=1;i<=n;i++)
			lcp[i][n+1]=lcp[n+1][i]=0,f[i]=n-i+1;
		for (int i=n;i>=1;i--)
			for (int j=n;j>=1;j--)
				lcp[i][j]=(s[i]==s[j]) ? lcp[i+1][j+1]+1 : 0;
		for (int i=1;i<=n;i++)
			for (int j=1;j<i;j++)
				if (i+lcp[i][j]<=n && s[j+lcp[i][j]]<s[i+lcp[i][j]])
					f[i]=max(f[i],f[j]+n-(i+lcp[i][j]-1));
		ans=0;
		for (int i=1;i<=n;i++)
			ans=max(ans,f[i]);
		cout<<ans<<"\n";
	}
	return 0;
}
//代码出自stoorz
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值