洛谷做题总结

目录

1.P1062 [NOIP2006 普及组]高低位转换

2.P1106 删数问题

3.P1138 第k小整数

4.P1025 [NOIP2001 提高组] 数的划分

5.P1037 [NOIP2002 普及组] 产生数

6.P1045 [NOIP2003 普及组] 麦森数

7.P1159 排行榜

8.P1869 愚蠢的组合数(涉及组合数公式)

9.P1865 A % B Problem(筛法+前缀和)

10.P1890 gcd区间

11.P1920 成功密码

12.P1134 [USACO3.2]阶乘问题

13.(普及+/提高)P1835 素数密度

14.P1004 [NOIP2000 提高组] 方格取数

15.P1006 [NOIP2008 提高组] 传纸条

 

1.P1062 [NOIP2006 普及组]高低位转换

高低位交换 - 洛谷

自猜:二进制存储,翻转数组,二进制转化十进制后输出。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
using namespace std;
int main()
{
	int a[35];
	long long m;
	string s;
	cin>>m;
	memset(a,0,sizeof(a));
	while(m)
	{
		s=char(m%2+'0')+s;
		m/=2;
	}
	int n=s.length();
	for(int i=n-1,j=31;i>=0;i--,j--)
		a[j]=s[i]-'0';
	for(int i=15;i>=0;i--)
		swap(a[i],a[i+16]);
	long long sum=0;
	for(int i=31;i>=0;i--) 
		if(a[i])
			sum+=pow(2,31-i);
	cout<<sum<<endl;
	return 0;	
} 

评价:麻烦,多余的代码量,运行时间过长。

官解:使用位运算操作,将原数据与运算后左移右移,进行或运算输出结果。

#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
    unsigned long long x;
    cin>>x;
    cout<<((x&0x0000ffff)<<16|(x&0xffff0000)>>16)<<endl;//万无一失的做法
}

评价:简洁,高效,难以置信。

2.P1106 删数问题

删数问题 - 洛谷

自猜:无想法,认为先删大的,但很容易举出反例,不合理。

官解:观察删除数据可知,删除比后位大的数据,类似山峰时先删除山峰,而且越靠近前面的山峰越早删除,但有一点,注意出现前导0的情况,要删除0。

#include<iostream>
using namespace std;
int main()
{
	string s;
	int k;
	cin>>s>>k;
	int n=s.length();
	while(k--)
	{
		for(int i=0;i<=n-2;i++)
		{
			if(s[i]>s[i+1])
			{
				s.erase(i,1);
				break;	
			}	
		}	
		n--;
		//这里注意若出现大于情况,就是正常长度减一
		//如果没有出现大于情况,那么就是要删掉最后一位
		//长度减一正常删掉 
	} 
	int j=0;
	while(j<n&&s[j]=='0') j++;
	if(j==n) cout<<"0";
	else
		for(int i=j;i<n;i++)
			cout<<s[i];
	cout<<endl;
 	return 0;	
} 

评价:要对于数据有足够的敏感度,观察数据被删除掉的规律,总结并修改,注意其中出现过的特殊情况。

3.P1138 第k小整数

第k小整数 - 洛谷

自猜:使用set处理数据,完美解决去除重复数据和排序问题,已无需更改。

#include<iostream>
#include<set>
using namespace std;
int main()
{
	set<int>s;
	int n,k;
	int temp;
	cin>>n>>k;
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		s.insert(temp);	
	}	
	if(k>s.size()) cout<<"NO RESULT"<<endl;
	else
	{
		int i=1;
		set<int>::iterator it;
		for(it=s.begin(),i=1;it!=s.end();it++,i++)
			if(i==k) cout<<*it<<endl;
	}
	return 0;
} 

评价:比较简单的题目,无需过多思考,简洁做法解决问题。

4.P1025 [NOIP2001 提高组] 数的划分

[NOIP2001 提高组] 数的划分 - 洛谷

自猜:使用先保证每个里面都有数,然后进行划分,但是没有想出来具体的方法。

官解:

方法一:动态规划(递推)

对于当前情况,转化为小球放进盒子内,盒子最终无空闲的情况,对此可分为两种情况组合而成:

1).如果场上至少有一个盒子内有一个小球,其余小球自由放入其余盒子内部,近似于将n-1个小球放入k-1个盒子内,保证无空盒子;

2).如果场上没有一个盒子有一个小球,那么先给每个盒子装进一个小球,后将n-k个小球装入k个盒子中,保证每个盒子最少为2个小球;

(面对这种写法是可以写出两种情况的递推表达式,进而建立本题的动态转移方程)

我们假设f[n][k]是将n个小球存入k个盒子中,且无空盒出现,则:

f[n][k]=f[n-1][k-1]+f[n-k][k](后面的建立在n>k的情况下)

对数据进行初始化确定边界

可发现当n==k时,f[n][k]=1;

当k==1ork==0时,f[n][k]=1;

当n<k时,f[n][k]=0;

代码如下:

#include<iostream>
using namespace std;
int main()
{
	int n,k,a[205][10];
	cin>>n>>k;
	for(int i=1;i<=n;i++) 
	{
		a[i][1]=1;
		a[i][0]=1;	
	} 
	for(int i=2;i<=k;i++)
	{
		a[1][i]=0;
		a[0][i]=0;
	}
	for(int i=2;i<=n;i++)
		for(int j=2;j<=k;j++)
			if(i>j) a[i][j]=a[i-1][j-1]+a[i-j][j];
			else a[i][j]=a[i-1][j-1];
	cout<<a[n][k];
	return 0;
}

评价:把问题抽象出基本情况,试图使用数学语言概况总结,其中一定要通过寻找正确的状态分析出动态转移方程,这样这类题就可以说成功了一半。

方法二:dfs+剪枝

方法三:母函数(生成函数)

5.P1037 [NOIP2002 普及组] 产生数

[NOIP2002 普及组] 产生数 - 洛谷

自猜:统计每个数在输入数据中有多少个,出现变成其他的数就进行统计,但样例只过了一个。

评价:原因在于忽视一类情况,例:

1->2

2->3

则1->3同样成立

同时本题数据过大,使用高精度算法。

官解:

方法一:使用Floyd+高精度,先确定每个数究竟能变成多少个其他数字,然后进行逐项比较相乘,高精度乘低精度求出最终结果。

#include<iostream>
#include<string>
using namespace std;
string str;
int k,vis[10][10],f[10],num[101];
void floyd()//弗洛伊德算法求出一个数可以有多少种变化情况 
{
	for(int k=0;k<=9;k++)
		for(int i=0;i<=9;i++)
			for(int j=0;j<=9;j++)	
				vis[i][j] = vis[i][j] || (vis[i][k] && vis[k][j]) ;
}
int main()
{
	cin>>str>>k;
	int a,b;
	while(k--)
	{
		cin>>a>>b;
		vis[a][b] = true;	
	} 
	for(int i=0;i<=9;i++) vis[i][i]=true; //一个数可以变成自己本身 
	floyd();
	for(int i=0;i<=9;i++)
		for(int j=0;j<=9;j++)
			if(vis[i][j]) f[i]++;//统计一个数能变成多少数(包括自己本身) 
	int len=2;//默认最终结果长度 
	num[1]=1;//如果没有任何数字可以发生改变,那么结果就是1,只有原数 
	int n=str.length();
	for(int i=0;i<n;i++)
	{
		for(int j=1;j<=100;j++) num[j]*=f[str[i]-'0'];//每一项乘当前位置数可变数量 
		for(int j=1;j<=100;j++) 
			if(num[j]>=10)
			{
				num[j+1]+=num[j]/10;
				num[j]%=10;
			}                     //进行进位操作 
		while(num[len]) len++;  //统计最终结果长度,便于输出 
	}
	for(int i=len-1;i>=1;i--) cout<<num[i];
	cout<<endl;
	return 0;
}

评价:该解法将看似和图论毫不相关的题目与Floyd相联系,展现了算法的相通,对思维是一种新的拓展,但由于Floyd算法时间复杂度过高,当输入数据>500时将不能满足正常解题,该解法面对小输入情况可以说非常精妙了。

方法二:深搜+高精度

6.P1045 [NOIP2003 普及组] 麦森数

[NOIP2003 普及组] 麦森数 - 洛谷

自猜:首先猜测使用高精度累乘判断位数,输出最后500位结果,然后发现数据过大,遍历必超时,猜测存在公式判断2^n-1位数,再猜测本题使用快速幂缩短运算时间,综合判断是一道高精快速幂题目。

评价:猜测正确,但代码能力不足,对于高精度乘法的理解力不足,无法自己敲出代码,对于此类答案要记忆。

官解:高精度+快速幂

位数判断:

假设2^n位数为k,由于2^n-1尾数不为0,则位数仍为k

我们可知10^n的位数为n+1

对于2^n如果我们也能将其转化为以10为底的数,那么指数再加1就是位数k了

易知10^log10(2)=2,则k=p*log10(2)+1

计算过程:

使用高精度+快速幂,注意一点,快速幂在进行高精度运算时,不止底数进行高精度运算,指数同样进行高精度运算,同时面对题目要求,只计算后500位就可以了。

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
long long p,n,res[1001],sav[1001],f[1001];
void maze_1()//当指数为奇数时,我们计算底数*最终结果,并将结果赋给储存输出数组 
{
	memset(sav,0,sizeof(sav));
	for(int i=1;i<=500;i++)
		for(int j=1;j<=500;j++)
			sav[i+j-1]+=res[i]*f[j];
	for(int i=1;i<=1000;i++)
	{
		sav[i+1]+=sav[i]/10;
		sav[i]=sav[i]%10;
	}
	for(int i=0;i<=1000;i++)
		res[i]=sav[i];
}
void maze_2()//指数/2时,底数*底数,将结果赋给底数数组 
{
	memset(sav,0,sizeof(sav));
	for(int i=1;i<=500;i++)
		for(int j=1;j<=500;j++)
			sav[i+j-1]+=f[i]*f[j];
	for(int i=1;i<=1000;i++)
	{
		sav[i+1]+=sav[i]/10;
		sav[i]=sav[i]%10;
	}
	for(int i=0;i<=1000;i++)
		f[i]=sav[i];
}
int main()
{
	cin>>p;
	n=(int)(log10(2)*p)+1;//公式直接得出结果位数 
	res[1]=1;//这个储存最终结果,奇数时底数相乘 
	f[1]=2;//初始化,这个数组用来存储底数,偶数时,底数自乘 
	cout<<n<<endl;
	while(p)
	{
		if(p&1) maze_1();
		p>>=1;
		maze_2();
	}
	res[1]--;
	for(int i=500;i>=1;i--) 
	{
		if(i%50==0&&i<500) cout<<endl;
		cout<<res[i];
	}
	return 0;
} 

评价:要明确在快速幂中,使用高精度的位置,而且要明确不同数组是存储底数还是存储最终结果,可以记忆一下这个模板。

7.P1159 排行榜

排行榜 - 洛谷

自猜:首先猜测为结构体排序,因为没有看到题目说明这个输入对应着现在的榜单,只想着上升就下降回去,下降就上升回去,交了一发,全wa;

后来想了想发现题目说明,在排序过程中应该不能移动位置不变的歌,将上升与下降的歌曲进行交换操作,交了一发,过了一个样例点(共十个);

再次思考,发现这个题不要使用排序方法,使用数组分开储存上升,下降的歌曲,使用最终答案数组储存不变歌曲,然后将下降歌曲全部填充进答案数组前部分空缺中,上升歌曲填入后半部分,输出即可,交了一发,AC。

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
string a[105],b[105],c[105];
int a1=0,b1=0;
int main()
{
	int n;
	cin>>n;
	string temp,tamp;
	for(int i=0;i<n;i++)
	{
		cin>>temp>>tamp;
		if(tamp=="UP")
		{
			a[a1]=temp;
			a1++;
		}
		else if(tamp=="DOWN")
		{
			b[b1]=temp;
			b1++;
		}
		else 
		{
			c[i]=temp;
		}
	}
	int sum=0,svm=0;
	for(int i=0;i<n;i++)
	{
		if(c[i]!="") continue;
		else
		{
			if(sum<b1)
			{
				c[i]=b[sum];
				sum++;
			}
			else
			{
				c[i]=a[svm];
				svm++;
			}
		}
	}
	for(int i=0;i<n;i++) cout<<c[i]<<endl; 
	return 0;	
} 

评价:读题不够认真,这算一道很水的模拟题,题意不难,认真一点即可。

官解:面对数组代码过长,可以使用STL 中queue来储存数据,原理相同。

#include<bits/stdc++.h>
using namespace std;
string name[101],dos;
queue<int> fr,en;
int n;bool sa[101];
int main(){
    cin>>n;for(int i=1;i<=n;i++){
        cin>>name[i]>>dos;
        if(dos=="UP")en.push(i);
        if(dos=="DOWN")fr.push(i);
        if(dos=="SAME")sa[i]=1;
    }
    for(int i=1;i<=n;i++){
        if(sa[i]==1)cout<<name[i]<<endl;
        else{
        if(!fr.empty()){cout<<name[fr.front()]<<endl;fr.pop();}
        else if(!en.empty()){cout<<name[en.front()]<<endl;en.pop();}
        }
    }
    return 0;
}

8.P1869 愚蠢的组合数(涉及组合数公式)

愚蠢的组合数 - 洛谷

自猜:面对较大数据,可以推测一定是有公式完成对奇偶的直接判断,搜索组合数学,找到了这个判断条件。

若C(n,m)为奇数,则n&m==n;

以下为证明过程:(洛谷大佬证明方法)

利用数学归纳法:

由C(n,k) = C(n,k-1) + C(n-1,k-1);

对应于杨辉三角:

1

1 2 1

1 3 3 1

1 4 6 4 1

………………

可以验证前面几层及k = 0时满足结论,

下面证明在C(n-1,k)和C(n-1,k-1) (k > 0) 满足结论的情况下,C(n,k)满足结论. (分类讨论并反证)

1).假设C(n-1,k)和C(n-1,k-1)为奇数:

则有:(n-1)&k == k;

(n-1)&(k-1) == k-1;

由于k和k-1的最后一位(在这里的位指的是二进制的位,下同)必然是不同的,所以n-1的最后一位必然是1.

现假设n&k == k.

则同样因为n-1和n的最后一位不同推出k的最后一位是1. 因为n-1的最后一位是1,则n的最后一位是0,所以n&k != k,与假设矛盾.

所以得n&k != k.

2).假设C(n-1,k)和C(n-1,k-1)为偶数:

则有:(n-1)&k != k;

(n-1)&(k-1) != k-1;

现假设n&k == k.

则对于k最后一位为1的情况:

此时n最后一位也为1,所以有(n-1)&(k-1) == k-1,与假设矛盾.

而对于k最后一位为0的情况:

则k的末尾必有一部分形如:10; 代表任意个0.

相应的,n对应的部分为:1{···}···; ···代表0或1.

而若n对应的{···}···中只要有一个为1,则(n-1)&k == k成立,所以n对应部分也应该是10.

则相应的,k-1和n-1的末尾部分均为01,所以(n-1)&(k-1) == k-1 成立,与假设矛盾.

所以得n&k != k.

由1)和2)得出当C(n,k)是偶数时,n&k != k.

3).假设C(n-1,k)为奇数而C(n-1,k-1)为偶数:

则有:(n-1)&k == k;

(n-1)&(k-1) != k-1;

显然,k的最后一位只能是0,否则由(n-1)&k == k即可推出(n-1)&(k-1) == k-1.

所以k的末尾必有一部分形如:10;

相应的,n-1的对应部分为:1{···}···;

相应的,k-1的对应部分为:01;

则若要使得(n-1)&(k-1) != k-1 则要求n-1对应的{*}*中至少有一个是0.

所以n的对应部分也就为 :1{···}···; (不会因为进位变1为0)

所以 n&k = k.

4).假设C(n-1,k)为偶数而C(n-1,k-1)为奇数:

则有:(n-1)&k != k;

(n-1)&(k-1) == k-1;

分两种情况:

当k-1的最后一位为0时:

则k-1的末尾必有一部分形如:10;

相应的,k的对应部分为 :11;

相应的,n-1的对应部分为 :1{···}0; (若为1{···}1,则(n-1)&k == k)

相应的,n的对应部分为 :1{···}1;

所以n&k = k.

当k-1的最后一位为1时:

则k-1的末尾必有一部分形如:01; (前面的0可以是附加上去的)

相应的,k的对应部分为 :10;

相应的,n-1的对应部分为 :01; (若为11,则(n-1)&k == k)

相应的,n的对应部分为 :10;

所以n&k = k.

由3),4)得出当C(n,k)为奇数时,n&k = k.

综上,结论得证!

#include<iostream>
using namespace std;
int main()
{
	long long n;
	cin>>n;
	while(n--)
	{
		long long a,b;
		cin>>a>>b;
		cout<<((a&b)==b?"1":"0")<<endl;
	}
	return 0;	
} 

评价:组合数学包涵甚广,内容很多,还要学很多。

9.P1865 A % B Problem(筛法+前缀和)

A % B Problem - 洛谷

自猜:一定区域内判断素数个数问题,想到先将数据预处理,使用欧拉筛O(n)级别时间复杂度,筛完之后使用前缀和储存每个位置前素数个数,最终根据输入位置相减即可。

#include<iostream>
#include<cstring>
using namespace std;
const long long maxn=1e6+5;
int prime[maxn];
int visit[maxn];
long long ans[maxn];
void Prime()
{
	memset(visit,0,sizeof(visit));
	memset(prime,0,sizeof(prime));
	for(int i=2;i<=maxn;i++)
	{
		if(!visit[i])
			prime[++prime[0]]=i;
		for(int j=1;j<=prime[0]&&prime[j]*i<=maxn;j++)
		{
			visit[prime[j]*i]=1;
			if(i%prime[j]==0)
				break;
		}	
	}
	ans[1]=0;
	for(int i=2;i<=maxn;i++)
	{
		if(!visit[i]) ans[i]=ans[i-1]+1;
		else ans[i]=ans[i-1];
	}
}
int main()
{
	long long n,m;
	cin>>n>>m;
	Prime();
//	for(int i=1;i<=m;i++) cout<<visit[i]<<" "<<ans[i]<<endl;
	while(n--)
	{
		long long l,r;
		cin>>l>>r;
		if(l<1||r>m) cout<<"Crossing the line"<<endl;
		else cout<<(ans[r]-ans[l-1])<<endl;//注意这里,被卡了好几发,最后看题解才发现
	}
	return 0;
}

评价:欧拉筛在处理判断素数方面非常方便,多背模板。

官解:这道题目没有要求保存素数,只保存素数个数,使用正常埃氏筛也可以,在筛的过程中保存前缀和即可。

#include<bits/stdc++.h>
using namespace std;

int f[1000001];
bool vis[1000001];

void shai(int n)
{
    f[1]=0;
    vis[1]=true;
    for(int i=2;i<=n;i++)
    {
        if(vis[i]==false) //在筛里进行前缀和
        {
            f[i]=f[i-1]+1;//前缀和计算
            for(int j=i+i;j<=n;j=j+i)
            {
                vis[j]=true;//标记操作
            }
        }
        else f[i]=f[i-1];//前缀和转移
    }
}

int main()
{
    int n,m;
    scanf("%d%d",&m,&n);
    shai(n);
    for(int i=1;i<=m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        if(l<1 || r>n) cout<<"Crossing the line"<<endl;//判断是否超出区间
        else 
        {
            int y=f[r]-f[l-1];//此处已经修改
            cout<<y<<endl;
        }
    }
    return 0;
}

评价:适当面对题目修改模板,用最简单的代码解决最复杂的问题。

10.P1890 gcd区间

gcd区间 - 洛谷

自猜:感觉应该先储存好gcd数据,然后直接访问即可,但一时没有想到方法。

官解:很简单的一道题,直接暴力二维数组储存两个数之间的gcd,一位一位的往下找,例如知道了第一个和第二个数据间gcd,那么当前gcd和第三个数据间的gcd就是第一个和第三个数据间的gcd。

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
long long gcd(long long a,long long b)
{
    return a==0?b:gcd(b%a,a);
}
long long ans[1005][1005],a[1005];
int main()
{
	long long n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		ans[i][i]=a[i];
	} 
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			ans[i][j]=gcd(ans[i][j-1],a[j]);
		}
	}
	while(m--)
	{
		long long s,v;
		scanf("%lld %lld",&s,&v);
		printf("%lld\n",ans[s][v]);
	}
	return 0;
} 

评价:简单的问题,但是自身代码能力不足,没能第一时间想出答案。

11.P1920 成功密码

成功密码 - 洛谷

自猜:观察题目想到了泰勒展开,后被提醒应该是无穷级数,寻找后发现这是-log(1.0-x)函数泰勒展开后的结果,该函数收敛,可求得极限为当前函数值,则超过极限值后的数据不需考虑,即循环终止条件。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int main()
{
	double n,x;
	scanf("%lf %lf",&x,&n);
	double lim=-log(1.0-x);
	double ans=0,s=1.0;
	for(int i=1;i<=(int)n;i++)
	{
		s*=x;
		ans+=1.0*s/i;
		if(round(ans * 10000.0) >= round(lim * 10000.0)) break;
	}
	printf("%.4lf\n",ans);
	return 0;
}

评价:对于这个题,最关键一步是想到题目中公式为函数泰勒展开式,如果实在未发现,其实可以强行循环两万次,不过这个题很巧妙,可以记忆一下。

12.P1134 [USACO3.2]阶乘问题

[USACO3.2]阶乘问题 - 洛谷

自猜:

考虑题目要求中只考虑不为零最右值,首先只考虑最后一位,每次取模10保留最后一位,同时发现每十位数据后最后的数字是重复的,所以对n/10后重复累乘最后一位,再对n%10的位数分别相乘,最终只过了一个样例点。

官解:

查找原因发现,对于

14!=87178291200,此时若只考虑最后一位2,那么15!保留位数就变成了2*15=30

但是15!=1307674368000,也就是最终结果为8,从这里就已经出现错误了

翻看洛谷题解找到对于这个的暴力解法,也就是保留的位数多一点,不止保留一位,那么这道题可以通过,但这种方法不是解决这道题的正确做法。

暴力代码:

#include<iostream>
using namespace std;
int main()
{
    long long result=1;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        result*=i;
        while(result%10==0)
            result/=10;
        result=result%100000000;
    }
    cout<<result%10;
}

分析这个题,发现出现问题的原因在于末位的零的出现,若当前乘法过程中没有出现0,可以一直保存最后一位即可,且最右非零数只可能为2,4,6,8,这四个数在乘6的情况下,最后一位数都与原数保持不变,那么只考虑最后一位数的情况下,4*2*8=4*2*5,这样将所有的5全部变成8,由于多次乘8会出现一定规律,所以用个数组直接调用即可,膜拜洛谷大佬。

洛谷代码:

#include<iostream>
#include<cmath>
using namespace std;
int a[4]={6,8,4,2};
int ans=1,n;
int main()
{
	cin>>n;
	while(n>0)
	{
		for(int i=1;i<=n%10;i++)
		{
			if(i!=5) ans=ans*i%10;	
		}	
		n/=5;
		ans=ans*a[n%4]%10;
	} 
	cout<<ans<<endl;
	return 0;
}

评价:这个题算是和数论有那么一点点关系,关键在于先分析最后的结果呈现什么规律,可以使用打表的方式,然后根据规律发现在什么情况下该结果会出现改变,把特殊情况单独提出来,然后转化为一般情况,这样得到最终结果,很不错的一道题。

13.(普及+/提高)P1835 素数密度

登录 - 洛谷

自猜:

面对又是求区间素数个数的题,首先想到打表然后前缀和保存,但是这个这个题首先数据范围直接让一般打表绝望,所以必须采取别的方法。

官解:

寻找评论区,找到了有关方法

首先合数存在一个性质:可以分解为两个不为1且不等于本身的因子相乘,即n=a*b(n为合数),这样可以引申出的关键题解为:一个合数必定存在一个1-\sqrt{}n内的素因子,也就是说我们把1-\sqrt{}n

内的素数保留下来,然后直接用这些素数去排除查询区间内的合数,然后保留下来的数就是区间内的素数。 

注意这道题是要求查询区间的素数个数,那么对于当前以及排除掉的数字,我们直接让当前数字减去下区间的数组位置定为合数,避免了要开过大数组爆栈且资源浪费(只使用了查询区间内的数组空间),最后遍历该数组确定所有素数个数即可。

PS:这个题中还有一点细节,就是对于下区间为1的情况,这时候手动调整区间为2,因为在题中1同样被确定为素数,这样造成最终答案错误。

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=1e6+5;
int prime[maxn] , cnt=0, ans=0;
bool vis[maxn];
bool isPrime[maxn];

void Euler() {
	for(int i = 2 ; i <= 50000 ; ++ i) {
		if(!vis[i]) {
			prime[++cnt] = i;
			for(int j = i + i ; j <= 50000 ; j += i) {
				vis[j] = 1;
			}
		} 
	}
}
long long _max(long long a,long long b)
{
	return a>b?a:b;
}
int main()
{
	int l,r;
	cin>>l>>r;
	l+=(l==1);
	if(l>r)
	{
		cout<<"0"<<endl;
		return 0;
	}
	Euler();
	for(long long i=1;i<=cnt;++i)
	{
		for(long long j=max(2,(l-1)/prime[i]+1)*prime[i];j<=r;j+=prime[i])
			if(j-l>=0) isPrime[j-l]=1;
	} 
	for(long long i=l;i<=r;i++) if(!isPrime[i-l]) ans++;
	cout<<ans<<endl;
	return 0;
}

评价:题解区都说是一道比较基础的筛法问题,但是码力不足想不出来,算是记忆一下吧,然后对于这种稍稍变形的筛法以及合数的定理也很重要,多多记忆,多多学习。

14.P1004 [NOIP2000 提高组] 方格取数

[NOIP2000 提高组] 方格取数 - 洛谷

自猜:

首先想到了深度搜索,后来发现标签里有动态规划,就开始思考状态转移方程,如果只走一遍,会发现只需要二维dp即可,走两次就是两边,第一次回溯然后把路径上数据清0,动手实践。

void collection()
{
	number[1][1]=map[1][1];
	for(int i=0;i<=n+1;i++) 
	{
		number[0][i]=number[i][0]=0;
	}
	for(int i=2;i<=n;i++)
	{
		number[1][i]=number[1][i-1]+map[1][i];
	}
	for(int i=2;i<=n;i++)
	{
		number[i][1]=number[i-1][1]+map[i][1];
		for(int j=2;j<=n;j++)
		{
			number[i][j]=max(number[i-1][j],number[i][j-1])+map[i][j];
		}
	}
}

后来只过了60分。。。

官解:

分析后发现每次取最优情况,这种类似于贪心的思想,在解决这个问题时,每次的最优情况累加并不能产生最终的最优解,这种方法是不行的

四维dp:

看题解明确了,这是这道题最简单的方法

简单来说就是使用i,j和k,w来分别表示两个人同时前进

然后在一个人经历了当前位置后,当前位置值就赋为0,在同时走的情况下,最终结果对应的就是走两边同时能取到的最优结果

#include<iostream>
#include<algorithm>
using namespace std;
int map[15][15],dp[15][15][15][15];
int n;
int _max(int a,int b,int c,int d)
{
	return max(max(a,b),max(c,d));
}
int main()
{
	
	cin>>n;
	int x,y,data;
	while(cin>>x>>y>>data&&(x||y||data))
	{
		map[x][y]=data;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=n;k++)
			{
				for(int w=1;w<=n;w++)
				{
					dp[i][j][k][w]=_max(dp[i-1][j][k-1][w],dp[i-1][j][k][w-1],dp[i][j-1][k-1][w],dp[i][j-1][k][w-1])+map[i][j]+map[k][w];
					if(i==k&&j==w) dp[i][j][k][w]-=map[k][w];
				}
			}
		}
	}
	cout<<dp[n][n][n][n]<<endl;
	return 0;	
} 

思路简单,代码简洁,很完美。

但是这道题存在优化情况,可以优化到三维,二维dp,对应代码量也要增加。

评价:

很不错的一道题,使用的方法很多,且题目本身做法不难,难在优化后的情况,值得记忆的一道题。

15.P1006 [NOIP2008 提高组] 传纸条

本题与14题类似,原理基本相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值