CSP前的做题计划

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

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

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

\(1997\)普及组/提高组:

棋盘问题1

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

分析:直接推式子.正方形:长和宽相等,直接枚举边长即可,\(\sum_{i=1}^{min(n,m)}(n-i+1)(m-i+1)\).矩形(包括正方形):长和宽可以不等,要分别枚举,\(\sum_{i=1}^n\sum_{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\n",ans1,ans2-ans1);
    return 0;
}

棋盘问题(2)

题意:在\(N \times 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("\n");
		}
	}
    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,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 \times 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("\n");
			}
			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\n");
		printf("12 17 14 15 16 25 18\n");
		printf("11 20 27 46 37 36 35\n");
		printf("8 33 40 43 30 23 38\n");
		printf("9 28 19 24 29 44 45\n");
		printf("10 31 42 47 32 39 34\n");
		printf("13 48 41 26 21 22 49\n");
		return 0;
	}
	if(n==9){
		printf("1 2 3 4 7 6 5 8 9\n");
		printf("10 21 80 69 40 73 78 71 32\n");
		printf("13 76 51 62 27 34 19 60 77\n");
		printf("16 25 58 45 26 75 64 67 72\n");
		printf("15 28 39 68 35 38 63 46 37\n");
		printf("14 33 74 29 54 59 50 81 22\n");
		printf("17 20 53 18 49 24 47 56 57\n");
		printf("12 41 48 79 30 43 36 23 44\n");
		printf("11 42 55 52 31 70 61 66 65\n");
		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\n",s1,s2,s3);
			}
    return 0;
}

阶乘之和

题意:用高精度计算出\(S=1!+2!+3!+…+n! (n≤50)\).其中“!”表示阶乘,例如:\(5!=5 \times 4 \times 3 \times 2 \times 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人:

\[ \begin{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\n",(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("\n%d\n",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\n",i-1);		
		else printf("1/%d\n",i-1);
		return 0;
	}
	if(i&1){
		n-=i*(i-1)/2;
		printf("%d/%d\n",i+1-n,n);
	}
	else{
		n-=i*(i-1)/2;
		printf("%d/%d\n",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\n",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\n",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\n",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("\nMAX=%d\n",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\n",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\n",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\n",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\n",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;
}

posted on 2019-10-03 07:50  PPXppx  阅读(203)  评论(0编辑  收藏  举报