#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;
}