今天又刷了三道题,都还不错,知识点分别是最小堆、并查集和质数筛选、DFS爆搜
首先上一下题目
第1题 poker
【问题描述】
一副扑克牌有n张牌。一般你买的一副新扑克牌里除了这n张牌外还会有一些张特殊的牌,如果你不小心弄丢了n张牌中的某一张,就可以用特殊牌来代替,但是如果你弄丢两张的话就没有办法了,因为特殊牌上的图案是一样的。
现在你得到了很多扑克牌,准确来说,n种牌你各有a1、a2、……、an张,同时你还有b张特殊牌,现在你需要从这些牌中整理出若干副牌供大家使用。整理出的一副牌可以由n种普通牌各一张组成,也可以由n-1种普通牌各一张再加一张特殊牌组成。
请你设计出一种方案,整理出尽可能多的牌。
【输入格式】
输入包括2行
第一行给出n和b1
第二行给出a1,a2…an。
【输出格式】
输出最多能整理出的牌的副数。
【样例输入】
5 5
5 5 5 5 5
【样例输出】
6
【数据规模】
对于20%的数据 1<=n<=100。牌的数量小于100。
对于40%的数据 1<=n<=3000
对于100%的数据 1<=n<=1000000 牌的数量<=10^6。
第2题 集合
【问题描述】
你想将[A,B]内的所有自然数分成若干个集合。
一开始每个数自己是一个集合,如果某两个数都是一个至少为P的素数的倍
数,则将这两个数所在的集合合并,问最后有多少个集合。
【输入文件】set.in
一行:A,B,P
【输出文件】set.out
集合个数
【输入样例】
10 20 3
【输出样例】
7
【样例解释】
{12,15,18}都是3 的倍数,{10,15,20}都是5 的倍数
故{10,12,15,18,20}为一个集合,其余的数各为一个集合
【数据规模】
50%:1<=A<=B<=1000,2<=P<=B
100%:1<=A<=B<=10^12,B<=A+10^6,2<=P<=B
第3题 卡片游戏
【问题描述】
有N只moreD在玩一个卡片游戏:
首先,桌子上有M张卡片,这M张卡片分别有一个唯一的1~M的编号。N只moreD在桌子上抢牌。每个人最后的得分是所得的所有卡片编号的乘积。
这N只moreD最后报出了自己的得分。你的任务是写一个程序,判断有没有人说谎。
【输入格式】
输入第一行一个整数T,表示T组测试数据。
对于每组测试数据:
第一行:两个用空格隔开的整数:N和M,表示moreD的数量和卡片的数量
第二行:有N个正整数Ai,表示每只moreD报出的得分。
【输出格式】
输出T行,每行输出’Yes’或’No’,表示’Yes’表示不可能没有人说谎 , ’No’表示可能没有人说谎。
【输入输出样例】
cards.in cards.out
3
2 3
2 3
2 3
3 6
2 5
4 6 No
Yes
No
【数据范围】
对于30%的数据N<=3 M<=10 Ai<=100
对于100%数据N<=5 M<=100 Ai<=50000 t<=10
第一题其实很简单,我们每次都要选一张数量最小的牌出来把除它之外的牌都减一(就是假设这一张用特殊牌代替其它的牌减一,凑成一副牌)这一题就是要选出最小的,我们可以用最小堆来维护,有一个点要注意,如果把其它每次都减一的话,会TLE,换个思路,除它之外都减一,我们可不可以就它加一,这样性质是差不多的,在加一个零点,随着它的增加而增加,这样性质就一样了,就可以转换了
下面上代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int f[1000001],much=0;
void down(int p)//维护最小堆
{
while(p<=much)
{
int son=p<<1;
if(f[son]>=f[son+1])
son++;
if(f[son]<f[p])
{
swap(f[son],f[p]);
p=son;
}
else break;
}
return ;
}
void up(int p)//维护最小堆
{
while(p>1)
{
int father=p>>1;
if(f[father]>f[p])
{
swap(f[father],f[p]);
p=father;
}
else break;
}
return ;
}
int read()//读入优化
{
char ch;
while(ch=getchar(),ch<'0' || ch>'9');
int num=ch-'0';
while(ch=getchar(),ch>='0' && ch<='9')
num=num*10+ch-'0';
return num;
}
int main()
{
int n,b,i,j,r,ans=0;
freopen("poker.in","r",stdin);
freopen("poker.out","w",stdout);
memset(f,1,sizeof(f));
scanf("%d %d",&n,&b);
for(i=1;i<=n;i++)//读入数据并且构建最小堆
{
r=read();
much++;
f[much]=r;
up(much);
}
for(i=1;i<=b;i++)//开始用特殊牌
{
ans++;//零点
f[1]++;//牌数最少的一种
down(1);//维护最小堆
if(ans==min(f[2],f[3]))//零点已经达到牌数第二小的了,根据题目特殊牌一副只能用一张,所以这是我们要退出循环
break;
}
printf("%d",f[1]);//牌数最小的就是答案,因为这一种牌最多只有这么多,所以也只能凑成相应的副数
return 0;
}
第二题:
这一题主要是数据太大,很多人一看到就投降┗( ´・∧・`)┛了,原因很简单,这一题我们要筛选质数,而我们的筛法需要的空间是O(n),太大了,连定义都定义不了,其实根据这题目根本不需要这么多质数,只要1到b-a就行了,因为如果大于这个范围的话分解一下任意两个数它的相同质因数都不会出现在大于这个范围,证明很简单因为如果两个不同的数分解出一个相同的质因数,那么这两个数最小就是一个是这个质数的一倍,另一个是两倍,所以绝对不会超过这个范围
下面上代码
#include<iostream>
#include<cstdio>
using namespace std;
long long p[1001000],bo[1001000],father[1001000],much=0;
void reper()//筛选一下质数,也可以不筛这么多,直接筛到b-a就行了,为了省事,只要不会TLE就行
{
for(long long i=2;i<1001000;i++)
if(!bo[i])
{
for(long long j=i+i;j<1001000;j+=i)
bo[j]=1;
}
for(long long i=2;i<1001000;i++)
if(!bo[i])
p[++much]=i;
}
long long find(long long x)//并查集查找
{
if(x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
reper();
long long a,b,P;
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
scanf("I64d %I64d %I64d",&a,&b,&P);
for(long long i=0;i<=b-a;i++)//初始化并查集
father[i]=i;
for(long long i=1;i<=much;i++)//这个是枚举每个符合条件的质数,如果它的倍数在范围里,直接把他们并起来
{
if(p[i]<P)//题目的要求
continue;
if(p[i]>b-a)//超过了
break;
long long start=(a/p[i]+(a%p[i]!=0))*p[i];//选到比a大的最小的质数的倍数
long long first=-1;//标记一下要并到哪里
start=((start-a)>0)? (start-a) : 0;//处理一下,因为我们数组开不到那么大,所以我们用f[i]表示f[i+a]
for(long long j=start;j<=b-a;j+=p[i])//循环枚举
{
if(first!=-1)//找到标记对象后直接打
{
long long fy=find(j);
father[fy]=first;
}
else//招标记对象(对象无要求只要是倍数中的一个就可以,为了简单我们就选第一个)
{
first=find(j);
}
}
}
long long ans=0;
for(long long i=0;i<=b-a;i++)//循环去找有几个集合
if(father[i]==i)
ans++;
cout <<ans;
return 0;
}
第三题:
一开始拿了个80分,原来是有bug,现在我们讲一下正解,首先想到的是DP,可是想了很久都想不出状态,后面看了下数据范围,发现暴力是完全可以的,所以我们就暴力出奇迹;
首先讲一下怎么暴力,我们定义DFS传的函数,x,y,ans,表示我们DFS到第x个人,这个人选到第y张牌,目前的值是ans
下面上代码
#include<iostream>
#include<iostream>
using namespace std;
int a[6],b[101],n,m,bj;
void dfs(int x,int y,int ans)//爆搜
{
if(bj)//如果已经知道一定有人说谎即bj==1
return ;
if(x==n+1)//如果x已经处理到n+1个人了表示不一定有人说谎
{
bj=1;
return ;
}
if(y==m+1)//当前所有的牌全选出来了
{
if(ans!=1)//如果数到一意味着可以有,反之则直接不行
return ;
dfs(x+1,1,a[x+1]);//如果可以有就搜下一个
return ;//搜完返回
}
if((!b[y]) && ans%y==0)//正常情况下如果y没被标记表示可以用并且除的尽就除
{
b[y]=1;//标记一下
dfs(x,y+1,ans/y);//继续搜
b[y]=0;//回溯是把标记给换回去,这一点很重要,一开始80分就是这个bug
}
dfs(x,y+1,ans);//如果y不能选就下一个
}
int main()
{
int t;
freopen("cards.in","r",stdin);
freopen("cards.out","w",stdout);
cin >>t;
while(t--)
{
int i;
cin >>n>>m;
for(i=1;i<=n;i++)//输入数据
cin >>a[i];
bj=0;//打个标记表示是不是一定有人说谎
dfs(1,1,a[1]);//开始暴力
if(bj)
cout <<"No"<<endl;
else cout <<"Yes"<<endl;
}
return 0;
}
希望对大家有帮助,如果有不懂得可以留言询问,至于测试数据无法上传,大家就拿标程对拍一下;