zoukankan      html  css  js  c++  java
  • 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;
    }
    
    
  • 相关阅读:
    关于求 p_i != i and p_i != i+1 的方案数的思考过程
    poj 3041 Asteroids 二分图最小覆盖点
    poj 1325 Machine Schedule 最小顶点覆盖
    poj 1011 Sticks 减枝搜索
    poj 1469 COURSES 最大匹配
    zoj 1516 Uncle Tom's Inherited Land 最大独立边集合(最大匹配)
    Path Cover (路径覆盖)
    hdu 3530 SubSequence TwoPoint单调队列维护最值
    zoj 1654 Place the Rebots 最大独立集转换成二分图最大独立边(最大匹配)
    poj 1466 Girls and Boys 二分图最大独立子集
  • 原文地址:https://www.cnblogs.com/PPXppx/p/11618994.html
Copyright © 2011-2022 走看看