18-6-2补题记录

1.一直在TLE的A题 -待补完

题意:第一行输入n和m 第二行输出n个数字 接下来m行输入l、r、d  要求判断l到r间的乘积能否被d整除。

思路:粗看很简单,好像可以直接暴力求解,再看妈耶这范围大的哟,n、m、l、r、d最大都可以到100k。

l、r、d大点是小事,关键是m也大,每次判断都要重新乘一遍至少也要100k次,稳稳的超时。

顺便...如果输入lrd用cin可能也会超时,scanf不会,导致补题的时候也非!!!!常暴躁。

我看到的题解大致是两种,但是大同小异都是分解质因数的做法,说是同一种也不为过,但是姑且先算是两种。

1.先将n个数都分解成质因数 如案例的 6 4 7 2 5

分解为6=2*3 4=2*2 7=7 2=2 5=5

每个质因数可以出现很多次,但是这个数只有这么一个,所以以质因数为下标,对应数字的序号作为量保存在vector中。

像这样:

2:1.2.2.4

3:1

5:5

7:3

2.当然d也要这么做 案例中的24=2*2*2*3。l=1,r=2。

这个时候我们就该判断 在序号1和序号2之间(包括1和2)有多少的2和3 那么可以从上边直接看出有3个2和一个3 满足条件所以输出Yes。

至于如何找有几个数 可以用upper_bound和lower_bound实现。那么这道题涉及到迭代器什么的...计划紫书摸到C++那块好好整整这块空缺。


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<int>G[100010];
void p(int id,int val)
{
	for(int i=2;i*i<=val;i++)
	{
		while(val%i==0)
		{
			val/=i;
			G[i].push_back(id);
		}
	}
	if(val>1)//想过这种情况太过累赘 而把循环条件改成 for(int i=2;i<=val;i++)然后当val==1时退出循环 但是...不用细想就该知道这会超时...
	{
		G[val].push_back(id);
	}
}
//用来分解质因数
int que(int l,int r,int i)
{
	return upper_bound(G[i].begin(),G[i].end(),r)-lower_bound(G[i].begin(),G[i].end(),l);//返回区间中具有该因子的数量
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		for(int i=0;i<100010;i++)G[i].clear();
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
		{
			int ls;
			scanf("%d",&ls);
			p(i,ls);
		}
		while(m--)
		{
			int l,r,d;
			scanf("%d%d%d",&l,&r,&d);
			int flag=1;
			for(int i=2;i*i<=d;i++)//将d分解质因数
			{
				int cnt=0;
				while(d%i==0)
				{
					d/=i;
					cnt++;
				}
				int pos=que(l,r,i);
				if(pos<cnt)
				{
					flag=0;
					break;
				}
			}
			if(d>1)
			{
				int pos=que(l,r,d);
				if(pos<1)flag=0;
			}
			if(flag)puts("Yes");
			else puts("No");
		}
	}
	return 0;
}

思路:

1.线性筛选筛出1-400内的素数

2.sum保存下每个数字分解后的素因子的个数,逐渐累加,当d分解后,从已知的d的质因子找到d的区间,从而判断质因子个数

3.如果d没有除干净和①判断方式一样

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=400;

int p[maxn];
vector<int>vv[100010];
int sum[100010][110];
int cnt;
void init()
{
	cnt=0;
	int vis[maxn]={0};
	for(int i=2;i<maxn;i++)
	{
		if(vis[i]==0)
			p[++cnt]=i;
		for(int j=1;j<=cnt && i*p[j]<maxn;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0)break;
		}
	}
}
int main()
{
	init();
	int T,n,m,x;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		memset(sum,0,sizeof(sum));
		for(int i=0;i<=100005;i++)vv[i].clear();
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&x);
			for(int j=1;j<=cnt;j++)
			{
				int c=0;
				while(x%p[j]==0)
				{
					c++;
					x/=p[j];
				}
				sum[i][j]=sum[i-1][j]+c;
			}
			if(x>1)vv[x].push_back(i);
		}
		while(m--)
		{
			int l,r,k;
			scanf("%d%d%d",&l,&r,&k);
			int f=1;
			for(int i=1;i<=cnt;i++)
			{
				int c=0;
				while(k%p[i]==0)
				{
					c++;
					k/=p[i];
				}
				if(sum[r][i]-sum[l-1][i]<c)
				{
					f=0;
					break;
				}
			}
			if(!f)puts("No");
			else if(k==1)puts("Yes");
			else
			{
				int pos=upper_bound(vv[k].begin(),vv[k].end(),r)-lower_bound(vv[k].begin(),vv[k].end(),l);
				if(pos<1)puts("No");
				else puts("Yes");
			}
		}
	}
	return 0;
}
比较:可能是因为水平有限什么的,我个人觉得①好很多,虽然1的vector数组中,合数为下标的vector是无效的,但是搜索起来快很多,2找了所有质数,但是搜索得把70多个素数都找过,可能还有优化余地。




2.看都不敢看的B题

二分法查找一个数字 这个数字好像还需要特别处理什么的感觉蛮烦的

(6.7)

这个题蛮烦的,一是精度,1e18真的不是闹着玩的,一不小心就溢出,二是二分的判断,愚蠢的家伙居然一直以为二分只能用来查找……

题意:

给了a,b,k 根据公式 n^a * [log2(n)]^b <= k  求n的最大值1<=a<=b<=10 1e6<=k<=1e18

思路:

二分 先去l,r的中间值mid 然后判断mid是可以再大还是应该再小,然后确定区间是变成l~mid还是mid~r 当然之后不管是哪一个都会赋值 变成 l~r 然后循环直到 l>r

因为太大的问题 所以我们的思路就是把左边的所有值除到右边去1 <= k / n^a  *  [log2(n)]^b 而这道题最为巧妙的是一个需要精度,而double 或者说 long double 确确实实的解决了这个精度 原因在于我们的解决方案用的是除法 所以虽然这道题是一道裸二分 但是check的判断方式可以说非常的巧妙。

可能我觉得巧妙是因为我读书少。。。

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

long long a,b,k;
long long s[64]={1};
/*
n^a  *  [log2(n)]^b <= k  

我们的思路是将左边的所有值都除到右边去 即 1 <= k / n^a  *  [log2(n)]^b
*/
int check(long long n)
{
	int j;
	for(j=0;j<n;j++)//[log2(n)] 
		if(n<=s[j])
			break;
	long double t=k;
	for(int i=0;i<a;i++)//k/n^a
	{
		t/=n;
		if(t<1.0)
			return 0;
	}
	for(int i=0;i<b;i++)// k/[log2(n)] 循坏b次 表示k/[log2(n)]^b
	{
		t/=j;
		if(t<1.0)
			return 0;
	}
	return 1;
}
int main()
{
	for(int i=1;i<63;i++)
	{
		s[i]=s[i-1]*2;
	}

	int n;
	cin>>n;
	while(n--)
	{
		cin>>a>>b>>k;
		long long l=0,r=k;
		long long mid;
		while(l<=r)
		{
			mid=(l+r)/2;
			if(check(mid))
			{
				l=mid+1;
			}
			else
			{
				r=mid-1;
			}
		}
		cout<<r<<endl;//然而 我并没有弄懂为什么每次的r都会是最精准的 我只注意到每次在l==r后 r一定会再减1 
                              //可能当l==r时他会略大一点什么的(emmm瞎猜的。。
	}
	return 0;
}


3.看上去就很难的C题 -待补(遥遥无期的那种)

学长说暂时先不考虑 可能难度巨大 反正挺吓人的

4.勉勉强强AC的D题   -待补完(感觉稍有意会)

题意:输入一个字符串的长度和一个字符串,如果判断第i开头的字符串和第i+1开头的字符串的字典序大小

思路:只能意会。。。可能还是因为是参考别人的 自己想了很多种方案全部没有过 因为1kk的数据实在太大 很容易TLE,就很爆炸

#include <iostream>
using namespace std;

char s[1000005];
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		cin>>n;
		scanf("%s",s);
		int cnt=1;
		for(int i=0;i<n-1;i++)
		{
			if(s[i]<s[i+1])
			{
				while(cnt--)cout<<"<";
				cnt=1;
			}
			else if(s[i]>s[i+1])
			{
				while(cnt--)cout<<">";
				cnt=1;
			}
			else
			{
				cnt++;
			}
		}
		while(--cnt)
			printf(">");
		cout<<endl;
	}
	return 0;
}

5.唯一水的E题

题意:给出排名、队名、题号、题目状态。要求按格式输出。
格式为排名占3位 ,队名占16位,题号占4位,评测情况占12位。相邻两个部分之间由1位|分开。
排名右对齐显示,队名左对齐显示,长度不足时用空格补齐评测情况则比较复杂,它由两侧的括号[]以及中间10像素组成
思路:按照题意的格式来输出 printf的格式化输出的练习 大概。。
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;

int main()
{
	//freopen("D:\\in.txt","r",stdin);
	//freopen("D:\\out.txt","w",stdout);
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int pm;
		char dm[20];
		int th;
		char zt[20];
		int bl;

		cin>>pm>>dm>>th>>zt;
		printf("%3d",pm);
		printf("|%-16s|%d|",dm,th);


		if(strcmp(zt,"Running")==0)
		{
			cin>>bl;
			cout<<"[";
			for(int i=0;i<10;i++)
			{
				if(i<bl)
					cout<<"X";
				else
					cout<<" ";
			}
			cout<<"]"<<endl;
		}
		else 
		{
			cout<<"[";
			for(int i=0;i<4;i++)
				cout<<" ";
			int ls=6;
			if(strcmp(zt,"FB")==0)
			{
				printf("AC*");
				ls-=3;
			}
			else
			{
				printf("%s",zt);
				ls=ls-strlen(zt);
			}
			for(int i=0;i<ls;i++)
				cout<<" ";
			cout<<"]"<<endl;
		}
	}
	//fclose(stdin);
	//fclose(stdin);
	//system("pause");
	return 0;
}

6.可能是DP的F题       -待补

7.蛋疼到极致的G题

题意:新定义一种规则

规则1:如果 ①a[i]、a[i-1]、a[i-2]、a[i-3]、a[i-4] 都 >r 并且 ②b[i-1]=b[i-2]=b[i-3]=b[i-4]=1,则 b[i] = 0
规则2:如果 ①a[i]、a[i-1]、a[i-2]、a[i-3]、a[i-4] 都 <l 并且 ②b[i-1]=b[i-2]=b[i-3]=b[i-4]=0,则 b[i] = 1

规则3:否则 b[i] = b[i-1]

现在已知a、b求r,l

数学不好学的后果就是下体受惊,很明显可以倒推,然而不知道怎么倒推,然后开始暴躁。。。

那么每次可以看四个b,然后看这四个b中有几个1几个0。1.全为1 2.全为0 3.有1有0 至于第三种情况不涉及r,l 的值,所以基本上我们不会用到。

我们是从四个b也就是条件②来开始判断,然后,就会出现这样的情况。

不满足②(情况3)

满足②且b[i]!=b[i-1] =>满足① => r小于对应数组a 或 l大于对应数组a

满足②且 不满足① =>对应的a数组中有元素<=r 或 对应的a数组中有元素>=l

所以当条件①满足时,a数组中的任何值都>r,即r小于a数组中的最小值,当条件①不满足时,那就有元素<=r,这时候就不能让a中的任何值都>r。 (我觉得此题最难受的地方就在这里,翻来覆去搞不通。。)

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

const int inf=1e9;
int a[100010];

int getmax(int l,int r)
{
	int ans=-inf;
	for(int i=l;i<=r;i++)
	{
		if(ans<a[i])ans=a[i];
	}
	return ans;
}

int getmin(int l,int r)
{
	int ans=inf;
	for(int i=l;i<=r;i++)
	{
		if(ans>a[i])ans=a[i];
	}
	return ans;
}

int main()
{
	char b[100010];//也想过用int 但是 蛋疼之处在于这一行没有空格分割 他就把这string当成一个int然后...
	int n;
	cin>>n;
	int l=-inf,r=inf;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)cin>>b[i];//scanf("%s",b);

	for(int i=5;i<=n;i++)
	{
		if(b[i-1]=='1' && b[i-2]=='1' && b[i-3]=='1' && b[i-4]=='1')
		{
			int Min=getmin(i-4,i);
			if(b[i]=='0')
			{
				r=min(r,Min-1);
			}
			else
			{
				if(r<Min)r=Min;//r=max(r,Min)
			}
		}
		if(b[i-1]=='0' && b[i-2]=='0' && b[i-3]=='0' && b[i-4]=='0')
		{
			int Max=getmax(i-4,i);
			if(b[i]=='1')
			{
				l=max(l,Max+1);
			}
			else
			{
				if(l>Max)l=Max;//l=min(l,Max)
			}
		}
	}
	cout<<l<<" "<<r<<endl;
	return 0;
}
/*
规则1:如果 a[i]、a[i-1]、a[i-2]、a[i-3]、a[i-4] 都 >r 并且 b[i-1]=b[i-2]=b[i-3]=b[i-4]=1,则 b[i] = 0
规则2:如果 a[i]、a[i-1]、a[i-2]、a[i-3]、a[i-4] 都 <l 并且 b[i-1]=b[i-2]=b[i-3]=b[i-4]=0,则 b[i] = 1
规则3:否则 b[i] = b[i-1]
*/



8.没A但是也挺水的H题

题意:输出三个字符串,方便起见,命名为str,s1,s2。要求输出一个包含s1,s2的str最短的子字符串。第一个字符串长度大于1小于100,后面两个串的长度大于1且小于10。
思路:
1.str中是否包含s1、s2 ? 包含 开始进入2判断: 不包含 直接输出No。
2.枚举出所有str的子序列,判断子序列中是否包含s1、s2 ? 包含 进入3判断 : 不包含 进入5。
3.子序列是否比待定字符串ss(第一个待定字符串为str)长度小 ? 是 将子序列赋值给ss : 否 是否长度相等?是 进入4 : 否 进入5。
4.子序列字典序是否比ss小 ? 是 子序列赋值给ss : 否 进入5。
5.是否枚举完毕 ? 是 输出结果:否 进入2。


顺便:substr真棒=w=

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

int main()
{
	string str, s1, s2;
	string ss,s;
	int t;
	cin >> t;
	while (t--)
	{
		cin >> str >> s1 >> s2;
		ss = str;
		int len = str.length();
		//cout << str.find(s1) << " " << str.find(s2) << endl;
		if (str.find(s1) == -1 || str.find(s2) == -1)
		{
			cout << "No" << endl;
		}
		else
		{
			for (int i = 0; i < len; i++)//子字符串的开头
			{
				for (int j = i + 1; j <= len; j++)//子字符串往后取得位数
				{
					s = str.substr(i, j - i);
					if (s.find(s1) != -1 && s.find(s2) != -1)
					{
						if (s.length() < ss.length())
						{
							ss = s;
						}
						else if(s.length() == ss.length() && s<ss)
						{
							ss = s;
						}
					}
				}
			}
			cout << ss << endl;
		}
	}
	//system("pause");
	return 0;
}



9.连题意都搞不懂的I题 待补完

题意:给定n,m 将n变成1~n的一个数组 然后求第m个全排列数组

思路:

①.有个直接的函数 next_permutation(a,a+n) 可以直接拿来用


#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
	int n,m;
	while(cin>>n>>m)
	{
		int a[1005];
		for(int i=0;i<n;i++)
			a[i]=i+1;
		m--;
		while(m--)
		{
			next_permutation(a,a+n);
		}
		for(int i=0;i<n;i++)
		{
			if(i>0)
				cout<<" "<<a[i];
			else
				cout<<a[0];
		}
		cout<<endl;
	}
	return 0;
}
②.如果不用函数 百度找到方法大多是dfs 想着把这道题作为dfs的练习 将来补上。。。



10.没缘没分的J题

题意:有两个人,英文名字挺长的姑且用第一个字母代表,一个是P,一个是U,都喜欢把一串有序的字符串打乱,P会把两个数字交换3n次,U会把两个数字交换7n+1次。输入是n和n个数字,这一列数字是1-n之间的数字,当然这一列数字是打乱的。要求根据这一列打乱的数字判断这是谁打乱的,输出名字。

思路:可能是水平太差 这让我觉得很神奇

1.计算出将每个数字换回原来的位置的次数x 即 交换数字来打乱原来的数列的次数 那么这个数字要么是3n 要么是7n+1

2.由题意可得 P是3n U是7n+1 而且 这两个数字奇偶性不一样 那么就可以从x来判断出是谁在打断数列

3.3n和n 同奇同偶 7n+1和n 奇偶不同

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

int a[1000005];
int main()
{
	int t;
	cin>>t;
	memset(a,0,sizeof(a));
	for(int i=1;i<=t;i++)
		cin>>a[i];

	int num=0;
	for(int i=1;i<=t;i++)
	{
		if(a[i]!=i)
		{
			int l=a[i];
			swap(a[l],a[i]);
			num++;
			i=0;
		}
	}

	if(t%2==num%2)
		cout<<"Petr"<<endl;
	else
		cout<<"Um_nik"<<endl;
	//system("pause");
	return 0;
}


小结:

6月2日的模拟赛,一直断断续续补题到6月7日,近一个星期,一方面毫无疑问是懒,另一方面其实有些题如果能钻通确实是可以做的,然而……

补题收获有一些,但是很多东西可能很快会忘,原因是掌握的不够深入,尽管有在这边努力但是……收效真的不是特别理想,可能是因为笨可能是因为不努力,但是应该有在进步。

猜你喜欢

转载自blog.csdn.net/qq_42191950/article/details/80559658