程序设计思维与实践 CSP-M2

CSP-M2

A - HRZ的序列

题目:

在这里插入图片描述


Sample:
input
2
5
1 2 3 4 5
5
1 2 3 4 5
output
NO
NO

思路:

set

不要被题目唬住了。满足条件的只有三种情况:

其一,只有一个数字多次重复;
其二,两个数字多次重复;
其三,三个数字多次重复,设这三个数为a,b,c(a<b<c),满足b-a=c-b。


总结:

set:自动去重,自动排序。

set大法好。


代码:
#include <iostream>
#include <set>
using namespace std;

long long t,n,a;
set<long long> s;

int main()
{
	cin>>t;	//数据组数
	while(t--)
	{
		cin>>n;	//序列a长度
		for(int i=1;i<=n;i++)
		{
			cin>>a;
			s.insert(a);
		}
		if(s.size()>3)
			cout<<"NO"<<"\n";
		else if(s.size()==1||s.size()==2)
			cout<<"YES"<<"\n";
		else if(s.size()==3)
		{
			long long t[10];int cnt=0;
			for(set<long long>::iterator it=s.begin();it!=s.end();it++)
			{
				t[++cnt]=*it;
			}
			if((t[2]-t[1])==(t[3]-t[2]))
				cout<<"YES"<<"\n";
			else
				cout<<"NO"<<"\n";	
		}
		s.clear();	
	} 
	return 0;
}

B - HRZ学英语

题目:

超链接
在这里插入图片描述


Sample:
input
//sample 1
ABC??FGHIJK???OPQR?TUVWXY?
//sample 2
AABCDEFGHIJKLMNOPQRSTUVW??M
output
//sample 1
ABCDEFGHIJKLMNOPQRSTUVWXYZ
//sample 2
-1

思路:

滑动窗口

这数据量判断字符串长度个26个字符的子串肯定超时,复杂度得在O(N)。

拿一个vis[]数组记录这个字母是否出现过。拿队列存结果子串。拿两个变量numque记录字母的数量和’?'的数量。然后遍历字符串,遍历到的字符有三种情况:

其一,A-Z,且没有出现过,入队,标记字母,字母数加一;
其二,?,入队,问号数加一;
其三,A-Z,出现过,队列进行出队操作,直到当前字符对应的vis不为1(表示在结果子串中未出现过),此时回归上面两种情况,按情况处理。注意pop的时候要相应地处理visnumque

每加入一个字符,判断num+que是否等于26,等于则得到结果子串,退出循环。最后判断满足条件的子串是否存在,不存在输出-1,存在则输出子串。

输出子串时,一直对队列做pop操作,如果是A-Z,直接输出,如果是’?’,去vis[]数组里找第一个为0的标号,输出对应的字母。


总结:

第一反应,滑动窗口!

然后,滑动窗口怎么处理?…两指针?…队列?…是不是还要有个数组来记录子串里26个字母的状态?

一 片 混 乱 ,满 脑 浆 糊 。

最后暴力拿了40分…

还是这个道理:ac了不代表掌握了


代码:
#include <iostream>
#include <string>
#include <queue>
using namespace std;

string s;
queue<char> q;
bool vis[30];

int main()
{
	cin>>s;
	int num=0,que=0;
	bool flag=0;
	for(int i=0;i<s.size();i++)
	{
		if(s[i]!='?'&&vis[s[i]-'A']==0)
		{
			q.push(s[i]);
			vis[s[i]-'A']=1;
			num++;
		}
		else if(s[i]=='?')
		{
			q.push(s[i]);
			que++;
		}
		else if(vis[s[i]-'A']==1)	//不符合条件
		{
			while(vis[s[i]-'A']!=0)
			{
				char t=q.front();
				q.pop();
				if(t=='?')
					que--;
				else
				{
					num--;
					vis[t-'A']=0;
				}
			}
			q.push(s[i]);
			vis[s[i]-'A']=1;
			num++;
		} 
		if(num+que==26)
		{
			flag=1;
			break;
		}
	}
	if(flag)
	{
		int i=0;
		while(!q.empty())
		{
			if(q.front()!='?')
			{
				cout<<q.front();
				q.pop();
			}
			else
			{
				while(vis[i]!=0)
					i++;
				cout<<(char)(i+'A');
				q.pop();
				i++;
			}	
		}
	}
	else
		cout<<"-1"<<endl;
	return 0;
}

D - 咕咕东的奇妙序列

题目:

超链接
在这里插入图片描述


Sample:
input
5
1
3
20
38
56
output
1
2
5
2
0

思路:

找规律/前缀和(*)

---1块,公差为 11位数)-----------------------
1
1 2
1 2 3
......
1 2 3 4 5 6 7 8 9
---2块,公差为 22位数)----------------------
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10 11
......
1 2 3 4 5 6 7 8 9 10 11 ... 98 99
---3块,公差为 33位数)--------------------------------
1 2 3 4 5 6 7 8 9 10 11 ... 98 99 100
1 2 3 4 5 6 7 8 9 10 11 ... 98 99 100 101
......
1 2 3 4 5 6 7 8 9 10 11 ... 98 99 100 101 ... 998 999
---4块,公差为 44位数)--------------------------------
1 2 3 4 5 6 7 8 9 10 11 ... 98 99 100 101 ... 998 999 1000
......

由此可得,每一块的各行长度形成了一组等差数列。对于第i块来说,公差d=i,记录b为每一块的最后一行的长度,即b=b'+i*n(b’为上一块的b,n为i位数的个数),那么每一块的首项a1就是b+i(这里的b是上一行的b)。那么就可以根据首项a1,公差d和项数n来计算这个块的长度。

x<=a-1(代码里的else),表示x在这个块里,这时,要计算这个块的第一行到x所属的这一行的长度,只需要修改项数就可以了,其原理与计算块的长度相同。

对于一个数x,可以通过上述操作来获取这个数所属的行,所以,可以通过二分得到k所属的上一行的最大值ans。那么,ans的这一行的数就是从1至ans,k在这之间。

对于1-ans,若要知道数x在其中的位置,只要将1至x中的一位数个数乘一,两位数个数乘二,以此类推,再相加即可。所以,先获取k在1-ans里的位置,再对1-ans进行二分,得到k的前一个数ans、最后根据kans所在位置的差值,确定k是第几位数,输出。

在最后输出的时候,通过num2str函数将ans转换为字符串,用来获取某位数字。


总结:

算了算数据点发现这题暴力就能拿60分,模测上第四题拿个60分表示已经心满意足了www

用前缀和写一直wa,等过了这段ddl高峰期,我再来try againTAT


代码:
#include <iostream>
#include <string>
#include <sstream>
using namespace std;

long long q,k,l,r,mid,ans;

long long solve(long long x,bool op)
{
	long long a=1,bb=0,b=0,n=0,d=0;
	while(1)
	{
		a*=10;	//两位数起
		d++;	//等差数列的差 
		if(x>a-1)	//从最大的一位数开始判断
		{
			n=a-a/10;	//等差数列的项数
			bb+=(b+d)*n+n*(n-1)/2*d;		//c+d为首项 
			b+=n*d; 	//e.g. 10-99共90个数 90*2 
		}
		else	//x是a位数 
		{
			n=x-a/10+1;	//有n个数
			bb+=(b+d)*n+n*(n-1)/2*d;
			b+=n*d;
			break;	 
		} 
	}
	if(op)
		return bb;
	else
		return b;
}
string num2str(long long i)	//数值转字符串 
{
	stringstream ss;
	ss<<i;
	return ss.str();
}
int main()
{
	cin>>q;
	for(int i=1;i<=q;i++)
	{
		cin>>k;
		l=0;r=1000000000;
		while(l<=r)
		{
			mid=(l+r)/2;
			if(solve(mid,1)<k)	//最后一个比k小的数 
			{
				ans=mid;
				l=mid+1;
			}
			else
				r=mid-1;
		}
		//k在1-ans+1之间 
		k=k-solve(ans,1);	//去掉1,1 2,。。。,1-ans所有的
		l=0;r=ans+1;
		while(l<=r)
		{
			mid=(l+r)/2;
			if(solve(mid,0)<k)
			{
				ans=mid;
				l=mid+1;
			}
			else
				r=mid-1;
		}
		//1,2,...,ans,ans+1(k在这个数里) 
		k=k-solve(ans,0);
		ans++;
		string s=num2str(ans);
		cout<<s[k-1]<<endl;
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值