暑假训练DAY26组合数学

神、上帝以及老天爷

 HDU - 2048 

HDU 2006'10 ACM contest的颁奖晚会隆重开始了! 
为了活跃气氛,组织者举行了一个别开生面、奖品丰厚的抽奖活动,这个活动的具体要求是这样的: 

首先,所有参加晚会的人员都将一张写有自己名字的字条放入抽奖箱中; 
然后,待所有字条加入完毕,每人从箱中取一个字条; 
最后,如果取得的字条上写的就是自己的名字,那么“恭喜你,中奖了!” 

大家可以想象一下当时的气氛之热烈,毕竟中奖者的奖品是大家梦寐以求的Twins签名照呀!不过,正如所有试图设计的喜剧往往以悲剧结尾,这次抽奖活动最后竟然没有一个人中奖! 

我的神、上帝以及老天爷呀,怎么会这样呢? 

不过,先不要激动,现在问题来了,你能计算一下发生这种情况的概率吗? 

不会算?难道你也想以悲剧结尾?! 

Input

输入数据的第一行是一个整数C,表示测试实例的个数,然后是C 行数据,每行包含一个整数n(1<n<=20),表示参加抽奖的人数。 
 

Output

对于每个测试实例,请输出发生这种情况的百分比,每个实例的输出占一行, 结果保留两位小数(四舍五入),具体格式请参照sample output。 
 

Sample Input

1
2

Sample Output

50.00%

错排问题,可以用公式:

也可以直接通过DPans[i]=(i-1)*(ans[i-2]+ans[i-1]);

我采用的是DP:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>

using namespace std;

double ans[25];
int main()
{
	ans[0]=1;
	ans[1]=0;
	double tot=1;
	for(int i=2;i<=20;i++)
	{
		ans[i]=(i-1)*(ans[i-2]+ans[i-1]);
	}
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		cin>>n;
		tot=1;
		for(double i=2;i<=n;i++)
		tot*=i;
		//cout<<ans[n]<<endl;
		printf("%.2lf%%\n",ans[n]/tot*100);
	}
	return 0;
}

 

Halloween treats

 HDU - 1808 

Every year there is the same problem at Halloween: Each neighbour is only willing to give a certain total number of sweets on that day, no matter how many children call on him, so it may happen that a child will get nothing if it is too late. To avoid conflicts, the children have decided they will put all sweets together and then divide them evenly among themselves. From last year's experience of Halloween they know how many sweets they get from each neighbour. Since they care more about justice than about the number of sweets they get, they want to select a subset of the neighbours to visit, so that in sharing every child receives the same number of sweets. They will not be satisfied if they have any sweets left which cannot be divided. 

Your job is to help the children and present a solution. 
 

Input

The input contains several test cases. 
The first line of each test case contains two integers c and n (1 ≤ c ≤ n ≤ 100000), the number of children and the number of neighbours, respectively. The next line contains n space separated integers a 1 , ... , a n (1 ≤ a i ≤ 100000 ), where a i represents the number of sweets the children get if they visit neighbour i. 

The last test case is followed by two zeros. 
 

Output

For each test case output one line with the indices of the neighbours the children should select (here, index i corresponds to neighbour i who gives a total number of a i sweets). If there is no solution where each child gets at least one sweet, print "no sweets" instead. Note that if there are several solutions where each child gets at least one sweet, you may print any of them. 

Sample Input

4 5
1 2 3 7 5
3 6
7 11 2 5 13 17
0 0

Sample Output

3 5
2 3 4

题意不难理解就是给出n个数,从中任意取出一些数使他们的和可以被另一个数K整除。

思路:本题采用前缀和和鸽笼定理的思想。在求出前缀和后对所有的前缀和对K求模,假如有求模结果直接为0,则从第一项加到那一项的结果自然为所要的数,否则如有两个求模结果相同的前缀和,则在他们之间的就是所要 的序列(不难证明,正是由于加上了一段求模为0 的序列才可以使前缀和不变)

代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#define md(l,r) (l+r)>>1
#define rson(x) x<<1|1
#define lson(x) x<<1
#define endl '\n'
#define sc(x) scanf("%lld",&x)

using namespace std;
typedef long long ll;
const int size=1e5+5;
ll sum[size],arr[size];
vector<int> No[size];
int main()
{
	ll c,n;
	while(~scanf("%lld%lld",&c,&n)&&n&&c)
	{
		for(int i=1;i<c;i++) No[i].clear();
		ll ans=0;
		for(int i=1;i<=n;i++)
		{
			sc(arr[i]);
			sum[i]=(sum[i-1]+arr[i])%c;
			No[sum[i]].push_back(i);
			if(sum[i]==0) ans=i;
		} 
		if(ans!=0)
		{
			for(int i=1;i<=ans;i++)
			{
				printf("%d ",i);
			}
			printf("\n");
		}
		else
		{
			for(int i=1;i<c;i++)
			{
				if(No[i].size()>=2)
				{
					ans=i;
					break;
				}
			}
			if(!ans) cout<<"no sweets"<<endl;
			else 
			{
				int l=No[ans][0],r=No[ans][1];
				if(l>r) swap(l,r); 
				for(int i=l+1;i<=r;i++)
				{
					printf("%d ",i);
				}
				cout<<endl;
			}
		}
		
	}
	return 0;
}

Dictionary

 CSU - 1828 

The isolated people of MacGuffin Island have a unique culture, and one of the most interesting things about them is their language. Their alphabet consists of the first 9 letters of the Roman alphabet (a, b, c, d, e, f, g, h, i). All of their words are exactly 9 letters long and use each of these 9 letters exactly once. They have a word for every possible permutation of these letters. In the library of their most sacred temple is a dictionary, and each word in their language has its own page. By coincidence they order their words exactly as they would be in ordered in English, so the word ‘abcdefghi’ is on the first page, and the word ‘ihgfedcba’ is on the last. The question is, given a list of random words from the MacGuffin language, can you say on which page of the MacGuffin dictionary each appears?

Input

The first line of the input file is a positive integer. This integer tells you how many words will follow. The upper limit for this number is 6000. Every subsequent line contains a single word from the MacGuffin language, so if the first number is 1000 there will be 1000 lines after it, each containing a single word.

Output

Each line of output will contain an integer. This integer should be the page number for the corresponding word.

Sample Input

4
abcdefgih
abcdefghi
abcdefgih
ihgfedcba

Sample Output

2
1
2
362880

这题是求一个字符串在他的排列中为第几种?

思路:(1)用next_permutation先求出所有的排列与他们的位次,并用Hash处理过后用map来储存,直接查就好了。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#define md(l,r) (l+r)>>1
#define rson(x) x<<1|1
#define lson(x) x<<1
#define endl '\n'
#define sc(x) scanf("%d",&x)

using namespace std;
typedef unsigned long long ULL;
const int HA=2333;
ULL Hash(char *s)
{
	ULL H=0;
	for(int i=0;i<9;i++)
	{
		H=H*9+s[i]-'a';
	}
	return H;
}
map<ULL,int> mp;
void init()
{
	char ori[10]="abcdefghi";
	int cnt=1;
	do{
		mp[Hash(ori)]=cnt++;
	}while(next_permutation(ori,ori+9));
}
int main()
{
	int n;
	init();
	while(~scanf("%d",&n))
	{
		char s[10];
		while(n--)
		{
			scanf("%s",s);
			printf("%d\n",mp[Hash(s)]);
		}
	}
	return 0;
}

这题数据比较小,轻松可以观察出来还是明显可以过 的。

但是显然,如果用康托展开的话在速度上会优秀很多,而且可扩展性也会好很多。

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int size=10;
ll H[size];
char s[size];
void init()
{
	H[1]=1;
	for(int i=2;i<=9;i++)
	{
		H[i]=H[i-1]*i;
	}
}
ll contor(char *s)
{
	ll ans=0;
	for(int i=0;i<9;i++)
	{
		int tm=0;
		for(int j=i+1;j<9;j++)
		if(s[j]<s[i]) ++tm;
		ans+=tm*H[8-i];
	}
	return ans;
}
int main()
{
	int t;
	init();
	scanf("%d",&t);
	while(t--)
	{
		char s[10];
		scanf("%s",s);
		printf("%lld\n",contor(s)+1);
	}
}

上面那个Hash的代码用了260ms而康托展开只用了4ms,可见优越性

How many integers can you find

 HDU - 1796 

 Now you get a number N, and a M-integers set, you should find out how many integers which are small than N, that they can divided exactly by any integers in the set. For example, N=12, and M-integer set is {2,3}, so there is another set {2,3,4,6,8,9,10}, all the integers of the set can be divided exactly by 2 or 3. As a result, you just output the number 7.

Input

  There are a lot of cases. For each case, the first line contains two integers N and M. The follow line contains the M integers, and all of them are different from each other. 0<N<2^31,0<M<=10, and the M integer are non-negative and won’t exceed 20.

Output

  For each case, output the number.

Sample Input

12 2
2 3

Sample Output

7

大意是求出一个序列在给定范围内的公倍数有多少个。

容斥定理枚举即可,n个数的公倍数的数量在n为奇数时总数就加上,n为偶数时就减去

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#define md(l,r) (l+r)>>1
#define rson(x) x<<1|1
#define lson(x) x<<1
#define endl '\n'
#define sc(x) scanf("%lld",&x)

using namespace std;
typedef long long ll;
ll gcd(ll a,ll b)
{
	return  b==0?a:gcd(b,a%b);
}
ll ans=0;
vector<ll> num;
int n,m;
void dfs( ll dep,ll t,ll now)
{
	ll lcm=t*num[now]/gcd(t,num[now]);
	if(dep&1) ans+=(m-1)/lcm;
	else ans-=(m-1)/lcm;
	for(int i=now+1;i<num.size();i++)
	{
		dfs(dep+1,lcm,i);
	}
}
int main()
{
	while(~scanf("%d%d",&m,&n))
	{
		num.clear();
		ll temp;
		for(int i=1;i<=n;i++) {sc(temp);if(temp!=0) num.push_back(temp);} 
		ans=0;
		for(int i=0;i<num.size();i++)
		{
			dfs(1,num[i],i);
		}
		cout<<ans<<endl;
	}
	return 0;
}

Ignatius and the Princess III

 HDU - 1028 

"Well, it seems the first problem is too easy. I will let you know how foolish you are later." feng5166 says. 

"The second problem is, given an positive integer N, we define an equation like this: 
  N=a[1]+a[2]+a[3]+...+a[m]; 
  a[i]>0,1<=m<=N; 
My question is how many different equations you can find for a given N. 
For example, assume N is 4, we can find: 
  4 = 4; 
  4 = 3 + 1; 
  4 = 2 + 2; 
  4 = 2 + 1 + 1; 
  4 = 1 + 1 + 1 + 1; 
so the result is 5 when N is 4. Note that "4 = 3 + 1" and "4 = 1 + 3" is the same in this problem. Now, you do it!" 

Input

The input contains several test cases. Each test case contains a positive integer N(1<=N<=120) which is mentioned above. The input is terminated by the end of file. 

Output

For each test case, you have to output a line contains an integer P which indicate the different equations you have found. 

Sample Input

4
10
20

Sample Output

5
42
627

划分数字题。

思路,将之转化为相对应的生成函数(也叫做母函数)(学过的应该知道什么意思,没学过建议先百度再看)。然后就是一道生成函数的模板题了。

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int size=150;
ll ans[size];
ll help[size];
void solve(int n)
{
	for(int i=0;i<=n;i++)
	{
		ans[i]=1;
		help[i]=0;
	}
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			for(int k=0;j+k<=n;k+=i)
			{
				help[j+k]+=ans[j];
			}
		}
		for(int j=0;j<=n;j++)
		{
			ans[j]=help[j];
			help[j]=0;
		}
	}
	cout<<ans[n]<<endl;
}
int main()
{
	int n;
	while(~scanf("%d",&n))
	{
		solve(n);
	}
}

Array Rearrangement

 HihoCoder - 1330 

Little Ho has written an array-shuffle program. It will shuffle an input array by some certain rearrangement order. Little Hi wants to know that after how many times of shuffle the array will come back to its original order?

More precisely, given a permutation P of 1 - N, the program shuffles the array by putting the i-th element into the Pi-th position. Assuming P = (2, 3, 1) and the initial array is (1, 2, 3), after the 1st shuffle it becomes (3, 1, 2), after the 2rd shuffle it becomes (2, 3, 1) and after the 3rd shuffle it comes back to (1, 2, 3).

You may assume the elements of input array are distinct. 

Input

The first line contains an integer N, the length of the array. (1 ≤ N ≤ 100)

The second line contains the permutation P which consists of N integers.

Output

Output the number of shuffles.

Sample Input

3
2 3 1

Sample Output

3

给出一个从1到n的序列,和一个变换法则,问其循环周期是多少。

思路:对每个数求出其循环周期再求最小公倍数即可。

代码:

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
ll arr[105];
ll gcd(int a,int b)
{
	return b==0?a:gcd(b,a%b);
}
int main()
{
	int n;
	while(~scanf("%d",&n))
	{
		for(int i=1;i<=n;i++) scanf("%lld",&arr[i]);
		ll cnt=0;
		for(int i=1;i!=1||!cnt;i=arr[i]) cnt++;
		for(int i=2;i<=n;i++)
		{
			ll tot=0;
			for(int j=i;j!=i||!tot;j=arr[j]) tot++;
			cnt=cnt*tot/gcd(cnt,tot);
		}
		cout<<cnt<<endl;
	}
}

CARDS

 POJ - 1721 

Alice and Bob have a set of N cards labelled with numbers 1 ... N (so that no two cards have the same label) and a shuffle machine. We assume that N is an odd integer. 
The shuffle machine accepts the set of cards arranged in an arbitrary order and performs the following operation of double shuffle : for all positions i, 1 <= i <= N, if the card at the position i is j and the card at the position j is k, then after the completion of the operation of double shuffle, position i will hold the card k. 

Alice and Bob play a game. Alice first writes down all the numbers from 1 to N in some random order: a1, a2, ..., aN. Then she arranges the cards so that the position ai holds the card numbered a i+1, for every 1 <= i <= N-1, while the position aN holds the card numbered a1. 

This way, cards are put in some order x1, x2, ..., xN, where xi is the card at the ith position. 

Now she sequentially performs S double shuffles using the shuffle machine described above. After that, the cards are arranged in some final order p1, p2, ..., pN which Alice reveals to Bob, together with the number S. Bob's task is to guess the order x1, x2, ..., xN in which Alice originally put the cards just before giving them to the shuffle machine. 

Input

The first line of the input contains two integers separated by a single blank character : the odd integer N, 1 <= N <= 1000, the number of cards, and the integer S, 1 <= S <= 1000, the number of double shuffle operations. 
The following N lines describe the final order of cards after all the double shuffles have been performed such that for each i, 1 <= i <= N, the (i+1) st line of the input file contains pi (the card at the position i after all double shuffles). 

Output

The output should contain N lines which describe the order of cards just before they were given to the shuffle machine. 
For each i, 1 <= i <= N, the ith line of the output file should contain xi (the card at the position i before the double shuffles). 

Sample Input

7 4
6
3
1
2
4
7
5

Sample Output

4
7
5
6
1
2
3

给出一组序列,与变换的次数,且其拥有变化法则a[i]=a[a[i]]。问原序列是什么。

思路:求出其循环的周期,再让周期减去原序列变换到现在序列的次数就是现在序列变换到原序列需要的次数了。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int size=1005;
int a[size],h[size];
int o[size];
void Swap(int n)
{
	for(int i=1;i<=n;i++)
	{
		h[i]=a[a[i]];
	}
	for(int i=1;i<=n;i++)
	{
		//cout<<a[i]<<' ';
		a[i]=h[i];
		
	}
	//cout<<endl;
}
bool check(int n)
{
	for(int i=1;i<=n;i++)
	{
		if(a[i]!=o[i]) return 0;
	}
	return 1;
}
int main()
{
	int n;
	int cnt;
	while(~scanf("%d%d",&n,&cnt))
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			o[i]=a[i]; 
		}
		int len;
		for(len=1;;len++)
		{
			Swap(n);
			if(check(n)) break;
		}
		len=len-cnt%len;
		while(len--)
		{
			Swap(n);
		}
		for(int i=1;i<=n;i++)
		{
			cout<<a[i]<<endl; 
		}
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值