2021/10/11 训练赛1 的个人小结

比赛在HDU进行,网址入口是User Login,密码是acm加训练室号码

1.   POJ 1260  Pearls  珍珠   线性DP

问题描述

在珍珠岛,每个人都喜欢珍珠。一家名为"皇家珍珠"的公司生产了大量带有珍珠的珠宝。皇家珍珠之所以有它的名字,是因为它向珍珠拉尼亚的王室交付。但它也为普通人生产手镯和项链。当然,这些人的珍珠质量比王室的珍珠质量要低得多。在珍珠珍珠被分成100个不同的质量类。质量等级由该质量类中单颗珍珠的价格确定。这个价格对于那个质量等级来说是独一无二的,而且价格总是比低质量类别的珍珠的价格高。

每个月,皇家珍珠的股票经理都会准备一份清单,列出每个质量等级所需的珍珠数量。珍珠是在当地珍珠市场购买的。每个质量等级每颗珍珠都有自己的价格,但对于某个质量等级的每笔完整交易,必须支付相当于该类别中十颗珍珠的额外金额。这是为了防止游客只买一颗珍珠。
此外,皇家明珠正遭受着全球经济放缓
的影响。因此,公司需要提高效率。首席财务官(首席财务官)发现,他有时可以通过购买比实际需要的更高质量的珍珠来省钱。只要价格保持不变,任何客户都会责怪皇家珍珠在手镯中放入更好的珍珠。
例如,10 欧元类别需要 5 颗珍珠,20 欧元类别需要 100 颗
珍珠。这通常要花费:(5+10)*10 = (100+10)*20 = 2350 欧元。
购买所有 105 颗 20 欧元类别的珍珠仅需花费:(5+100+10)*20 = 2300
欧元。
问题是,在首席财务官知道有多少珍珠最适合在更高质量的类别中购买之前,它需要大量的计算
工作。你被要求帮助皇家珍珠与计算机程序。
鉴于珍珠的数量和不同质量类别的每颗珍珠的价格,给出购买清单中所有东西所需的最低
价格。珍珠可以在要求的,或在更高质量的类购买,但不能在较低的类别购买。

输入

输入的第一行包含测试案例的数量。每个测试案例都以包含 c 类别数(1 <= c <= 100)的行开始。然后,c 行跟随,每个数字 ai 和 pi。第一个数字是一个班级需要的珍珠数量(1<= ai <= 1000)。第二个数字是该类中每颗珍珠皮的价格(1 <= pi <= 1000)。课程的品质(因此价格)按上升顺序给出。输入中的所有数字都是整数。

输出

对于每个测试案例,单行包含单个数字:购买列表中所有内容所需的最低价格。

示例输入

2
2
100 1
100 2
3
1 10
1 11
100 12

样品输出

330
1344

问题的解决:

状态表示:

dp[i] : 表示前i个珍珠的最低价格      (本题题干描述满足动态规划无后效性,当前状态与之前的状态有关)    

属性:min

状态计算:

状态转移方程为  dp[i]=min(dp[i],dp[j]+(sum[i]-sum[j]+10)*val[i])

括号内第二个式子的含义是:前 j 个数的情况已经确定下来了,j+1到i都由i的价值来计算,即前缀和数组相减得到 j+1到i的珍珠个数

注意点

1.由于min和max不一样,max的Dp数组一般有时候初始全为0,反正max可以挑到比0大的,但min数组不行,因为0比较小,dp[]很可能挑到0,所以要初始化dp的值

2.这题类似于最长上升子序列虽然数组是1维,但要有两个for循环,第二个j是为了让前面的j个数和现在的当前数建立联系,最长上升子序列的公式 dp[i]=max(dp[i],dp[j]+1)

代码

#include <iostream> 
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
int n,c,a,b;
const int N=200;
int sum[N];                      //前缀和数组 
int val[N];                        //存珍珠的价格 
int dp[N];
int main()
{
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d",&c);
        memset(sum,0,sizeof sum); //while循环,每次都要重置 
        memset(val,0,sizeof val);
        memset(dp,0,sizeof dp);
        
        for(int i=1;i<=c;i++) 
        {
            int a,p;
            scanf("%d%d",&a,&p);
            sum[i]=sum[i-1]+a;
            val[i]=p;
        }
        for(int i=1;i<=c;i++)
        {
            dp[i]=(sum[i]+10)*val[i];                      //dp[]中存的是全买一样的花费 
        for(int j=i-1;j>=1;j--)
        {
            dp[i]=min(dp[i],(sum[i]-sum[j]+10)*val[i]+dp[j]);                         //因为min和max不太一样min算下来的值很有可能被0取代,所以要在前面先算dp[]的值 
        }
        } 
        cout<<dp[c]<<endl;
    }
}

回文串

Problem Description

Write a program to determine whether a word is a palindrome. A palindrome is a sequence of characters that is identical to the string when the characters are placed in reverse order. For example, the following strings are palindromes: “ABCCBA”, “A”, and “AMA”. The following strings are not palindromes: “HELLO”, “ABAB” and “PPA”.

Input

The input file will consist of up to 100 lines, where each line contains at least 1 and at most 52 characters. Your program should stop processing the input when the input string equals “STOP”. You may assume that input file consists of exclusively uppercase letters; no lowercase letters, punctuation marks, digits, or whitespace will be included within each word.

Output

A single line of output should be generated for each string. The line should include “#”, followed by the problem number, followed by a colon and a space, followed by the string “YES” or “NO”.

Sample Input

ABCCBA
A
HELLO
ABAB
AMA
ABAB
PPA
STOP

Sample Output

#1: YES
#2: YES
#3: NO
#4: NO
#5: YES
#6: NO
#7: NO

这道题目不上代码了,具体想放出来的原因是,在While(  !EOF)里面的函数体,我定义了char a[]  ,它每次的任务是负责装一个字符串,但是循环后我发现会保留上个案例的答案在字符串里面,这是没有清空字符串 ,而用char定义的字符串如何清空呢,用 memset(s,'\0',sizeof s)

而用 string定义的可以 s.clear(),也可以     s="";

并且  s+=a[] ,是字符串的增加

还有 char a[N]  string s 也可以  if(s==a)


汉诺塔问题 HDOJ 2175 汉诺塔IX

Problem Description

1,2,...,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上.
在第1根柱子上的盘子是a[1],a[2],...,a[n]. a[1]=n,a[2]=n-1,...,a[n]=1.即a[1]是最下
面的盘子.把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上.
问第m次移动的是那一个盘子.

 

Input

每行2个整数n (1 ≤ n ≤ 63) ,m≤ 2^n-1.n=m=0退出

 

Output

输出第m次移动的盘子的号数.

 

Sample Input

 
 

63 1 63 2 0 0

 

Sample Output

 
 

1 2

题解

第一种方法 递归

为了防止以后的训练赛像这次一样想不出名堂,还是要一步一步理解汉诺塔问题

假设n是盘子的数量

1.n=1时

第一次--------1号盘--------     A------>C

2.n=2时

第一次--------1号盘--------     A------->B

第二次--------2号盘--------     A------->C

第三次--------1号盘--------     B------->C

:      

综上可知我们可以用 n=2次来模拟n>=2的情况

第一阶段--------n-1个盘--------     A------->B

第二阶段--------第n号盘--------     A------->C

第三阶段--------n-1个盘--------     B------->C

一共的移动次数是 sum=2^n-1次


问题规模是n

第一阶段的移动次数是 2^(n-1)-1次

第二阶段的移动次数是 1次

第三阶段的移动次数是 2^(n-1)-1次


所以对于输入一个m

如果m<=2^(n-1)-1,说明在第一阶段

如果m==2^(n-1)-1+1,说明在第二阶段

如果m>2^  (n-1)-1+1     说明在第三阶段 


#include <iostream> 
#include <cmath>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=64;
__int64 f[N];

int flag;
void dfs(int a,int b,int c,int n,__int64 m)
{
	if(flag)
	return;
	if(!n)
	return;
	if(f[n-1]==m)
	{
		cout<<n<<endl; 
		flag=1;
		return;
	}
	if(f[n-1]>m)  //中间值大于m 说明处于第一阶段 
	dfs(a,c,b,n-1,m);
	if(f[n-1]<m)
	dfs(b,a,c,n-1,m-f[n-1]);  //中间值小于m 说明处于第三阶段 
}
int main()
{
	f[0]=1;
	f[1]=2;
    for(int i=2;i<=N;i++)
	{
		f[i]=f[i-1]*2;
		//cout<<f[i]<<endl;
	}
	__int64 m;
   int n;
	while(scanf("%d%I64d",&n,&m)!=EOF&&n)
	{
		flag=0;
		int a=1,b=1,c=1;
	    dfs(a,b,c,n,m);  //盘数和第m次移动一并放入递归 
	}
	return 0;
}

第二种方法找规律

1.当n=1时

1

2.当n=2时

1   2   1

3.当n=3时

 2   1    1   2   1

4当n=4时

1   2   1   3   1   2   1     1   2   1   3   1   2   1  (可以观察到新来的4的位置是3的位置的两倍)

5.当n=5时

1   2   1   3   1   2   1   4   1   2   1   3   1   2   1     1   2   1   3   1   2   1   4   1   2   1   3   1   2   1

规律由此出来了

既然具有对称性,那么一定与数字2有关,

可知,没有经过对称变换的数字的位置

数字 1     2     3     4      5    

位置 1     2     4     8      16

当n为总盘数时,由此可以得出:

n=1时,数字1在2^(n-1)的位置上

n=2时,数字2在2^(n-1)的位置上

……

1   2   1   3   1   2   1   4   1   2   1   3   1   2   1     1   2   1   3   1   2   1   4   1   2   1   3   1   2   1

除了加粗部分

其他1的位置

3,5,7,9,11,13,1517……

其他2的位置

6,10,14,18,22……

其他3的位置

12,20,28……

可以看出呈现两倍的关系

#include <iostream> 
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
using namespace std;
int m,n;
int main()
{
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		if(n==0&&m==0)
		break;
		if(m%2!=0)
		{ 
		cout<<"1"<<endl;   //如果m是奇数,那么输出1 
		continue;
		} 
		else
		{
			int i=0;
			while(m%2==0)   //把它(偶数)变为一个奇数要几次,
			//这个几次再加1就是对应的数字 
			{
				m/=2;
				i++;
			}
			cout<<i+1<<endl;
		}
	}
}

取(m堆)石子游戏

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 6247    Accepted Submission(s): 3823


 
Problem Description
m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的中那一堆取走7个剩下3个.
 
 
Input
输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出.
 
 
Output
先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output.
 
 
Sample Input
 
  
2 45 45 3 3 6 9 5 5 7 8 9 10 0
 
 
Sample Output
 
  
No Yes 9 5 Yes 8 1 9 0 10 3
 

#include <iostream> 
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring> 
using namespace std;
const int N=2e5+10;
int m;
int f[N];
int main()
{
	while(scanf("%d",&m)!=EOF&&m)
	{
		int sum=0;
		for(int i=1;i<=m;i++)
		{
			cin>>f[i];
			sum^=f[i];
		}
		if(!sum)
		puts("No");
		else
		{
			puts("Yes");
			int res;
			for(int i=1;i<=m;i++)
			{
				 res=f[i]^sum;
				if(res<f[i])
				{
				 cout<<f[i]<<" "<<res<<endl;
			    }
		    }
		}
		memset(f,0,sizeof f);
	}
}

   这道题有个经验就是 异或值不能直接用,要先把它转化为int 值       

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值