Codeforces Round #741 (Div. 2) CF1562

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

A.思维

是一个小思维题

我们知道2b−1 mod b=b−12b-1\ mod\ b=b-12b1 mod b=b1

这也是最大的

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

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

#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,72,3,5,72,3,5,7构成,且每一个数字最多只出现了一次

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

#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.思维+构造

分类讨论

对于所给二进制字符sss 长度为lenlenlen

  1. 如果存在索引iii s[i]==0s[i]==0s[i]==0i>⌊len2⌋i>\lfloor \frac{len}{2}\rfloori>2len

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

  2. 否则一定对于i>⌊len2⌋i>\lfloor \frac{len}{2}\rfloori>2lens[i]==0s[i]==0s[i]==0

    1. s[⌊len2⌋]==0s[\lfloor \frac{len}{2}\rfloor]==0s[2len]==0: 选择[⌊len2⌋,len],[⌊len2⌋+1,len][\lfloor \frac{len}{2}\rfloor,len],[\lfloor \frac{len}{2}\rfloor+1,len][2len,len],[2len+1,len]
    2. s[⌊len2⌋]==1s[\lfloor \frac{len}{2}\rfloor]==1s[2len]==1:选择[⌊len2⌋,len−1],[⌊len2⌋+1,len][\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,−11,-11,1的和

pre1pre1pre1:偶数位置乘−1-11

pre2pre2pre2:奇数位置乘−1-11

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

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

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

  1. abs(sum)abs(sum)abs(sum)为奇数答案为111
  2. abs(sum)abs(sum)abs(sum)为偶数答案为222

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

我们找到l≤id≤rl\le id\le rlidr 使得sum(l,id)=sum(l,r)/2+1sum(l,id)=sum(l,r)/2+1sum(l,id)=sum(l,r)/2+1 sum(l,id−1)=sum(l,r)/2sum(l,id-1)=sum(l,r)/2sum(l,id1)=sum(l,r)/2

那么sum(id+1,r)=sum(l,r)/2sum(id+1,r)=sum(l,r)/2sum(id+1,r)=sum(l,r)/2

那么当我们抽走ididid时,sum(id+1,r)=−sum(l,r)sum(id+1,r)=-sum(l,r)sum(id+1,r)=sum(l,r)

那么sum(l,id−1)+sum(id+1,r)=0sum(l,id-1)+sum(id+1,r)=0sum(l,id1)+sum(id+1,r)=0

至于偶数呢?

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

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

#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.二分

已经没有什么好说的了

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

记为函数solve(l,r)solve(l,r)solve(l,r)

找到[l,r][l,r][l,r]中,ididid sum(l,id)=sum(l,r)/2+1sum(l,id)=sum(l,r)/2+1sum(l,id)=sum(l,r)/2+1 sum(l,id−1)=sum(l,r)/2sum(l,id-1)=sum(l,r)/2sum(l,id1)=sum(l,r)/2

我们已知了sum(l,id)sum(l,id)sum(l,id) 那么我们也就是知道了pre(id)pre(id)pre(id)

因此,我们将每一个pre(i)pre(i)pre(i)的值开一个vectorvectorvector

然后进vector[pre(id)]vector[pre(id)]vector[pre(id)]中找第一个>=l>=l>=l的作为ididid

#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. 如果存在sl′+i>sl+is_{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]为以后缀iii为结尾的最长上升子序列的长度

那么我们转移方式就是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]+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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值