ACM Steps_Chapter Seven_Section3

Holding Bin-Laden Captive!

#include<stdio.h>
 #include<string.h>
 int value[4] = {0,1,2,5} , c1[10000] , c2[10000];
 int num[4] , sum ;
 int main ()
 {
     while ( scanf ( "%d%d%d" , &num[1] , &num[2] , &num[3] ) , num[1] ||num[2] || num[3] )
     {
          sum = num[1] * 1 + num[2] * 2 + num[3] * 5 ;
          for ( int i = 0 ; i <= sum ;   ++ i )
          {
              c1[i] = 0 ;
              c2[i] = 0 ;
          }
          for ( int i = 0 ; i <= num[1] ; ++ i )
              c1[i] = 1 ;
          for ( int i = 0 ; i <= num[1]*1 ; ++ i )
              for ( int j = 0 ; j <= num[2]*2 ; j += 2 )
                  c2[j+i] += c1[i] ;
          for ( int i = 0 ; i <= num[2]*2+num[1]*1 ; ++ i )
          {
              c1[i] = c2[i] ;
              c2[i] = 0 ;
          }
          for ( int i = 0 ; i <= num[1]*1+num[2]*2 ; ++ i )
              for ( int j = 0 ; j <= num[3]*5 ; j += 5 )
                  c2[j+i] += c1[i] ;
          for ( int i = 0 ; i <= num[1]*1+num[2]*2+num[3]*5 ; ++i )
          {
              c1[i] = c2[i] ;
              c2[i] = 0 ;
          }
          int pos ; 
          for ( pos = 0 ; pos <= sum ; ++ pos )
          {
              if ( c1[pos] == 0 )
              {
                 printf ( "%d\n" , pos ) ;
                 break ;
              }
          }
          if ( pos == sum + 1 )
             printf ( "%d\n" , pos ) ;
     }
     return 0 ;
 }

Simple Addition Expression

/*HDU2451思想不是太难,要细心*/
/*
题目大意:有一个关于 简单加法表达式  的定义告诉你,就是  选一个数字i  
如果    i+  (i+1) +(i+2) 它的和,没有任何一位进位的话,那就是 一个ie简单加法表达式。
 
例如:11+12+13就是,而 13+14+15就不是,因为它个位进位了。
 
现在给你一个n,叫你求出小于n的值中有多少个i可以构成简单加法表达式。
 
解题思路:要想个位不进位,明显的个位数字只能取  0-2,其他高位不进位,
必然只能取0-3的数字啦,所以就是排列问题啦这
*/
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
int main()
{
	string s;
	while(cin>>s)
	{
		int i, ans = 0,temp;
		for(i = 0; i < s.length(); i++)
		{	
			//获取第i位上数字的值
			temp = s[i] - '0';
			//如果是最后一位,可选择0、1、2
			if( i == s.length() - 1)
			{
				if(temp < 3)
					ans = ans + temp ;
				else ans = ans + 3;
				continue;
			}
			//如果是最后一位,可选择0、1、2、3
			if(temp < 4)
				ans = ans + temp * pow(4.0, (s.length()-2-i)*1.0) * 3;
			else
			{
				ans = ans + pow(4.0, (s.length()-i-1)*1.0) * 3;
				break;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

N-dimensional Sphere

/*
题意:给出n维的n+1个坐标,求一个坐标,使得n+1到该坐标的距离相等。距离定义为{(x1, x2 ... xN)| ∑(xi-Xi)^2 = R^2 (i=1,2,...,N) }

首先,列出n+1个方程,显然二次项系数都为1,所以可以先把二次项消去,得到n个n元一次方程。

然后,开始高斯消元。由于java在n=50时,很久都出不了答案,c++又会爆longlong,所以我们可以选择模P。

ax=b的解等于ax=b(mod P)的解(x<P),这个很好证明。

所以在模P下,加减乘除都很容易搞定。除法用乘以逆元,乘法用二分加法,拓展欧几里德部分的乘法是不会溢出的。

为了方便处理,把输入所有的值加上一个偏移量。
*/
#include<cstdio>
 #include<cstring>
 #include<algorithm>
 typedef long long LL;
 #define MAXN 60
 #define P 200000000000000003LL
 #define S 100000000000000000LL
 using namespace std;
 LL x[MAXN], g[MAXN][MAXN], a[MAXN][MAXN], b[MAXN][MAXN];
 int n;
 inline LL Mod(LL x) {
     if (x >= P)
         return x - P;
     return x;
 }
 LL MulMod(LL a, LL b) {
     LL res;
     for (res = 0; b; b >>= 1) {
         if (b & 1)
             res = Mod(res + a);
         a = Mod(a + a);
     }
     return res;
 }
 LL ExtGcd(LL a, LL b, LL &x, LL &y) {
     if (b == 0) {
         x = 1;
         y = 0;
         return a;
     }
     LL t, d;
     d = ExtGcd(b, a % b, x, y);
     t = x;
     x = y;
     y = t - a / b * y;
     return d;
 }
 LL InvMod(LL a, LL n) {
     LL x, y;
     ExtGcd(a, n, x, y);
     return (x % n + n) % n;
 }
 void Gauss() {
     int i, j, k;
     LL inv, tmp;
     for (i = 0; i < n; i++) {
         for (j = i; j < n; j++) {
             if (g[j][i])
                 break;
         }
         if (i != j) {
             for (k = i; k <= n; k++)
                 swap(g[i][k], g[j][k]);
         }
         inv = InvMod(g[i][i], P);
         for (j = i + 1; j < n; j++) {
             if (g[j][i]) {
                 tmp = MulMod(g[j][i], inv);
                 for (k = i; k <= n; k++) {
                     g[j][k] -= MulMod(tmp, g[i][k]);
                     g[j][k] = (g[j][k] % P + P) % P;
                 }
             }
         }
     }
     for (i = n - 1; i >= 0; i--) {
         tmp = 0;
         for (j = i + 1; j < n; j++) {
             tmp += MulMod(x[j], g[i][j]);
             if (tmp >= P)
                 tmp -= P;
         }
         tmp = g[i][n] - tmp;
         tmp = (tmp % P + P) % P;
         x[i] = MulMod(tmp, InvMod(g[i][i], P));
     }
 }
 int main() {
     int c, ca = 1;
     int i, j;
     LL tmp;
     scanf("%d", &c);
     while (c--) {
         scanf("%d", &n);
         memset(g, 0, sizeof(g));
         memset(b, 0, sizeof(b));
         for (i = 0; i <= n; i++) {
             for (j = 0; j < n; j++) {
                 scanf("%I64d", &a[i][j]);
                 a[i][j] += S;
                 b[i][n] += MulMod(a[i][j], a[i][j]);
                 if (b[i][n] >= P)
                     b[i][n] -= P;
             }
         }
         for (i = 0; i < n; i++) {
             for (j = 0; j < n; j++) {
                 tmp = a[i + 1][j] - a[i][j];
                 tmp = (tmp % P + P) % P;
                 g[i][j] = MulMod(tmp, 2);
             }
             g[i][n] = b[i + 1][n] - b[i][n];
             g[i][n] = (g[i][n] % P + P) % P;
         }
         Gauss();
         printf("Case %d:\n", ca++);
         printf("%I64d", x[0] - S);
         for (i = 1; i < n; i++)
             printf(" %I64d", x[i] - S);
         putchar('\n');
     }
     return 0;
 }

Square Coins

#include<stdio.h>

#define max 310
int main()
{
    int coin[17] = {1,4,9,16,25,36,49,64,81,100,121,144,169,196,225,256,289}; //列出所有coin的面值
    int n,i,j,k, an[max], bn[max]; //an[]记录结果,bn[]记录中间值
    while(scanf("%d", &n) && n)
    {
        for(i = 0; i <= n; i ++) //赋初值
        {
            an[i] = 1;
            bn[i] = 0;
        }
        for(j = 1; j < 17; j ++) //从1到289
        {
            for(i = 0; i <= n; i ++)
            {
                for(k = 0; k + i <= n; k += coin[j])
                    bn[k + i] += an[i];
            }
            for(i = 0; i <= n; i ++)
            {
                an[i] = bn[i];
                bn[i] = 0;
            }
        }
        printf("%d\n", an[n]);
    }
    return 0;
}

Fruit

#include<stdio.h>
 #include<string.h>
 int main ()
 {
     int n , m ;
     int c1[110] , c2[110] , min[110] , max[110] ;
     while ( scanf ( "%d%d" , &n , &m ) != EOF )
     {
           for ( int i = 1 ; i <= n ; ++ i )
           {
               scanf ( "%d%d" , min + i , max + i ) ;
           }
           memset( c1 , 0 , sizeof (c1) ) ;
           memset( c2 , 0 , sizeof (c2) ) ;
           for ( int i = min[1] ; i <= max[1] ;  ++ i ) //
               c1[i] = 1 ;
           for ( int i = 2 ; i <= n ;  ++ i )
           {
               for ( int j = 0 ; j <= m ;  ++ j )
                   for ( int k = min[i] ; ( k + j <= m )&& ( k <= max[i] ) ; ++ k )
                       c2[k+j] += c1[j] ;
               for ( int j = 0 ; j <= m ; ++ j )
                   c1[j] = c2[j] , c2[j] = 0 ;
           }
           printf ( "%d\n" , c1[m] ) ;
     }
     return 0 ;
 }

The Balance

/*
每种砝码既可以放在右盘,又可以放在左盘,(若按左物右码来说),
放在左盘那就取减号,放在右盘就取加号。
*/
#include<stdio.h>
 int c1[10001] , c2[10001] ;
 int counts , n , num[101] , sum , nfind[10001] ;
 int main ()
 {
     while ( scanf( "%d" , &n ) != EOF )
     {
           sum = 0 , counts = 0 ;
           for ( int i = 1 ; i <= n ; ++ i )
           {
               scanf ( "%d" , num + i ) ;
               sum += num[i] ; 
           }
           for ( int i = 0 ; i <= sum ; ++ i )
           {
               c1[i] = 0 ;
               c2[i] = 0 ;
           }
           c1[0] = c1[num[1]] = 1 ;
           for ( int i = 2 ; i <= n ;  ++ i )
           {
               for ( int j = 0 ; j <= sum ; ++ j )
                   for ( int k = 0 ; j + k <= sum && k <= num[i] ; k += num[i] )
                   {
                       if ( j >= k ) c2[j-k] += c1[j] ;
                       else c2[k-j] += c1[j] ;
                       c2[k+j] += c1[j] ;
                   }
               for ( int j = 0 ; j <= sum ; ++ j )
                   c1[j] = c2[j] , c2[j] = 0 ;
           }
           for ( int i = 1 , j = 0 ; i <= sum ; ++ i )
           {
               if ( c1[i] == 0 ) 
               {
                    counts ++ ;
                    nfind[j++] = i ;
               }
           }
           if ( counts == 0 ) printf ( "0\n" ) ;
           else {
                       printf ( "%d\n" , counts ) ;
                       for ( int j = 0 ; j < counts ; ++ j )
                           printf ( "%d%c" , nfind[j] , j + 1 == counts ? '\n' : ' ' ) ; 
                }
     }
     return 0 ;
 }

String

/*
题意:求由n个1、m个0组成,并且任意前缀中1的个数不少于0的个数的字符串的个数,并模20100501。

分析:这题很赞,首先是模型的转化或者说是公式的推导,这里用到了一个非常巧妙的转化。

catalan

卡塔兰数的一般公式为 C(2n,n)/(n+1);


h(n)= h(0)*h(n-1) + h(1)*h(n-2) + ... + h(n-1)h(0) (其中n>=2),这是n阶递推关系; 
  还可以化简为1阶递推关系: 如h(n)=(4n-2)/(n+1)*h(n-1)(n>1) h(0)=1 
  该递推关系的解为:h(n)=C(2n,n)/(n+1)=P(2n,n)/(n+1)!=(2n)!/(n!*(n+1)!) (n=1,2,3,...) 
  卡 塔兰数例的前几项为(sequence A 0 0 0 1 0 8 in OEIS) [注: n = 0, 1, 2, 3, … n] 
  1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, …

我并不关心其解是怎么求出来的,我只想知道怎么用catalan数分析问题。 
  我总结了一下,最典型的三类应用:(实质上却都一样,无非是递归等式的应用,就看你能不能分解问题写出递归式了) 
  1.括号化问题。  
  矩阵链乘: P=a0×a1×a2×a3×……×an,共有(n+1)项,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?(h(n)种) 
  类似题目:有N个节点的二叉树共有多少种情形? 
  2.出栈次序问题。  
  一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列? 
  类似题目:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈) 
  * 
  * * 
  * * * 
  * * * * 
  * * * * * 
  形如这样的直角三角形网格,从左上角开始,只能向右走和向下走,问总共有多少种走法? 
  问题的由来:编号为 1 到 n 的 n 个元素,顺序的进入一个栈,则可能的出栈序列有多少种? 
  对问题的转化与思考:n 个元素进栈和出栈,总共要经历 n 次进栈和 n 次出栈。这就相当于对这 2n 步操作进行排列。 
  一个模型:一个 n*n 的正方形网格,从左上角顶点到右下角顶点,只能向右走和向下走。问共有多少种走法。如果将向右走对应上述问题的出栈,向下走对应上述问题的进栈,那么,可 以视此模型为对上述问题的具体描述。而解决此问题,只要在总共从左上角到右下角的2n步中,选定向右走的步数,即共有C(n 2n)中走法。 
  但是存在一个问题,如果走法越过了对角线,那么对应到上述问题是出栈数比入栈数多,这是不符合实际的。 
  对以上模型进行处理,对角线将以上正方形网格分成两部分,只留下包含对角线在内的下半部分,那么就不会出现越过对角线的问题。而这问题就是开始提出的问题。 
  ------------------------------------------------------- 
  问题等价于:n个1和n个0组成一2n位的2进制数,要求从左到右扫描,1的累计数不小于0的累计数,试求满足这条件的数有多少? 
  解答: 设P2n为这样所得的数的个数。在2n位上填入n个1的方案数为 C(n 2n) 
  不填1的其余n位自动填以数0。从C(n 2n)中减去不符合要求的方案数即为所求。 
  不合要求的数指的是从左而右扫描,出现0的累计数超过1的累计数的数。 
  不合要求的数的特征是从左而右扫描时,必然在某一奇数2m+1位上首先出现m+1个0的累计数,和m个1的累计数。 
  此 后的2(n-m)-1位上有n-m个1,n-m-1个0。如若把后面这部分2(n-m)-1位,0与1交换,使之成为n-m个0,n-m-1个1,结果得 1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n-1个1和n+1个0组成的一个排列。 
  反过来,任何一个 由n+1个0,n-1个1组成的2n位数,由于0的个数多2个,2n是偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面的部分,令0 和1互换,使之成为由n个0和n个1组成的2n位数。即n+1个0和n-1个1组成的2n位数,必对应于一个不合要求的数。 
  用上述方法建立了由n+1个0和n-1个1组成的2n位数,与由n个0和n个1组成的2n位数中从左向右扫描出现0的累计数超过1的累计数的数一一对应。 
  例如 10100101 
  是由4个0和4个1组成的8位2进制数。但从左而右扫描在第5位(显示为红色)出现0的累计数3超过1的累计数2,它对应于由3个1,5个0组成的10100010。 
  反过来 10100010 
  对应于 10100101 
  因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应,故有 
  P2n = C(n 2n)— C(n+1 2n) 
  这个结果是一个“卡塔兰数”Catalan 
  3.将多边行划分为三角形问题。 
  将一个凸多边形区域分成三角形区域的方法数? 
  类似题目:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果他 
  从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路? 
  类似题目:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?

本题:

所以最终结论就是C(n+m,n) – C(m+n,m – 1),结果再mod一个20100501就可以了
结论出来但是我这里还WA了很久,因为为了避免减法对mod的影响,我开始把公式化简成了
C(n+m,n) – C(m+n,m – 1) = (m+n)!* (n+1-m) / ((n+1)! * m!)
优化:

接下来就是求上式子模20100501的值,因为n、m很大,所以我们利用质因数分解来分解阶乘,然后分子分母抵消指数最后求指数幂的和即可,但是这样做还是会TLE。而用对于阶乘,有很多非常好的性质可以利用(不利用就TLE。。。),下面介绍N!的质因数分解,也就是求N!中x的幂,首先因为N! = 1*2*3*……*N,所以N!中x的幂就是各个数质因数分解后x的幂之和,考虑含1,2,……,N中含x^1的共有x^1,2*x^1,……,y*x^1共y个,其中y=floor(N/x^1),而含x^2的共有x^2,2*x^2,……,y*x^2共y个,其中y=floor(N/x^2)=floor((N/x)/x),所以可以利用递归来计算N!中x的幂:
*/
#include "cstdio"
#include "cstring"
#include "cstdlib"
#include "iostream"
using namespace std;
const int N=2000000;
const int mod=20100501;
bool ss[N+5];
int n1[N/10],n2[N/10],n3[N/10],pl[N/10];
int n,m,tot;
__int64 s1,s2;
void init()//打素数表
{
 int i,j;
 memset(ss,true,sizeof(ss));
 for(i=2;i*i<=N;i++)
 {
  if(ss[i])
  {
   for(j=2;i*j<=N;j++)
    ss[i*j]=false;
  }
 }
 tot=0;
 for(i=2;i<=N;i++)
  if(ss[i])
  {
   pl[tot++]=i;
  }
}
void Div(int *list,int n)
//将n!分解成素数因子连乘,分解后的指数存在list[]数组中
{
 int i,j,temp;
 for(i=0;i<tot;i++)
  list[i]=0;
 for(i=0;i<tot&&pl[i]<=n;i++)
 {
  temp=n;
  while(temp/pl[i])
  {
   list[i]+=temp/pl[i];
   temp/=pl[i];
  }
 }
}
__int64 pow(__int64 x,__int64 cf)
{
 if(cf==0)
  return 1;
 __int64 res=pow(x,cf/2);
 res=(res*res)%mod;
 if(cf%2)
  res*=x;
 res%=mod;
 return res;
}
int main()
{
 init();
 int n,t;
 register int i;
 scanf("%d",&t);
 while(t--)
 {
  scanf("%d%d",&n,&m);
  Div(n1,m+n);
  Div(n2,n);
  Div(n3,m);
  for(i=0;i<tot;i++)
  {
   n1[i]-=n2[i];
   n1[i]-=n3[i];
  }
  s1=1;
  for(i=0;i<tot;i++)
  {
   s1*=pow(pl[i],n1[i]);
   s1%=mod;
  }
  Div(n1,m+n);
  Div(n2,n+1);
  Div(n3,m-1);
  for(i=0;i<tot;i++)
  {
   n1[i]-=n2[i];
   n1[i]-=n3[i];
  }
  s2=1;
  for(i=0;i<tot;i++)
  {
   s2*=pow(pl[i],n1[i]);
   s2%=mod;
  }
  __int64 ans=s1-s2;
  ans=((ans%mod)+mod)%mod;
  printf("%I64d\n",ans);
 }
 return 0;
}

Rooted Trees Problem

/*
    求一棵n个节点树的方法数T[n]  子树不分顺序
    容易想到拆分,枚举子树怎么组成,然后乘以方法数
    如 5 = 1 + 2 + 2 
    由于子树不分顺序,所以枚举出来的相同规模的子树是一种有重集的组合 ,用公式C(n+m-1,m)
    然后用乘法原理乘起来即可
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
#include<queue>

using namespace std;


long long T[50];
int cnt[50] , n;

long long gcd(long long a, long long b)
{
    return b?gcd(b,a%b):a;
}

long long C(long long n, long long m)
{
    if(m > n - m ) m = n - m;
    long long mul = 1 , div = 1;
    for(int i = 0; i < m;i++)
    {
        mul *= (n-i);
        div *= (i+1);
        long long g = gcd(mul,div);
        mul/=g , div /= g;
    }
    return mul / div;
}

void dfs(int start, int left)
{
    if( left == 0 )
    {
        long long ans = 1;
        for(int i = 1; i<=n ;i ++)
        {
            if(cnt[i] == 0)continue;
            ans = ans * C(T[i] - 1 + cnt[i] , cnt[i]);//重集的组合
        }
        T[n] += ans;
        return ;
    }
   if(start > left )return;
   cnt[start]++;
   dfs(start , left - start);
   cnt[start]--;
   dfs(start+1 , left);
}
void init()
{
    T[1] = T[2] = 1;
    for(n = 3; n <= 40 ; n++)
    {
        dfs(1,n-1);
    }
}
int main() {

    init();
    while( ~scanf("%d",&n) )
        printf("%I64d\n",T[n]);
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值