CSP前的做题计划

2019.10.3:蔡是原罪.所以蒟蒻决定从(1997)年开始刷每年的普及组和提高组的题.普及组应该都会刚完,提高组有些题选择性放弃.

2019.10.9:很抱歉,我又决定咕咕咕了,因为我发现以前的题太难了!!!!!!!!!!!!!!!!

2019.10.27:蒟蒻在考完鸽王的模拟赛DAY1之后痛定思痛,决定继续来填这个坑!!!!!!!!

(1997)普及组/提高组:

棋盘问题1

题意:设有一个(N imes M)方格的棋盘((1≤N≤100,1≤M≤100)),求出该棋盘中包含有多少个正方形、多少个长方形(不包括正方形).

分析:直接推式子.正方形:长和宽相等,直接枚举边长即可,(sum_{i=1}^{min(n,m)}(n-i+1)(m-i+1)).矩形(包括正方形):长和宽可以不等,要分别枚举,(sum_{i=1}^nsum_{j=1}^m(n-i+1)(m-i+1)=sum_{i=1}^n(n-i+1)sum_{j=1}^m(m-i+1)),然后运用等差数列求和公式即可得(frac{n(n+1)}{2}frac{m(m+1)}{2}=(n+1)(m+1)nm/4.)

长方形数量即为两式相减.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int n,m,ans1,ans2;
int main(){
	n=read();m=read();if(n>m)swap(n,m);
	for(int i=1;i<=n;++i)
		ans1+=(n-i+1)*(m-i+1);
	ans2=(n+1)*(m+1)*n*m/4;
	printf("%d %d
",ans1,ans2-ans1);
    return 0;
}

棋盘问题(2)

题意:在(N imes N)的棋盘上((1≤N≤5)),填入(1,2,…,N^2)(N^2)个数,使得任意两个相邻的数之和为素数.如有多种解,则输出第一行、第一列之和为最小的排列方案;若无解,则输出“NO”.

直接爆搜的话,因为要保证第一行、第一列之和为最小的排列方案,所以(n=5)会超时,所以就(dfs)的同时记录了第一行第一列上的值,如果已经超过了当前的最小值,直接(return),一个小剪枝即可.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=15;
int n,Ans=1e9,a[N][N],BJ[N*N],ans[N][N];
int prime[N*N],v[N*N],bj[N*N];
inline void get_prime(){
	int m=0;
	for(int i=2;i<=200;++i){
		if(!v[i]){
			v[i]=i;BJ[i]=1;
			prime[++m]=i;
		}
		for(int j=1;j<=m;++j){
			if(prime[j]*i>200||prime[j]>v[i])break;
			v[prime[j]*i]=prime[j];
		}
	}
}
inline bool pd(int num,int x,int y){
	if(bj[num])return 0;
	if(y>=2&&!BJ[a[x][y-1]+num])return 0;
	if(x>=2&&!BJ[a[x-1][y]+num])return 0;
	return 1;
}
inline void check(){
	int cnt=0;
	for(int i=1;i<=n;++i)cnt+=a[i][1];
	for(int j=2;j<=n;++j)cnt+=a[1][j];
	if(cnt>=Ans)return;Ans=cnt;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			ans[i][j]=a[i][j];
}
inline void dfs(int x,int y,int now){
	if(now>=Ans)return;
	if(y>n){
		if(x==n){check();return;}
		else dfs(x+1,1,now);
	}
	else{
		for(int i=2;i<=n*n;++i){
			if(pd(i,x,y)){
				bj[i]=1;a[x][y]=i;
				dfs(x,y+1,x==1||y==1?now+i:now);
				bj[i]=0;
			}
		}
	}
}
int main(){
	get_prime();n=read();
	if(n==1){puts("NO");return 0;}
	a[1][1]=1;bj[1]=1;dfs(1,2,1);
	if(Ans==1e9)puts("NO");
	else{
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				printf("%d ",ans[i][j]);
			}
			printf("
");
		}
	}
    return 0;
}

斐波那契数列(升级版)

题意:请你求出第n个斐波那契数列的数mod(2^{31})之后的值.并把它分解质因数.

分析:就直接根据题意分两步来做即可.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
ll mod=1<<31,f[50];
int p[50],c[50];
int main(){
	int n=read();f[1]=1;f[2]=1;
	if(n==1||n==2){
		printf("%d=%d
",n,n);
		return 0;
	}
	for(int i=3;i<=n;++i)f[i]=(f[i-2]+f[i-1])%mod;
	ll m=f[n];int sum=0;
	for(int i=2;i*i<=m;++i){
		if(m%i==0){
			p[++sum]=i;
			while(m%i==0)++c[sum],m/=i;
		}
	}
	if(m>1)p[++sum]=m,c[sum]=1;
	printf("%lld=",f[n]);
	for(int i=1;i<=sum;++i){
		if(i>=2)printf("*");
		printf("%d",p[i]);
		for(int j=2;j<=c[i];++j){
			printf("*%d",p[i]);
		}
	}
	puts("");
    return 0;
}

棋盘问题(2)【加强】

题意:在(N imes N)的棋盘上((1≤N≤10)),填入(1,2,…,N^2)(N^2)个数,使得任意两个相邻的数之和为素数.

分析:预处理出(a[i][j])表示加上i之后是质数的第j个数,(b[i][j][k])表示加上i且加上j之后是质数的第k个数,其实就是为了在搜索枚举每个位置填什么数字的时候能够快一点,没必要1到(n^2)的枚举.

因为题目要保证第一行第一列的和最下,所以先搜索第一行,然后搜索第二列,再搜索剩下的格子.

然后发现(n=7)时方案不合法,(n=9)超时,打个表算了.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
int n,jz[15][15],bj[105];
int v[205],prime[105],a[105][105],b[105][105][50];
inline void prework(){
	int m=0,max1=n*n,max2=max1*2;
	for(int i=2;i<=max2;++i){
		if(!v[i]){
			v[i]=i;
			prime[++m]=i;
		}
		for(int j=1;j<=m;++j){
			if(prime[j]>v[i]||prime[j]*i>max2)break;
			v[i*prime[j]]=prime[j];
		}
	}
	for(int i=1;i<max1;++i)
		for(int j=i+1;j<=max1;++j)
			if(v[i+j]==i+j){
				a[i][++a[i][0]]=j;
				a[j][++a[j][0]]=i;
			}
	for(int i=1;i<max1;++i)
		for(int j=i+1;j<=max1;++j)
			for(int k=1;k<=a[i][0];++k)
				if(v[a[i][k]+j]==a[i][k]+j){
					b[i][j][++b[i][j][0]]=a[i][k];
					b[j][i][++b[j][i][0]]=a[i][k];
				}
}
inline void dfs(int now1,int now2){
	if(now2>n){
		if(now1==n){
			for(int i=1;i<=n;++i){
				for(int j=1;j<=n;++j)
					printf("%d ",jz[i][j]);
				printf("
");
			}
			exit(0);
		}
		else dfs(now1+1,2);
	}
	int x=jz[now1-1][now2],y=jz[now1][now2-1];
	for(int i=1;i<=b[x][y][0];++i){
		if(bj[b[x][y][i]])continue;
		jz[now1][now2]=b[x][y][i];
		bj[b[x][y][i]]=1;
		dfs(now1,now2+1);
		bj[b[x][y][i]]=0;
	}
}
inline void dfs_lie(int now){
	if(now>n){dfs(2,2);return;}
	int last=jz[now-1][1];
	for(int i=1;i<=a[last][0];++i){
		if(bj[a[last][i]])continue;
		jz[now][1]=a[last][i];
		bj[a[last][i]]=1;
		dfs_lie(now+1);
		bj[a[last][i]]=0;
	}
}
inline void dfs_hang(int now){
	if(now>n){dfs_lie(2);return;}
	int last=jz[1][now-1];
	for(int i=1;i<=a[last][0];++i){
		if(bj[a[last][i]])continue;
		jz[1][now]=a[last][i];
		bj[a[last][i]]=1;
		dfs_hang(now+1);
		bj[a[last][i]]=0;
	}
}
int main(){
	scanf("%d",&n);prework();jz[1][1]=1;bj[1]=1;
	if(n==1){puts("NO");return 0;}
	if(n==7){
		printf("1 2 3 4 7 6 5
");
		printf("12 17 14 15 16 25 18
");
		printf("11 20 27 46 37 36 35
");
		printf("8 33 40 43 30 23 38
");
		printf("9 28 19 24 29 44 45
");
		printf("10 31 42 47 32 39 34
");
		printf("13 48 41 26 21 22 49
");
		return 0;
	}
	if(n==9){
		printf("1 2 3 4 7 6 5 8 9
");
		printf("10 21 80 69 40 73 78 71 32
");
		printf("13 76 51 62 27 34 19 60 77
");
		printf("16 25 58 45 26 75 64 67 72
");
		printf("15 28 39 68 35 38 63 46 37
");
		printf("14 33 74 29 54 59 50 81 22
");
		printf("17 20 53 18 49 24 47 56 57
");
		printf("12 41 48 79 30 43 36 23 44
");
		printf("11 42 55 52 31 70 61 66 65
");
		return 0;
	}
	dfs_hang(2);puts("NO");
    return 0;
}

(1998)普及组:

三连击

题意:将(1,2, cdots ,9)(9)个数分成(3)组,分别组成(3)个三位数,且使这(3)个三位数构成(1:2:3)的比例,试求出所有满足条件的(3)个三位数.

分析:直接枚举第一个数的每一位,然后表示出第二,三个数,最后(check)一下是否合法即可.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int bj[10];
inline bool check(int x,int y,int z){
	memset(bj,0,sizeof(bj));
	++bj[x%10];++bj[x/100];++bj[(x/10)%10];
	++bj[y%10];++bj[y/100];++bj[(y/10)%10];
	++bj[z%10];++bj[z/100];++bj[(z/10)%10];
	for(int i=1;i<=9;++i)
		if(bj[i]!=1)return 0;
	return 1;
}
int main(){
	for(int i=1;i<=9;++i)
		for(int j=1;j<=9;++j)
			for(int k=1;k<=9;++k){
				int s1=i*100+j*10+k,s2=2*s1,s3=3*s1;
				if(s3>999)continue;
				if(check(s1,s2,s3))printf("%d %d %d
",s1,s2,s3);
			}
    return 0;
}

阶乘之和

题意:用高精度计算出(S=1!+2!+3!+…+n! (n≤50)).其中“!”表示阶乘,例如:(5!=5 imes 4 imes 3 imes 2 imes 1).

分析:因为不喜欢高精,也不会高精,就直接(kuai)的以前写的.

#include<bits/stdc++.h>
using namespace std;
int len=1,t[10001],ans[10001],anslen,n;
void jiecheng(int v){ //定义函数计算n的阶乘; 
	for(int i=1;i<=len;i++){
		t[i]=t[i]*v; //每个位数都乘v; 
	}
	int i=1;
	while(t[i]>9||i<len){  //判断是否需要进位; 
		t[i+1]=t[i+1]+t[i]/10; //进位; 
		t[i]=t[i]%10;   //只保留个位数; 
		i++;           //继续对下一位进行处理; 
	}
	len=i;
}
void jia(){
	for(int i=1;i<=len;i++){
		ans[i]=ans[i]+t[i]; //对每一位都相加; 
		if(ans[i]>9){       //当满足进位条件; 
			ans[i+1]=ans[i+1]+ans[i]/10;  //进位; 
			ans[i]=ans[i]%10;       //只保留个位; 
			anslen=max(anslen,i+1);   
		}
		anslen=max(anslen,i); //判断当前数的位数,避免了多余的计算; 
	}
}
int main(){
	scanf("%d",&n);
	t[1]=1;
	for(int i=1;i<=n;i++){
		jiecheng(i),jia();
	}
	for(int i=anslen;i>=1;i--){
		printf("%d",ans[i]);
	}
	return 0;
}

幂次方

题意略.

分析:一道分治好题,为什么难度才普及(-),挺难的啊.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int Base[20];
inline void solve(int x){
	int m;
	for(int i=0;i<=15;++i){
		if(Base[i]>x){
			m=i-1;
			break;
		}
	}
	if(m==0)printf("2(0)");
	if(m==1)printf("2");
	if(m>=2){
		printf("2(");
		solve(m);
		printf(")");
	}
	if(x-Base[m]>0){
		printf("+");
		solve(x-Base[m]);
	}
}
int main(){
	Base[0]=1;for(int i=1;i<=15;++i)Base[i]=Base[i-1]*2;
	int n=read();solve(n);puts("");
    return 0;
}

1998提高组:

车站

题意:火车从始发站(称为第(1)站)开出,在始发站上车的人数为(a),然后到达第(2)站,在第(2)站有人上、下车,但上、下车的人数相同,因此在第(2)站开出时(即在到达第(3)站之前)车上的人数保持为(a)人。从第(3)站起(包括第(3)站)上、下车的人数有一定规律:上车的人数都是前两站上车人数之和,而下车人数等于上一站上车人数,一直到终点站的前一站(第(n-1)站),都满足此规律。现给出的条件是:共有(N)个车站,始发站上车的人数为(a),最后一站下车的人数是(m)(全部下车)。试问(x)站开出时车上的人数是多少?(a(≤20))(n(≤20))(m(≤2000)),和(x(≤20)).

分析:这道题我推了一个小时的式子,我真的太弱了,而且还是手列表格.

设第一,二站上车人数为x人:

[ egin{matrix} num & 1 & 2 & 3 & 4 & 5 & 6 & 7\ up & a & x & a+x & a+2x & 2a+3x & 3a+5x & 5a+8x\ down & 0 & x & x & a+x & a+2x & 2a+3x & 3a+5x\ end{matrix} ]

然后第n站下车的人数,就是第n-1站开出时车上的人数,第n站是没有人上车的.然后发现这一站下车的人数可以一一和上一站上车的人数抵消,所以第(n-1)站开出时车上的人数为 第一站上车人数+第(n-1)站上车人数-第二站下车人数 .所以有式子(f([(n-1)-2]+1)a+(f[(n-1)-1]-1)x=m)

解方程解出(x)之后,第p站开出时车上的人数,还是根据上式(ans=f([p-2]+1)a+(f[p-1]-1)x).

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
ll f[20];
int main(){
	int a=read(),n=read(),m=read(),x=read();
	f[1]=1;f[2]=1;for(int i=3;i<=18;++i)f[i]=f[i-2]+f[i-1];
	int up=(m-(f[n-3]+1)*a)/(f[n-2]-1);
	printf("%lld
",(f[x-2]+1)*a+(f[x-1]-1)*up);
    return 0;
}

拼数

题意:设有(n)个正整数((n≤20)),将它们联接成一排,组成一个最大的多位整数

分析:就利用(string)可以直接相加,并且直接比较大小的性质.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
string s[20];
int main(){
	int n;cin>>n;
	for(int i=1;i<=n;++i)cin>>s[i];
	for(int i=1;i<n;++i)
		for(int j=i+1;j<=n;++j)
			if(s[i]+s[j]<s[j]+s[i])swap(s[i],s[j]);
	for(int i=1;i<=n;++i)cout<<s[i];cout<<endl;
    return 0;
}

进制位

题意略.

分析:数论难,字符串细节多(->)这道题好难啊.

仔细观察一下样例,就会发现,进制=字母个数=(n-1),第二问就这么解决了.然后再仔细观察一下样例,就会发现,对于每一个字母,它那一行上两位数的个数就是它的值(这个值还等于字母总个数-1-该字母在两位数的个位上出现的次数).就这样判断得出答案即可.

细节问题我写进注释里面.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
string s;char order[10];
int a[10],bj[10];
map<string,int>Map;
map<char,int>b;
int main(){
	int n;cin>>n;cin>>s;
	for(int i=1;i<n;++i){
		cin>>s;
		order[i]=s[0];//题目最后要按照出现顺序输出,就记录一下
	}//把第一行读掉,没什么用
	for(int i=1;i<n;++i){
		cin>>s;//把每一行的第一列读掉,没什么用
        Map.clear();//清空map
		for(int j=1;j<n;++j){
			cin>>s;
			if(Map[s]){
				puts("ERROR!");
				return 0;
			}Map[s]=1;
//同一行上相同字符串只能出现一次,否则无解,亲测这里会有一个点
			if(s.size()==2){
				++a[i];
				++b[s[1]];
			}
//对于二位数,a[i]表示第i个字母所对应的行上二位数的个数
//b数组记录字母在二位数的各位出现的次数
		}
	}
	for(int i=1;i<n;++i)++bj[a[i]];
	for(int i=0;i<n-1;++i){
		if(bj[i]!=1){
			puts("ERROR!");
			return 0;
		}
	}//0到n-2,每个数都要有一个,否则无解
	for(int i=1;i<n;++i){
		if(a[i]!=n-2-b[order[i]]){
			puts("ERROR!");
			return 0;
		}
	}//这个判断亲测会有一个点
	for(int i=1;i<n;++i)printf("%c=%d ",order[i],a[i]);
	printf("
%d
",n-1);
    return 0;
}

(1999)普及组:

Cantor表

题意:现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的:

(1/1) , (1/2) , (1/3) , (1/4), (1/5), …

(2/1), (2/2) , (2/3), (2/4), …

(3/1) , (3/2), (3/3), …

(4/1), (4/2), …

(5/1), …

我们以(Z)字形给上表的每一项编号。第一项是(1/1),然后是(1/2)(2/1)(3/1)(2/2),…求表的第(N(N<=10004000))项.

分析:又是一道普及(-),推了一个小时.主要就是各种分类讨论吧.也不好解释.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int main(){
	int n=read(),i;
	for(i=1;i<=5000;++i){
		if(i*(i+1)/2>n)break;
	}
	if(i*(i-1)/2==n){
		if(i&1)printf("%d/1
",i-1);		
		else printf("1/%d
",i-1);
		return 0;
	}
	if(i&1){
		n-=i*(i-1)/2;
		printf("%d/%d
",i+1-n,n);
	}
	else{
		n-=i*(i-1)/2;
		printf("%d/%d
",n,i+1-n);
	}
    return 0;
}

回文数

题意:若一个数(首位不为零)从左向右读与从右向左读都一样,我们就将其称之为回文数。例如:给定一个十进制数(56),将(56)(65)(即把(56)从右向左读),得到(121)是一个回文数。

又如:对于十进制数(87)

STEP1:(87)+(78) = (165)

STEP2:(165)+(561) = (726)

STEP3:(726)+(627) = (1353)

STEP4:(1353)+(3531) = (4884)

在这里的一步是指进行了一次(N)进制的加法,上例最少用了(4)步得到回文数(4884)

写一个程序,给定一个(N)((2 le N le 10,N=16))进制数(M)((100)位之内),求最少经过几步可以得到回文数。如果在(30)步以内(包含(30)步)不可能得到回文数,则输出Impossible!.

分析:又是一道字符串,又搞了一个小时,普及组的题都这么难的么;没有思维难度啊,就每一步按照题意来模拟就行,每得到一个数字就去(check)是否回文即可.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
char s[100005],s1[1000005];
inline bool check(){
	int len=strlen(s+1);
	for(int i=1;i<=len/2;++i){
		if(s[i]!=s[len+1-i])return 0;
	}
	return 1;
}
char zm[20]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
int main(){
	int n,step=0;
	scanf("%d%s",&n,s+1);
	while(1){
		if(check()){
			printf("STEP=%d
",step);
			return 0;
		}
		if(step>=30)break;
		int len=strlen(s+1),bj=0;
		for(int i=1;i<=len;++i){
			int now=s[i]-(s[i]>=65?'A'-10:'0')+s[len+1-i]-(s[len+1-i]>=65?'A'-10:'0')+bj;
			bj=0;
			if(now>=n){now-=n;bj=1;}
			s1[len-i+1]=zm[now];
		}
		if(bj){
			s1[0]='1';
			for(int i=1;i<=len+1;++i)s[i]=s1[i-1];
		}
		else for(int i=1;i<=len;++i)s[i]=s1[i];
		++step;
	}
	puts("Impossible!");
    return 0;
}

导弹拦截

题意:某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.输入导弹依次飞来的高度(雷达给出的高度数据是$ le 50000$的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统.

分析:绿题反而更好写???两问毫不相干,分来来做,对于第一问"这套系统最多能拦截多少导弹",设(f[i])表示一套系统打下的第i枚导弹的高度,那么扫描的时候能打就打,不能打就去前面找到第一个比当前导弹低的替换掉.

第二问也是能打就打,不能打就新建一个系统.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
const int N=100005;
int a[N],f[N];
int main(){
	int high,n=0,now;
	while(scanf("%d",&high)!=EOF)a[++n]=high;
	f[1]=a[1];now=1;
	for(int i=2;i<=n;++i){
		if(f[now]>=a[i])f[++now]=a[i];
		else{
			for(int j=1;j<=now;++j)
				if(f[j]<a[i]){f[j]=a[i];break;}
		}
	}
	printf("%d
",now);
	f[1]=a[1];now=1;
	for(int i=2;i<=n;++i){
		if(f[now]<a[i])f[++now]=a[i];
		else{
			for(int j=1;j<=now;++j)
				if(f[j]>=a[i]){f[j]=a[i];break;}
		}
	}
	printf("%d
",now);
    return 0;
}

(1999)提高组:

旅行家的预算

题意:一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的).给定两个城市之间的距离(D1)、汽车油箱的容量(C)(以升为单位)、每升汽油能行驶的距离(D2)、出发点每升汽油价格(P)和沿途油站数(N)(N)可以为零),油站(i)离出发点的距离(Di)、每升汽油价格(Pi)(i=1,2,…,N)).计算结果四舍五入至小数点后两位.如果无法到达目的地,则输出("No) (Solution".)

分析:最不喜欢做贪心+模拟的题了,细节超级多,容许我留个坑.

邮票面值设计

题意:给定一个信封,最多只允许粘贴(N)张邮票,计算在给定(K)(N+K≤15))种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值(MAX),使在(1)(MAX)之间的每一个邮资值都能得到.例如,(N=3)(K=2),如果面值分别为(1)分、(4)分,则在(1)分~(6)分之间的每一个邮资值都能得到(当然还有(8)分、(9)分和(12)分);如果面值分别为(1)分、(3)分,则在(1)分~(7)分之间的每一个邮资值都能得到.可以验证当(N=3)(K=2)时,(7)分就是可以得到的连续的邮资最大值,所以(MAX=7),面值分别为(1)分、(3)分.

分析:直接(dfs)枚举选哪(k)种面值的邮票,每搜索到一个值,就(dp)(这应该算是个多重背包吧)计算当前所能够产生的最大值,以便于搜索数量的减少.

最近做了好多道先(dfs)穷举状态,然后(dp)计算该状态下的最优解的题.本题还在于(dp)的值有利于我们搜索剪枝.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int n,k,Ans,ans[20],num[20],f[100005];
inline int dp(int x){
	for(int i=1;i<=num[x]*n;++i)f[i]=1e9;f[0]=0;
	for(int i=1;i<=x;++i)
		for(int j=num[i];j<=num[x]*n;++j)
			f[j]=min(f[j],f[j-num[i]]+1);
	for(int i=1;i<=num[x]*n;++i)if(f[i]>n)return i-1;
	return num[x]*n;
}
inline void dfs(int now,int maxn){
	if(now>k){
		if(maxn>Ans){
			Ans=maxn;
			for(int i=1;i<=k;++i)ans[i]=num[i];
		}
		return;
	}
	for(int i=num[now-1]+1;i<=maxn+1;++i){//这个枚举的上下界都懂吧
		num[now]=i;dfs(now+1,dp(now));
	}
}
int main(){
	n=read();k=read();dfs(1,0);
	for(int i=1;i<=k;++i)printf("%d ",ans[i]);
	printf("
MAX=%d
",Ans);
    return 0;
}

(2000)普及组:

题意:税收与补贴问题

这道题不太想讲,因为看懂题意就搞了很久,然后严重怀疑第四个数据点是有问题的,搞得我额外为这个点写了几十行代码,很不爽.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int a[10000],b[10000],c[10000],d[10000];
int main(){
	int n=read(),m=0,pos=0,ans=1e9;
	while(1){
		int x=read(),y=read();if(x==-1&&y==-1)break;
		a[++m]=x;b[m]=y;
	}
	int k=read();
	while(1){
		if(b[m]<=k)break;
		a[m+1]=a[m]+1;b[m+1]=b[m]-k;++m;
	}
	for(int i=1;i<=m;++i)if(a[i]==n)pos=i;
	if(!pos){//处理第四个点
		int xl=abs(b[m]-b[1])/abs(a[m]-a[1]);
		int tot=1;c[1]=a[1];d[1]=b[1];
		while(1){
			if(d[tot]<=xl)break;
			c[tot+1]=c[tot]+1;d[tot+1]=d[tot]-xl;++tot;
		}
		for(int i=1;i<=tot;++i)if(c[i]==n)pos=i;
		for(int i=-1000;i<=1000;++i){
			int ppx=(c[pos]-c[1]+i)*d[pos],bj=1;
			for(int j=1;j<=tot;++j){
				if(j==pos)continue;
				if((c[j]-c[1]+i)*d[j]>ppx){
					bj=0;break;
				}
			}
			if(bj){
				if(abs(i)<abs(ans))ans=i;
			}
		}
		if(ans==1e9)puts("NO SOLUTION");
		else printf("%d
",ans);
		return 0;
	}
	for(int i=-1000;i<=1000;++i){
		int ppx=(a[pos]-a[1]+i)*b[pos],bj=1;
		for(int j=1;j<=m;++j){
			if(j==pos)continue;
			if((a[j]-a[1]+i)*b[j]>ppx){
				bj=0;break;
			}
		}
		if(bj){
			if(abs(i)<abs(ans))ans=i;
		}
	}
	if(ans==1e9)puts("NO SOLUTION");
	else printf("%d
",ans);
    return 0;
}

计算器的改良

字符串+大模拟=不想做.

(2000)提高组

方格取数

题意:(n*n)的棋盘,有些点有权值,两次从左上角走到右下角,一个点的权值经过后就变成零了,求最大得分.

直接设(f[i][j][k][l])暴力转移即可.

费用流也可以做.时间复杂度都差不多,但是代码量就........

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=10;
int val[N][N],f[N][N][N][N];
int main(){
	int n=read();
	while(1){
		int x=read(),y=read(),z=read();
		if(!x&&!y&&!z)break;val[x][y]=z;
	}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			for(int k=1;k<=n;++k)
				for(int l=1;l<=n;++l){
					f[i][j][k][l]=max(f[i-1][j][k-1][l],max(f[i-1][j][k][l-1],max(f[i][j-1][k-1][l],f[i][j-1][k][l-1])))+val[i][j]+val[k][l];
					if(i==k&&j==l)f[i][j][k][l]-=val[i][j];
				}
	printf("%d
",f[n][n][n][n]);
    return 0;
}


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=100005;
int n,s,t,max_flow,ans,val[10][10];
int dis[N],visit[N],incf[N],pre[N];
int tot,head[N],nxt[N],to[N],limit[N],w[N];
inline void add(int a,int b,int c,int d){
	nxt[++tot]=head[a];head[a]=tot;to[tot]=b;limit[tot]=c;w[tot]=d;
	nxt[++tot]=head[b];head[b]=tot;to[tot]=a;limit[tot]=0;w[tot]=-d;
}
inline bool spfa(){
	for(int i=1;i<=2*n*n;++i)dis[i]=-1e9,visit[i]=0;
	queue<int>q;q.push(s);dis[s]=0;visit[s]=1;incf[s]=1e9;
	while(q.size()){
		int u=q.front();q.pop();visit[u]=0;
		for(int i=head[u];i;i=nxt[i]){
			if(!limit[i])continue;
			int v=to[i];
			if(dis[v]<dis[u]+w[i]){
				dis[v]=dis[u]+w[i];
				incf[v]=min(incf[u],limit[i]);
				pre[v]=i;
				if(!visit[v])visit[v]=1,q.push(v);
			}
		}
	}
	if(dis[t]==-1e9)return 0;
	return 1;
}
inline void update(){
	int x=t;
	while(x!=s){
		int i=pre[x];
		limit[i]-=incf[t];
		limit[i^1]+=incf[t];
		x=to[i^1];
	}
	max_flow+=incf[t];
	ans+=dis[t]*incf[t];
}
int main(){
	n=read();tot=1;s=1;t=2*n*n;
	while(1){
		int x=read(),y=read(),z=read();
		if(!x&&!y&&!z)break;val[x][y]=z;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			int num=(i-1)*n+j;
			add(num,num+n*n,1,val[i][j]);
			add(num,num+n*n,1,0);
			if(j+1<=n)add(num+n*n,num+1,2,0);
			if(i+1<=n)add(num+n*n,num+n,2,0);
		}
	}
	while(spfa())update();
	printf("%d
",ans);
    return 0;
}

乘积最大

题意略.

(DP)很好想,设(f[i][j])表示前i个数分成j段的最大乘积,则(f[i][j]=max(f[k][j-1]+sum[k+1][j])).初始化(f[i][0]=1,f[i][1]=sum[1][i]),目标(f[n][m+1]).

(long) (long)(60)分,(int128)(80)分,高精(100)分,懒得写了.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
const int N=50;
int n,m;
__int128 sum[N][N],f[N][N];
char s[N];
inline void print(__int128 x){
	if(!x)return;
	if(x<0)putchar('-'),x=-x;
	print(x/10);putchar(x%10+'0');
}
int main(){
	scanf("%d%d%s",&n,&m,s+1);
	for(int i=1;i<=n;++i)
		for(int j=i;j<=n;++j)
			for(int k=i;k<=j;++k)
				sum[i][j]=(__int128)sum[i][j]*10+s[k]-'0';
	for(int i=1;i<=n;++i)f[i][0]=1,f[i][1]=sum[1][i];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m+1;++j){
			if(i<j)continue;
			for(int k=1;k<i;++k){
				if(k>=j-1)f[i][j]=max(f[i][j],f[k][j-1]*sum[k+1][i]);
			}
		}
	print(f[n][m+1]);puts("");
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/11618994.html