20190514测试总结

博主分享了一场考试经历,包括多道题目,如Classroom Watch、字符金字塔、养猪等。对各题给出问题描述、输入输出及数据范围,还阐述了考试时的思路及失误,给出题解,如枚举范围优化、前缀和技巧、贪心与背包结合、树形DP、LCA应用等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

情人节你还考试(滑稽

怪不得我考的这么差。

考试历程
这场考试考的很迷糊,第一题说是一个送分题,那么大家就都想A掉,但是我又一时间想不出什么法子,于是汗一坨一坨地冒出来,然后又想兼顾后面几题,就都没有怎么的深入思考,导致分数不堪入目,可以说是倒数。难受的很。

气氛逐渐严肃起来

一.Classroom Watch

【问题描述】
给出一个正整数 n,现在问存在多少个 x,使得 x在十进制下的每一位之和加上 x 等于 n。

【输入】
共 1 行,一个正整数n 。

【输出】
第一行输出一个整数 m,表示有 m 个符合条件的 (若没有符合条件的 ,请只输出一个 )。
下面m行,每行一个 x 。
【数据范围】
1<=n<=109;
题目大意:给定一个n,求所有的满足以下条件的数:
该数+该数上的所有位上的数字之和=n

这真是一道可以用脚做来的题目,可是我想从1–n暴力枚举显然会超时,于是我用了一些奇奇怪怪的然并卵优化然而还是超时的一塌糊涂。

【题解】:其实我们会发现能满足条件的数一定不会比n小太多,因为一个位上最多提供9的额外代价,而最多有9位,那么最保险便可以从n-10000—n枚举这样也是不会超时的。

code

#include<bits/stdc++.h>
using namespace std;
int n,a[1000],top=0;
int main()
{   freopen("num.in","r",stdin);
    freopen("num.out","w",stdout);
	scanf("%d",&n);
	int m,sum=0;
	m=n;
	while(m>0)
	{
		sum++;//记录位数
		m/=10;
	}//拆 
	for(int i=n-sum*9;i<n;i++)//下限 
	{
		int p=i,ans=0;
		while(p>0)
		{
			ans+=p%10;
			p/=10;
		}
		ans+=i;
		if(ans==n)
		{
			a[++top]=i;
		}
	}
	printf("%d\n",top);
	for(int i=1;i<=top;i++)
	{
		printf("%d\n",a[i]);
	}
} 

心太浮躁,要深入思考

二.字符金字塔

【问题描述】
金字塔是很神奇的,小x决定创建自己的字符金字塔。
小x的字符金字塔规则是这样的,他的金字塔有N层,第一层一个字符,第二层两个字符,依次类推。并且金字塔的字符是按顺序重复的填写某一段字符,且每一行填写的顺序和上一行填写的顺序是完全反的(也就是常说的S型填充)。
现在的问题是,小x想知道,在某一行,哪个字符出现的次数。
【输入】
第一行,一个整数N。表示金字塔的高度。
接下来一行字符串,表示要填充的字符串。保证字符串的长度不超过10^6。保证字符串中全是大写字母。
第三行一个整数K,表示要查询的次数。保证K 在[1…50000]之间。
接下来K行,每行一个整数Y和一个字符ch,表示要查询第Y行,包含字符ch的个数。 保证Y在[1…N]之间。
【输出】
一共K行,第i行表示第i次查询的结果,即第X行ch出现的次数。
【数据范围】
50% N<=1000
70% 1<=N<=10^18 ,字符串的长度不超过10^5

考试时差点想出正解,但是不知道哪一个小步骤出错导致全盘皆输。

【题解】:这和S排列没有任何关系。
为了方便计算,我们先预处理出前i位j字母出现的个数,利用前缀和技巧。
由于数据量非常的dog,所以我们在做求和公式时要利用模运算。
利用第x-1行的剩余字符串和x行相互合并求解

code

#include<bits/stdc++.h>
using namespace std;
char a[1000011];
int k,size=0,sum[1000011][30]={ };
long long n,space,last,num;
int main()
{   //freopen("piramida.in","r",stdin);
    //freopen("piramida.out","w",stdout);
	scanf("%lld",&n);
	scanf("%s",a);
	size=strlen(a);
    for(int i=1;i<=size;i++)
	{
	  for(int j=0;j<26;j++)
       sum[i][j]=sum[i-1][j];
	  int u=(int)(a[i-1]-'A');
    	sum[i][u]++;
    }
    scanf("%d",&k);
    for(int K=1;K<=k;K++)
	{  
	   long long  x;
	   char s;
	   scanf("%lld ",&x);
	   scanf("%c",&s);
	   int u=(int)(s-'A');
	   //模运算
	   long long y=x;
	   //求x-1行前的字符总长度,方便求出x-1行多出来的字符串
	   if(y%2)
	    {
	       y--;
		   y/=2;
		   y=y%size;
		   long long z=x%size;
		   y*=z; 	
	    }
	   else 
        {
		    long long z=y-1;
        	y/=2;
        	y=y%size;
        	z%=size;
        	y=y*z;
		}
	    y%=size;
	    if(y+x<=size)//x-1行剩下的+x行无法组成完整的字符串 
	      {
		    printf("%lld\n",sum[x+y][u]-sum[y][u]);
			continue;
		  }
	 //x-1行剩余的部分和第x行开头一部分组合成一个完整的字符串 
	 space=x+y-size;//除去x-1行剩余的部分和第x行开头一部分组合成一个完整的字符串 后剩余的长度
     num=space/size;//还能装几个字符串
     last=space-num*size;//第x行还多来
     printf("%lld\n",sum[size][u]*(num+1)-sum[y][u]+sum[last][u]);
	} 
    return 0;
}

三.养猪

【问题描述】
你有一个猪圈,有N头猪,每天你最多可以杀一头猪卖钱,收益就是猪的体重。但是每过一天猪的体重都会下降P[i] (当然,如果猪的体重≤0了,利润自然就是0),问K天内你的最大获利。

【输入】
第一行两个正整数N、K;
第二行N个数表示猪的初始重量A[i];
第三行N个数表示P[i];

【输出】
一行一个整数表示最大的获利。

【数据范围】
20% 数据满足 1≤N≤20
100%数据满足1≤N≤1000, 体重≤105

考试中没深入思考直接一个DFS,完美爆炸。

【题解】:先思考一个贪心的问题,如果第i头猪的p[i]比第j头猪的p[j]要大,第i头猪一定先选。当两头猪p[i]相同时先选体重大的。sort一排序便可以,我们可以认为是一个预处理工作。

那么接下来便是如何选择k头猪使价值最大的问题,那么这样就类似于一个0/1背包,k为体积,价值便是每头猪的a[]-(k-1)*p[]

code

#include<bits/stdc++.h>
using namespace std;
int n,k,ans=-1;
int f[1001];//前i头猪,j天
struct fuc
{
	int v,p;
}a[1001];
bool mycmp(fuc a,fuc b)
{
	return a.p>b.p||(a.p==b.p&&a.v>b.v);
}
int main()
{   
    freopen("pig.in","r",stdin);
    freopen("pig.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	  scanf("%d",&a[i].v);
	for(int i=1;i<=n;i++)
	  scanf("%d",&a[i].p);
	sort(a+1,a+n+1,mycmp);
    for(int i=1;i<=n;i++)
      for(int j=k;j>=1;j--)
      {
      	int val=a[i].v-(j-1)*a[i].p;
      	if(val<=0) val=0;
      	f[j]=max(f[j],f[j-1]+val);
      	ans=max(ans,f[j]);
	  }
	printf("%d",ans);
	
}

四.叶子的染色

【问题描述】
给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根、内部结点和叶子均可)着以黑色或白色。
你的着色方案应该保证根结点到每个叶子的简单路径上都至少包含一个有色结点(哪怕是这个叶子本身)。
对于每个叶结点u,定义c[u]为从根结点到U的简单路径上最后一个有色结点的颜色。
给出每个c[u]的值,设计着色方案,使得着色结点的个数尽量少。

【输入】
第一行包含两个正整数m, n,其中n是叶子的个数,m是结点总数。结点编号为1,2,…,m,其中编号1,2,… ,n是叶子。
以下n行每行一个0或1的整数(0表示黑色,1表示白色),依次为c[1],c[2],…,c[n]。
以下m-1行每行两个整数a,b(1<=a < b <= m),表示结点a和b 有边相连。

【输出】
仅一个数,即着色结点数的最小值

【题解】
首先,我们可以证明:虽然这题无根,但无论是在哪个根上,答案都是一样的
证明:假如我们的根为x,y为与x相邻的非叶子节点,那么x与y的颜色不可能相同。
(如果颜色相同,y号节点就不需要染色)
而既然颜色不同,那么将根从x变成y对答案显然也不会产生影响。
那么我们就可以在n+1~m的范围中随机选取根
用f[x][0/1]表示x的子树中,最后一个点想要得到一个白色/黑色的最近祖先的最小代价

显然我们可以得到以下转移方程:

f[x][0]+=min(f[y][1],f[y][0]-1);//若子节点和父亲同色,子节点可以少染一个 
f[x][1]+=min(f[y][0],f[y][1]-1);

code

#include<bits/stdc++.h>
using namespace std;
#define Max 1887415157
int n,m;
int first[500010]={};
int tot=0;
int ans=Max;
int f[500010][2],c[500010]={};//用f[x][0/1]表示x的子树中,最后一个点想要得到一个白色/黑色的最近祖先的最小代价
int v[500010];
struct fuc{
	int x;
	int next;
}a[500010];
void add(int x,int y){
	tot++;
	a[tot].next=first[x];	
	a[tot].x=y;
	first[x]=tot;
}
void dfs(int x,int fa){
	if(c[x]==1&&fa)
	{
		f[x][v[x]]=1;
		f[x][v[x]^1]=Max;
		return;
	}
	f[x][0]=f[x][1]=1;
	for(int i=first[x];i;i=a[i].next)
	{
		int y=a[i].x;
		if(y==fa) continue;
		dfs(y,x);
		f[x][0]+=min(f[y][1],f[y][0]-1);//若子节点和父亲同色,子节点可以少染一个 
		f[x][1]+=min(f[y][0],f[y][1]-1);
	}
}
int main()
{
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;++i) scanf("%d",&v[i]);
	for(int i=1;i<n;++i){
		int x,y;
		scanf("%d %d",&x,&y);
		add(x,y); add(y,x);
		c[x]++; c[y]++;
	}
	dfs(m+1,0);
	ans=min(f[m+1][0],f[m+1][1]);
	printf("%d",ans);
	return 0;
}

五.紧急集合

【问题描述】
欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有N个等待点,有N-1条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。

参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币(用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点后,迅速在N个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费最少的组将是游戏的赢家。

小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?
【输入】
第一行两个正整数N和M(N<=500000,M<=500000),之间用一个空格隔开。分别表示等待点的个数(等待点也从1到N进行编号)和获奖所需要完成集合的次数。
随后有N-1行,每行用两个正整数A和B,之间用一个空格隔开,表示编号为A和编号为B的等待点之间有一条路。
接着还有M行,每行用三个正整数表示某次集合前小可可、小可可的朋友以及你所在等待点的编号。
【输出】
一共有M行,每行两个数P,C,用一个空格隔开。
其中第i行表示第i次集合点选择在编号为P的等待点,集合总共的花费是C个游戏币。
【数据范围】
40%的数据中N<=2000,M<=2000
100%的数据中,N<=500000,M<=500000

考试的时候我怎么没想到是个LCA板子题呢ε=ε=ε=(#>д<)ノ

我们可以证明满足条件的点一定是三个点中任意两个点的LCA上,且不会有两个答案。
那么本体就很裸了,求3个LCA,比较就出来了。

code

#include<bits/stdc++.h>
using namespace std;
struct fuc
{
	int x,v,next;
}a[5000001];
int first[500001],top=0,n,m,f[500101][30],dep[500011],t;
void add(int x,int to)
{
	++top;
	a[top].x=to;
	a[top].next=first[x];
	first[x]=top;
}
void dfs(int x,int fa)
{
	for(int i=first[x];i;i=a[i].next)
	{
		int y=a[i].x;
		if(y==fa) continue;
		dep[y]=dep[x]+1;
		f[y][0]=x;
		for(int j=1;j<=t;j++)
	      f[y][j]=f[f[y][j-1]][j-1];
		dfs(y,x);
	}
	
}
int lca(int x,int y)
{
   if(dep[x]>dep[y]) swap(x,y);
   for(int i=t;i>=0;i--)
     {
       if(dep[f[y][i]]>=dep[x]) y=f[y][i];	
	 } 
	if(x==y) return x;
	for(int i=t;i>=0;i--)
	{
		if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
	}
	return f[y][0];
}
int main()
{
	freopen("meet.in","r",stdin);
	freopen("meet.out","w",stdout);
	scanf("%d%d",&n,&m);
	t=log2(n);
	for(int i=1;i<n;i++)
	{ 
	  int x,y;
	  scanf("%d%d",&x,&y);
	  add(x,y);
	  add(y,x);
    }
    dep[1]=1;
    dfs(1,0);
    for(int i=1;i<=m;i++)
      {
      	int x,y,z,ans=99999999,p=0;
      	scanf("%d%d%d",&x,&y,&z);
      	int lcaxy,lcayz,lcazx,qx,qy,qz;
      	lcaxy=lca(x,y);
      	lcayz=lca(y,z);
      	lcazx=lca(x,z);
      	qx=dep[x]+dep[y]-dep[lcaxy]+dep[z]-2*dep[lca(lcaxy,z)];
      	qy=dep[y]+dep[z]-dep[lcayz]+dep[x]-2*dep[lca(lcayz,x)];
      	qz=dep[z]+dep[x]-dep[lcazx]+dep[y]-2*dep[lca(lcazx,y)];
      	if(ans>qx) ans=qx,p=lcaxy;
      	if(ans>qy) ans=qy,p=lcayz;
      	if(ans>qz) ans=qz,p=lcazx;
      	printf("%d %d\n",p,ans);
	  }
} 

妙啊

这次考试是真的萎了,好好吸取教训,认真反思,待下一次的翻盘。

你强任你强,我绝不服输!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值