(pku 1014) (hdu 1059) (zoj 1049) Dividing muhanshu

2024-02-14 10:32

本文主要是介绍(pku 1014) (hdu 1059) (zoj 1049) Dividing muhanshu,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

(pku 1014) (hdu 1059) (zoj 1049) Dividing

前几天看了刘老师关于母函数的课件,顺便找几个题目来热热身.看了1059的题目以后,很快我就把他和母函数联系起来了,于是就动手写了程序,没想到提交就wa掉了,后来发现是在多项式计算的地方出现了问题.后来一个师姐看我做这个题目,她也去动手做了起来.她用的是贪心的思想,开始问她是怎么做的时候,wa想的巧,可是后来验证这种想法是错误,这里贪心保证了局部最优,但是不是全局最优.但是给我的启发还是不小的,我们面队一个问题,要善于从不同的角度去分析他,解决他.同时要多多同他人交流,这样才能更好的提高自己.
对母函数做法修改好了,却发现超时.毕竟数据好大.无奈之下,我另劈方法,联想到硬币问题的dp解法,顿时豁然开朗,终于ac了.但是所要的时间还是挺多的,后来看看别人的论坛发现还有很多可以优化的地方,看来算法的魅力真是无穷的啊!
优化方案一:
#include<stdio.h>
bool opt[60000];
int num=0;
int mid,max;
int stone[7];
int input()
{
scanf("%d%d%d%d%d%d",&stone[1],&stone[2],&stone[3],&stone[4],&stone[5],&stone[6]);
if(!stone[6]&&!stone[1]&&!stone[2]&&!stone[3]&&!stone[4]&&!stone[5]) {return 1;} //读到末行
num++;
printf("Collection #%d:/n",num);
mid=0;
for(int i=1;i<=6;i++) mid+=stone[i]*i;
if (mid % 2 ==1) //明显的剪枝
{
printf("Can't be divided./n/n");
return 2;
}
else mid/=2; //我们的任务就是求opt
return 0;
}

void dp()
{
memset(opt,0,60000); //初始化,opt所有成员为false
opt[0]=true; //opt[0]显然是true
max=0; //当前最大值是0

for (int i=1;i<=6;i++)
{
if (stone[i]>0)
{
for(int j=max;j>=0;j--) // 从当前最大值开始往下算
if (opt[j]) //找到可行的j
{
if (opt[mid]) //判断是否已经求出结果
{
printf("Can be divided./n/n");
return;
}
for (int k=1;k<=stone[i];k++) //在刚找到的可行j基础上加石头.
{
if (j+k*i>mid || opt[j+k*i]) break; //如果已经大于总价值的一半mid,或opt[j+k*i]已计算,跳出循环
opt[j+k*i]=true;
}
}
}
max=max+i*stone[i]; //更新当前最大值
if (max>mid) max=mid; //如果当前最大值超过了mid,对其赋值为mid
}
printf("Can't be divided./n/n"); //如果从1到6循环完一遍后仍然没有算出opt[mid],说明无解
}

int main()
{
while (1)
{
int t=input();
switch (t)
{
case 1 : return 0; //读到末行,结束
case 2 : continue; //读到明显的剪枝
default : dp();
}
}
return 0;
}
优化方案二:
结论 对于任意一种珠宝的个数n,如果n>=8, 可以将n改写为 11(n为奇数) 或 12(n为偶数)。

证明:

对于任意一组数,6的个数为n(n>=8)

一、如果可以分成两堆,我们可以分成两种情况:
   1.
      两堆里都有6,那么我们可知:把n改为n-2,仍然可分。
(两堆各减一个6)
2. 只有一堆里有6,设为左边,那么左边的总和不小于6*8=48。
我们观察,5*6=6*5 ,4*3=6*2 , 3*2=6 , 2*3=6 , 1*6=6
而 5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45 < 48
由抽屉原理右边必然存在
(多于5个的5 或者 多于2个的4 或者 多于1个的3
或者 多于2个的2 或者 多于5个的1)
即右边至少存在一组数的和等于若干个6,比如右边有3个4,这样把左边的2个6与右边的3个4交换,则又出现左右都有6的情况。 根据1,我们还是可以把n改为n-2且可分的状态不变。
综合1,2。我们可以看出只要原来n的个数=8,我们就可以把它改为n-2,这样操作一直进行到n<8。我们可以得出结论,对于大于等于8的偶数,可以换成6。
对于大于8的奇数,可以换成7。换完之后仍然可分。

二、如果不能分成两堆:
显然改为n-2时同样也不能分,那么对于大于等于8的偶数,可以换成6;对于大于8的奇数,可以换成7。换完之后仍然不可分。

综合一、二,我们得出结论把不小于8的偶数改为8,大于8的奇数改为7,原来可分与否的性质不会改变。

以上是对6的讨论,同样的方法可以推出
5的个数 6*4 + 4*4 + 3*4 + 2*4 + 1*4 = 64 < 5*13
即5的个数多于12时,偶数换为12,奇数换为11
4的个数 6*1 + 5*3 + 3*3 + 2*1 + 1*3 = 35 < 4*9
即4的个数多于8时,偶数换为8,奇数换为7
3的个数 5*2 + 4*2 + 2*2 + 1*2 = 24 < 3*9
即3的个数多于8时,偶数换为8,奇数换为7
2的个数 5*1 + 3*1 + 1*1 = 9 < 2*5
即2的个数多于4时,偶数换为4,奇数换为3
1的个数 多于5则必然可分(在总数是偶数的前提下)

综上所述,
对于任意一种珠宝的个数n,如果n>=8, 可以将n改写为 11(n为奇数) 或 12(n为偶数)。

进一步分析:
对每个数(1-6),以上只是粗略的估计,可以进一步减少其最大有效取值,例如,
对于6,5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45
就有4和2不能同时出现,5和1不能同时出现,3个5和1个3不能同时出现,4个5不能和1个4同时出现等等,所以组合不出6的整数倍的情况的总价值至多为25,所以当6的个数大于6时,奇数可改为5,偶数可改为6。
1-5 也有类似情况。

为了得出精确值,下面先我们讨论这样一个数论命题。

命题:
可重复的从自然数集中取出n个数(n>=2),其中必有若干个数之和能被n整除。

证明:设取出的n个自然数为a1,a2,a3,.....an

考虑这样的n+1个数 0, a1, a1+a2 , a1+a2+a3 , ...... , a1+a2+a3+...+an, 由于自然数模n的剩余类有n个,所以以上n+1个数中必有两个同余。 这两个数的差必被n整除,而且这两个数的差就是原来的n个数中的一些数的和。
这就证明了命题。

由以上命题
对于6而言,我们至多从{1,2,3,4,5}中可重复的找出5个数使它们不能组合成6的倍数。
所以这些数的和小于等于5*5=25
对于5而言,我们至多从{1,2,3,4,6}中可重复的找出4个数使它们不能组合成5的倍数。
所以这些数的和小于等于6*4=24
对于4而言,我们至多从{1,2,3,5,6}中可重复的找出3个数使它们不能组合成4的倍数。
所以这些数的和小于等于3*6=18 , 然而,两个6就是4的倍数, 所以最多有一个6
此时不能有两个5(2*5+6=16是4的倍数), 最多才6 + 5 + 3 = 14 < 3*5 =15
所以这些数的和小于等于3*5=15
对于3而言,我们至多从{1,2,4,5,6}中可重复的找出2个数使它们不能组合成3的倍数。
所以这些数的和小于等于2*5=10

(6就是3的倍数,所以不能取6)

对于2而言,我们至多从{1,3,4,5,6}中可重复的找出1个数使它们不能组合成6的倍数。

所以这些数的和小于等于1*5=5



考虑到 4*6 < 25 < 5*6 , 我们可以算出6的最大有效个数为5 。

考虑到 4*5 < 24 < 5*5 , 我们可以算出5的最大有效个数为5 。但是其实应该修正为6, 如果遇到如下特殊情况,左边5个6,右边6个5。此时虽然左右可以交换,但是交换后仍然只有一边有5,与(一、2)中讨论情况不符。

考虑到 3*4 < 15 < 4*4 , 我们可以算出5的最大有效个数为4 。但是其实应该修正为5, 如果遇到如下特殊情况,左边4个5,右边5个4。此时虽然左右可以交换,但是交换后仍然只有一边有4,与(一、2)中讨论情况不符。

考虑到 3*3 < 10 < 4*3 , 我们可以算出5的最大有效个数为4 。但是其实应该修正为5, 如果遇到如下特殊情况,左边3个5,右边5个3。此时虽然左右可以交换,但是交换后仍然只有一边有3,与(一、2)中讨论情况不符。

考虑到 2*2 < 5 < 3*2 , 我们可以算出5的最大有效个数为3 。 但是其实应该修正为4,如果遇到如下特殊情况,左边1个3和1个5,右边4个2。此时虽然左右可以交换,但是交换后仍然只有一边有2,与(一、2)中讨论情况不符。


我们得出最后的精确结论:

奇数改为 偶数改为
6的个数大于5 5 4
5的个数大于6 5 6
4的个数大于5 5 4
3的个数大于5 5 4
2的个数大于4 3 4 



优化后的代码:
#include  < iostream >
using   namespace  std;

long  n[ 6 ];
long  sum;
const   long  MAX_N  =   60000 ;

int  dividable()
{
    
int  f[MAX_N];
    
for  ( int  i  =   0 ; i  <=  sum; i ++ )
        f[i] 
=   0 ;
    f[
0 =   1 ;
    
for  ( int  i  =   0 ; i  <   6 ; i ++ )
    {
        
for  ( int  j  =   1 ; j  <=  n[i]; j ++ )
        {
            
int   base   =  j  *  (i  +   1 );
            
if  ( base   >  sum)  break ;
            
for  ( int  k  =  sum  -  (i + 1 ); k  >=   base   -  i  -   1 ; k -- )
                
if  (f[k])
                    f[k 
+  i  +   1 =   1 ;
            
if  (f[sum])  return   1 ;
        }
    }
    
return  f[sum];
}

int  main()
{
    
long  cases  =   0 ;
    
while  ( true )
    {
        sum 
=   0 ;
        
for  ( long  i  =   0 ; i  <   6 ; i ++ )
        {
            cin 
>>  n[i];
        }
        
if  (n[ 5 >   5 ) n[ 5 =   4   +  n[ 5 %   2 ;
        
if  (n[ 4 >   6 ) n[ 4 =   6   -  n[ 4 %   2 ;
        
if  (n[ 3 >   5 ) n[ 3 =   4   +  n[ 3 %   2 ;
        
if  (n[ 2 >   5 ) n[ 2 =   4   +  n[ 2 %   2 ;
        
if  (n[ 1 >   4 ) n[ 1 =   4   -  n[ 1 %   2 ;
        
for  ( long  i  =   0 ; i  <   6 ; i ++ )
        {
            sum 
+=  n[i]  *  (i  +   1 );
        }
        
if  (sum  ==   0 )
            
break ;
        cases
++ ;
        cout 
<<   " Collection # "   <<  cases  <<   " :/n " ;
        
if  (sum  %   2   !=   0 )
        {
            cout 
<<   " Can't be divided./n/n " ;
            
continue ;
        }
        sum 
/=   2 ;
        
if  (dividable())
            cout 
<<   " Can be divided./n " ;
        
else
            cout 
<<   " Can't be divided./n " ;
        cout 
<<  endl;
    }
    
return   0 ;
}

这篇关于(pku 1014) (hdu 1059) (zoj 1049) Dividing muhanshu的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/708261

相关文章

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

hdu 2093 考试排名(sscanf)

模拟题。 直接从教程里拉解析。 因为表格里的数据格式不统一。有时候有"()",有时候又没有。而它也不会给我们提示。 这种情况下,就只能它它们统一看作字符串来处理了。现在就请出我们的主角sscanf()! sscanf 语法: #include int sscanf( const char *buffer, const char *format, ... ); 函数sscanf()和

hdu 2602 and poj 3624(01背包)

01背包的模板题。 hdu2602代码: #include<stdio.h>#include<string.h>const int MaxN = 1001;int max(int a, int b){return a > b ? a : b;}int w[MaxN];int v[MaxN];int dp[MaxN];int main(){int T;int N, V;s

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

hdu 3790 (单源最短路dijkstra)

题意: 每条边都有长度d 和花费p,给你起点s 终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。 解析: 考察对dijkstra的理解。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstrin

hdu 2489 (dfs枚举 + prim)

题意: 对于一棵顶点和边都有权值的树,使用下面的等式来计算Ratio 给定一个n 个顶点的完全图及它所有顶点和边的权值,找到一个该图含有m 个顶点的子图,并且让这个子图的Ratio 值在所有m 个顶点的树中最小。 解析: 因为数据量不大,先用dfs枚举搭配出m个子节点,算出点和,然后套个prim算出边和,每次比较大小即可。 dfs没有写好,A的老泪纵横。 错在把index在d

hdu 1102 uva 10397(最小生成树prim)

hdu 1102: 题意: 给一个邻接矩阵,给一些村庄间已经修的路,问最小生成树。 解析: 把已经修的路的权值改为0,套个prim()。 注意prim 最外层循坏为n-1。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstri

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时