tzoj题解:猫萌的寒假前集训队训练(寒假集训队训练复刻版)

如果有写的不清楚的地方可以问我,或者群里的大佬们

目录

1001

1002(这题没人看吧)

1003

1004

1005

1006

1007

1008

1009

1010

1011

1012

1013

1014

1015

 


1001

 关键信息:5位到6位的数,回文。

两种方法:

Plan A 先打一个回文数表,再循环10000-999999找到 各位和 等于n 的数,将其输出。

Plan B 先循环10000-999999找到 各位和 等于n 的数,再判断是否为回文数。

建议选择Plan B,比A快得多得多的多。

虽然我用了A也过了 ,但是

附上代码c++

#include<iostream>
#include<string>
#include<sstream>
using namespace std;

int x[1000010];

void huiwen()//回文数表
{
    for(int i=10000;i<1000000;i++)
    {
        string a;
        //string 容器,嫩好使的东西怎么能不用
        //与字符数组使用有相似之处
        ostringstream b;
        b<<i;
        a=b.str();
        //c++中数字转字符串 
        //itoa为非标准数字转字符串 某些编译器可能不能用
        //不喜欢就自己写个函数吧,奥利给
        int l=a.size(),j;
        //size()为a中元素个数
        for(j=0;j<l/2;j++)//判断从前往后三位与从后往前三位是否相等
            if(a[j]!=a[l-j-1])break;
        if(j==l/2)x[i]=1;
    }
}

int main()
{
    huiwen();
    int n;
    bool f=0;
    cin>>n;
    for(int i=10000;i<1000000;i++)
    {
        int d=i,sum=0;
        if(x[i]==0)continue;
        //非回文数跳过
        while(d)
        {
            sum+=d%10;
            d/=10;
        }
        //计算各位和
        if(sum==n)
        {
            cout<<i<<endl;
            f=1;
        }
    }
    if(!f)cout<<"-1"<<endl;
    //如果没有输出-1
}

1002(这题没人看吧)

 关键信息:串并联概率,输出百分号

串联:全部相乘

并联:1-每个不能工作的概率相乘

附上代码c

#include<stdio.h>
#include<string.h>

int main()
{
	int t;
	char s[20];
	int n;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%s",s);
		scanf("%d",&n);
		double a,res=1;
		if(strcmp(s,"parallel")==0)
        //并联
		for(int i=0;i<n;i++)
		{
			scanf("%lf%%",&a);
			res*=(1-a/100);
			if(i==n-1)
			res=1-res;
		}
		else
        //串联
		for(int i=0;i<n;i++)
		{
			scanf("%lf%%",&a);
			res*=a/100;
		}
		printf("%.2f%%\n",res*100);
	}
}

1003

 关键信息:流量当月不清0

题意读取:假设上月留下的流量为 d ,本月流量优先使用本月套餐内流量,然后再使用 d。

附上代码c++(其实只有输入输出)

#include<iostream>
using namespace std;

int spend(int use,int yue,int yuemon,int d)
{
    if(yue+d<use)
    {
        cout<<yuemon+(use-yue-d)<<endl;
        d=0;
    }
    else if(use>yue)
    {    
        cout<<yuemon<<endl;
        d=0;
    }
    else
    {
        cout<<yuemon<<endl;
        d=yue-use;
    }
    return d;
}

int main()
{
    int yue,yuemon;//套餐流量与套餐价格
    cin>>yue>>yuemon;
    yue*=1024;
    yuemon*=100;
    int d=0;//上月留下的流量
    int i=12;
    while(i--)
    {
        int use;//本月使用流量
        cin>>use;
        d=spend(use,yue,yuemon,d);
        //输出函数
    }
}

1004

 烦人题

关键信息:非法地址输出 双冒号表示法 ,去除前导0

陷阱:a 双冒号只出现一次

          b 双冒号在头尾中间情况不同,以及只有双冒号的情况

本题使用函数将大幅度简化复杂度。

各位最好自己也了解一下

本题函数作用:

sscanf 用于从已读取的字符串中,读出需要内容

transform用于将小写字母转大写

附上代码c++

代码最后 寻找 0:0 与 :0:0解析

0:0有可能找到的是1000:0 此时不能删为100

而:0:0无法判断开头双冒号的情况

故两者结合 可以保证答案正确

c++的函数非常好用,反正我原来c写的时输出条件判断都绕晕了,有大佬愿意指导一下吗

#include<iostream>
#include<string>
#include<stdlib.h>
#include<sstream>
#include<algorithm>
#include<cctype>
using namespace std;

int main()
{
    string s[8];
    char c[100];
    while(gets(c))//读取字符数组
    {
        int d[8],i;
        string str(c);//将字符数组初始化给string
        int l=str.size();//元素个数
        for(i=0;i<l;i++)//判断地址是否合法
        {
            if(!((str[i]>='0'&&str[i]<='9')||(str[i]>='A'&&str[i]<='F')||str[i]==':'))
            {
                cout<<"It's not a IPv6 address!"<<endl;
                break;
            }
        }
        if(i<l)continue;
        //相信你们已经看出来初始化string步骤有点多余。。。
        sscanf(c,"%X:%X:%X:%X:%X:%X:%X:%X",&d[0],&d[1],&d[2],&d[3],&d[4],&d[5],&d[6],&d[7]);
        //从字符数组中读出十六进制数
        for(i=0;i<8;i++)
        {
            ostringstream p;
            p<<hex<<d[i];
            s[i]=p.str();
        }
        //转化16进制数存入s
        //意义在于去除前导0
        //注意,这里存入的字母是小写的
        str=s[0];
        for(i=1;i<8;i++)
        {
            str.push_back(':');//在尾部加:
            str.append(s[i]);//在尾部连接字符串
            //也可以写为str+=s[i];
            //你问那为啥要用append?
            //装13不行?
        }
        //将字符串连接
        transform(str.begin(),str.end(),str.begin(),::toupper);
        //这行用于将字母大写化
        //string 的find:找到第一个出现的子串位置并返回
        //若没有找到 返回-1
        int x=str.find("0:0");
        if(x!=-1&&(x==0||str[x-1]==':'))//特判以免出现1000:0的情况被误删
        {
            str.erase(x,3);//从x位置开始删除3个元素
            while(x<str.size()&&str[x]==':'&&str[x+1]=='0')
                str.erase(x,2);
            if(x==str.size())str.insert(x,":");//如果位置在尾加:
            if(x==0)str.insert(x,":");//如果位置在头加:
            cout<<str<<endl;
        }
        else
        {
            x=str.find(":0:0");//出现1000:0的情况重新寻找
            if(x==-1)//还是没找到输出-1
            {
                cout<<str<<endl;
                continue;
            }
            str.erase(x+1,3);
            while(x+1<str.size()&&str[x+1]==':'&&str[x+2]=='0')
                str.erase(x+1,2);
            if(x+1==str.size())str.insert(x+1,":");
            if(x+1==0)str.insert(x+1,":");
            cout<<str<<endl;
        }
    }
}

1005

 关键信息:位置改变规律 

你问我咋做,当然是找规律

此题==判断编号1何时重新回到位置1

为了看的明白点 将编号设为a 位置则用x表示

        x在前n位时,洗牌一次变为2x

        x在后n位时,洗牌一次变为(x-n)*2-1

用dowhile 和 条件分支语句即可完成

算法的事能叫猜吗

附上代码

#include<iostream>
using namespace std;

int main()
{
    int n;
    while(cin>>n)
    {
        int m=0,d=1;
        do
        {
            if(d<=n)
                d*=2;
            else
                d=(d-n)*2-1;
            m++;//这是一个计数器
        } while(d!=1);
        cout<<m<<endl;
    }
}

1006

 别说你复数运算不会。

关键信息: 最简输出

若 虚部为1或-1 则 1 i 和 -1 i 写为 i 和 -i

若 实部为0 虚部不为0 省略实部

若 虚部为0 省略0i

附上代码c

#include<stdio.h>
#include<string.h>
double a,b,x,y;
double sa,sb;
char s[2];
void suan()
{
    //公式
    //其实乘除也简单,但是调用函数不帅吗
	if(s[0]=='*')
	{
		sa=a*x-b*y;
		sb=a*y+b*x;
	}
	else
	{
		sa=(a*x+b*y)/(x*x+y*y);
		sb=(b*x-a*y)/(x*x+y*y);
	}
}

void put()
{
	if(sa!=0)
	{
		printf("%.1f",sa);
		if(sb>0)printf("+");
	}
	if(sb!=0)
	{
		if(sb!=1&&sb!=-1)
			printf("%.1f",sb);
		if(sb==-1)printf("-");
		printf("i");
	}
	if(!sa&&!sb)printf("0.0");
	printf("\n");
}

int main()
{
	int n;
	scanf("%d",&n);
	while(n--)
	{
		scanf("%lf%lf%s%lf%lf",&a,&b,&s,&x,&y);
        //加减较简单,直接干就完事
		if(s[0]=='+')
		{
			sa=a+x;
			sb=b+y;
		}
		else if(s[0]=='-')
		{
			sa=a-x;
			sb=b-y;
		}
		else//调用乘除函数
			suan();
		put();//输出函数
	}
}

1007

关键信息:复习一门课的最高效率值

纯纯水题 

找到最小即可得出答案

但是我用了一种比较cool的方法(反正估计没人看这题)

//自娱自乐

#include<iostream>
#include<queue>
#include<functional>
using namespace std;

int main()
{
    int t;
    int n,m;
    cin>>t;
    while(t--)
    {
        priority_queue<int,vector<int>,greater<int>> q;
        //想不到吧
        //优先队列 一手大炮打蚊子
        //这里的用途 是 每存进去一个数会自动排序
        //快去和你的同学们炫耀 
        //哎你这题咋写的
        //啊你这就是逊哎 我可是用的优先队列
        cin>>n>>m;
        while(n--)
        {
            int d;
            cin>>d;
            q.push(d);
            //存入队列且排序
        }
        cout<<(100-q.top())*(100-q.top())<<endl;
        //队头即是最小值
    }
}

1008

 关键信息:无

这有啥方法吗?

要说卡到我原因是我不会算利息(对 是我)

#include<iostream>
#include<stdio.h>
using namespace std;

int main()
{
	int t,m;
	double n,a,b,c,d,e,x;
	cin>>t;
	
	while(t--)
	{
		cin>>n>>a>>b>>c>>d>>e>>x>>m;
		double q=a,w=e-n;
		q*=x/100;
		q*=12;
		double p=12*m;
		w*=x/100;
		w*=p;
		printf("%.2f %.2f %.2f %.2f\n",b,q+w,d,e-d-b-c-q);
	}
 } 

1009

 关键信息:寻找最长回文子串,字符数不超过255

由于不超过255,暴力就行了

若是字符数较大,则要用 马拉车 算法,篇幅较长,详情自己了解

附上代码c

#include<stdio.h>
#include<string.h>

char s[300];

int max(int x,int y)
{
	return x>y?x:y;
}

int main()
{
	while(scanf("%s",s)!=EOF)
	{
		int n=strlen(s);
		int i,j;
		int res=1;
		for(i=0;i<n-1;i++)
		{
			if(s[i]==s[i+1])//如果是偶数长度的回文子串
			{
				int d=2;
				int l=i,r=i+1;
				while(l-1>=0&&r+1<n&&s[--l]==s[++r])//向外扩散,不建议用后自增
				{
					d+=2;
				}
				res=max(res,d);
			}
			if(i-1>=0&&i+1<n&&s[i-1]==s[i+1])//奇数长度回文子串
			{
				int d=3;
				int l=i-1,r=i+1;
				while(l-1>=0&&r+1<n&&s[--l]==s[++r])//向外扩散
				{
					d+=2;
				}
				res=max(res,d);
			}
		}
		printf("%d\n",res); 
	}
}

1010

 关键信息: TRUE的个数

做法猜测:

a 概率从大到小排序

b 选出前L个为TRUE的概率相加

c R-L个有可能为TRUE的,故选择TRUE和FALSE中较大的一个概率相加,直到 已经选了R个正确的,进入d

d 由于对的已经被选完了,剩下全是错的,加上其错误的概率即可

运行后果然一致。(虽然此题在我看来就是很奇怪)

附上代码

#include<stdio.h>
#include<algorithm>
#include<functional>
using namespace std;

int main()
{
	int t;
	int L,R,n;
	double x[300];
	int i,j;
	scanf("%d",&t);
	for(j=1;j<=t;j++)
	{
		scanf("%d %d %d",&L,&R,&n);
		for(i=0;i<n;i++)
			scanf("%lf",&x[i]);
		sort(x,x+n,greater<double>());
        //此乃大到小快排
        //反正冒泡也行
		double res=0;
		for(i=0;i<n;i++)
		{
			if(L)
			{
				res+=x[i];
				R--;
				L--;
			}
			else
			{
				if(x[i]>0.5&&R)
				{
					res+=x[i];
					R--;
				}
				else res+=1-x[i];
			}
		}
		printf("Data Set %d:\n",j);
		printf("%.2f\n",res);
	}
}

1011

 难是不难,说不难的话有点难

直接上代码c

#include<stdio.h>
#include<string.h>
char num[10][10]={"zero","one","two","three","four","five","six","seven","eight","nine"};
char cmpy[3];
char a[3][10];
char cmp[3];
char s;

void putcmp(double x,double y)//存入正确的比较符号
{
	if(x>y)cmp[0]='>';
	else if(x<y)cmp[0]='<';
	else cmp[0]='=';
}

void yunsuan()//运算
{
	double b,c,d;
	for(int i=0;i<10;i++)
	{
		if(strcmp(num[i],a[0])==0)b=i;
		if(strcmp(num[i],a[1])==0)c=i;
		if(strcmp(num[i],a[2])==0)d=i;
	}
	if(s=='+')
		b+=c;
	else if(s=='-')
		b-=c;
	else if(s=='*')
		b*=c;
	else
		b/=c;
	putcmp(b,d);
}

int main()
{
	char d[100];
	while(gets(d))
	{
		memset(a,0,sizeof(a));
		memset(cmpy,0,sizeof(cmpy));
		int i,k=0,j=0;
		bool f=1;
		int l=strlen(d);
		for(i=0;i<l;i++)
		{
            //存入第一个数
			if(d[i]=='+'||d[i]=='-'||d[i]=='*'||d[i]=='/')
			{
				f=2;
				s=d[i];//存下运算符
				a[k][j]=='\0';
				k++;j=0;
			}
            //存第二第三个数以及比较符号
			else if(d[i]=='='||d[i]=='<'||d[i]=='>')
			{
				a[k][j]=='\0';
				k++;j=0;
				if(d[i]=='=')
				{
					cmpy[0]='=';
					cmpy[1]='\0';
				}
                //注意判断比较符号是否有两位
				else if(d[i]=='<'||'>')
				{
					cmpy[0]=d[i];
					cmpy[1]='\0';
					if(d[i+1]=='=')
					{
						cmpy[1]=d[i+1];
						cmpy[2]='\0';
						i++;
					}
				}
			}
			else a[k][j++]=d[i];
		}
		a[k][j]='\0';//最后一位加'\0'
        //判断除0
		if(strcmp(a[1],"zero")==0&&s=='/')
		{
			printf("expression  false\n");
			continue;
		}
        //调用函数运算,存入正确的比较符号到cmp
		yunsuan();
		//判断比较符号是否正确
		if(strcmp(cmpy,cmp)==0)printf("true\n");
		else
			printf("%s%c%s %s %s\n",a[0],s,a[1],cmp,a[2]);
	}
}

1012

关键信息:递归?

什么递归我们不熟,我和打表才是好兄弟 

递归太慢,打表才是真理

附上代码c

#include<stdio.h>
int main()
{
	int a,b,c;
	int w[30][30][30]={1};
	int i,j,k;
	for(i=0;i<=20;i++)
		for(j=0;j<=20;j++)
		{
			w[i][j][0]=1;
			w[0][i][j]=1;
			w[i][0][j]=1;
		 } 
	//进行一个表的打
	for(i=1;i<=20;i++)
		for(j=1;j<=20;j++)
			for(k=1;k<=20;k++)
			{
				if(i<j&&j<k)
				{
					w[i][j][k]=w[i][j][k-1]+w[i][j-1][k-1]-w[i][j-1][k];
				}
				else
				{
					w[i][j][k]=w[i-1][j][k]+w[i-1][j-1][k]+w[i-1][j][k-1]-w[i-1][j-1][k-1];
				}
			}
    
	while(scanf("%d%d%d",&a,&b,&c),!(a==-1&&b==-1&&c==-1))
	{
		if(a>20||b>20||c>20)
		{
			printf("w(%d, %d, %d) = %d\n",a,b,c,w[20][20][20]);
		}
		else if(a<=0||b<=0||c<=0)
		{
			printf("w(%d, %d, %d) = %d\n",a,b,c,w[0][0][0]);
		}
		else printf("w(%d, %d, %d) = %d\n",a,b,c,w[a][b][c]);
	}
}

1013

 什么? 并查集 什么?强联通

nonono 我选择找规律

左边出现的值,在右边也必定出现,并且左右值的 个数 都要等于n

附上代码c

#include<stdio.h>
#include<string.h>
int n,m;
int l[10010];
int r[10010];

int main()
{
	while(scanf("%d %d",&n,&m),n||m)
	{
		memset(l,0,sizeof(l));
		memset(r,0,sizeof(r));
		while(m--)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			if(a!=b)
			{
				l[a]++;//记录左边出现的值
				r[b]++;//记录右边出现的值
			}
		}
		int i;
		for(i=1;i<=n;i++)
		{
			if(r[i]==0||l[i]==0)break;
		}
		if(i==n+1)printf("Yes\n");
		else printf("No\n");
	}
} 

1014

 关键信息:水平竖直一步为1,斜走为sqrt(2)

找规律尔,看不出来就画图

a 判断n m是否为1

b 判断n*m的是奇是偶

附上代码c

#include<stdio.h>
#include<math.h>
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	if(n==1||m==1)printf("%.2f\n",(double)n*m*2-2);
	else if(n*m%2==0)
		printf("%.2f\n",(double)n*m);
	else
		printf("%.2f\n",(double)n*m-1+sqrt(2));
}

1015

关键信息:拓扑排序+优先队列

此处主要讲述为什么要根据 出度为0 倒着排序,若要学习拓扑排序 私下问我 或者 自行解决 。

如若根据 入度为0 正着排序

例如 5个数,4个要求

5 4

5 1

4 1

2 3

一开始入度为0的有 2 和 5 ,先存入 2 和 5 ,可以发现已经出错了,根据题意,应该是1在2前,而2已经被排到了第一位,故此法不可取。

而倒着排序则不会有此问题。

#include<vector>
#include<iostream>
#include<string>
#include<queue>
#include<string.h>
using namespace std;
#include<stdio.h>
#include<string.h>

vector<int> q[30010];//理解为动态二维数组即可
int in[30010],out[30010];//入度 出度
int topp[30010];
priority_queue<int> que;//优先队列,队头为大

void top(int n)
{
	int i,j;
	for(i=1;i<=n;i++)//存入出度为0的数
	{
		if(out[i]==0)
		{
			que.push(i);
		}
	}
	while(!que.empty())//队列不空
	{
		int p=que.top();
		topp[n--]=p;//倒着存入输出的数组
		que.pop();
		for(i=0;i<in[p];i++)
		{
			out[q[p][i]]--;
			if(out[q[p][i]]==0)//如果出现新的出度为0的数
			{
				que.push(q[p][i]);
			}
		}
	}
}

void get()//读取函数
{
	int a,b;
	scanf("%d%d",&a,&b);
	q[b].push_back(a);//记录先于b之前的数存到q[b]
	in[b]++;//b的入度
	out[a]++;//a的出度
}

int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		memset(q,0,sizeof(q));
		memset(in,0,sizeof(in));
		memset(out,0,sizeof(out));
		int n,m;
		int i,j;
		scanf("%d%d",&n,&m);
		int nn=n;
		for(i=1;i<=m;i++)
			get();
        
		top(n);//拓扑排序函数调用
		for(i=1;i<=nn;i++)
		{
			if(i!=1)printf(" ");
			printf("%d",topp[i]);
		}
		printf("\n");
	}
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值