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:32.当然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日,近一个星期,一方面毫无疑问是懒,另一方面其实有些题如果能钻通确实是可以做的,然而……
补题收获有一些,但是很多东西可能很快会忘,原因是掌握的不够深入,尽管有在这边努力但是……收效真的不是特别理想,可能是因为笨可能是因为不努力,但是应该有在进步。