纪中暑假集训 2020.07.20【NOIP提高组】模拟 反思+题解

这次比赛100分,侥幸排到了第2 (没啥可骄傲的,一大坨人第2) 强烈吐槽T2、T3、T4只有一个数据点,部分分都拿不了!!!

T1:【CQOI2008】矩阵的个数

Description

给出一个N行3列非负整数矩阵的各行各列之和,统计有多少个矩阵满足此条件。输出答案模10^17的值。

Input

第一行包含四个正整数N,c1, c2, c3,即行数与三列之和。第二行包含N个正整数,即各行三个数之和。每行每列之和均不超过125。

Output

仅一个数,满足条件的矩阵个数模10^17的值。

Sample Input

3 2 3 4
1 2 6

Sample Output

17

Hint

1<=N<=200

反思&题解

比赛&正解思路: 首先,因为如果处理了前2列的话,第3列的和也就是确定的了,所以我们设 f [ i ] [ j ] [ k ] 为 f[i][j][k]为 f[i][j][k]当前处理到了第i行,第1列的和为j,第2列的k的方案书,那么最后输出的是 f [ n ] [ c 1 ] [ c 2 ] f[n][c1][c2] f[n][c1][c2]。接下来我们来思考动态转移方程:
首先,我们设第1列可以增加x,第2列可以增加y,显然 j + x ≤ c 1 j+x≤c1 j+xc1 k + y ≤ c 2 k+y≤c2 k+yc2,那么方程便一下出来了:
f [ i ] [ j + x ] [ k + y ] + = f [ i − 1 ] [ j ] [ k ] f[i][j+x][k+y]+=f[i-1][j][k] f[i][j+x][k+y]+=f[i1][j][k]
反思: 相对于整套题来说这题算最简单的了,DP题思路很重要

CODE

#include
using namespace std;
const long long mo=100000000000000000;
int n,c1,c2,c3,sum,a[205];
long long f[205][150][150];
int main()
{
	scanf("%d%d%d%d",&n,&c1,&c2,&c3);
	int i;
	for (i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	if (sum!=c1+c2+c3) printf("0\n");
	else
	{
		int j,k,x,y;
		f[0][0][0]=1;
		for (i=1;i<=n;i++)
		{
			for (j=0;j<=c1;j++)
			{
				for (k=0;k<=c2;k++)
				{
					for (x=0;x+j<=c1;x++)
					{
						for (y=0;y+k<=c2;y++)
						{
							if (x+y>a[i]) break;
							else
							{
								f[i][j+x][k+y]=(f[i][j+x][k+y]+f[i-1][j][k])%mo;
							}	
						}	
					}
				}
			}
		}
		printf("%lld\n",f[n][c1][c2]);
	}
	return 0;
}

T2:昂贵的珍珠垂饰

Description

情人节之际,Alex决定用K种珍珠为他的GF做一串举世无双的珍珠垂饰与她的项链相配。珍珠垂饰是由珍珠连接而成的,其长度可以认为就是珍珠垂饰上珍珠的个数。众所周知,Alex家缠万贯,每种珍珠他都拥有N颗。根据将珍珠垂饰打开后珍珠不同的排列顺序可以区别不同种类的项链。现在,他好奇自己可以组成多少种长度为1至N的不同的珍珠垂饰?当然,为显富有,每串珍珠垂饰都要必须由K种珍珠连成。

答案取模1234567891。

Input

输入包含多组数据。第一行是一个整数T,表示测试数据的个数。每组数据占一行,包含两个整数N和K,用一个空格隔开。

[Technical Specification]

1 <= T <= 10
1 <= N <= 1,000,000,000
1 <= K <= 30

Output

每组数据输出仅一行,为项链的种类数。

Sample Input

2
2 1
3 2

Sample Output

2
8

反思&题解

比赛思路: 懵……
正解思路: 数学毒瘤题……
首先很明显看出来这题是容斥原理 (明显吗,还是我数学没救了???)
假设当前我们要处理出的序列长度为n,那么方案数为:
k n − C ( k , 1 ) ∗ ( k − 1 ) n + C ( k , 2 ) ∗ ( k − 2 ) n − C ( k , 3 ) ∗ ( k − 3 ) n … … k^n-C(k,1)*(k-1)^n+C(k,2)*(k-2)^n-C(k,3)*(k-3)^n…… knC(k,1)(k1)n+C(k,2)(k2)nC(k,3)(k3)n
即:
k n + ∑ ( − 1 ) i ∗ C ( k , i ) ∗ ( k − i ) n k^n+∑(-1)^i*C(k,i)*(k-i)^n kn+(1)iC(k,i)(ki)n
但是n非常大,所以这样枚举做绝对会超时,所以我们将几个式子列出来看看
k n − C ( k , 1 ) ∗ ( k − 1 ) n + C ( k , 2 ) ∗ ( k − 2 ) n − C ( k , 3 ) ∗ ( k − 3 ) n … … k^n-C(k,1)*(k-1)^n+C(k,2)*(k-2)^n-C(k,3)*(k-3)^n…… knC(k,1)(k1)n+C(k,2)(k2)nC(k,3)(k3)n
k n − 1 − C ( k , 1 ) ∗ ( k − 1 ) n − 1 + C ( k , 2 ) ∗ ( k − 2 ) n − 1 − C ( k , 3 ) ∗ ( k − 3 ) n − 1 … … k^{n-1}-C(k,1)*(k-1)^{n-1}+C(k,2)*(k-2)^{n-1}-C(k,3)*(k-3)^{n-1}…… kn1C(k,1)(k1)n1+C(k,2)(k2)n1C(k,3)(k3)n1
k n − 2 − C ( k , 1 ) ∗ ( k − 1 ) n − 2 + C ( k , 2 ) ∗ ( k − 2 ) n − 2 − C ( k , 3 ) ∗ ( k − 3 ) n − 2 … … k^{n-2}-C(k,1)*(k-1)^{n-2}+C(k,2)*(k-2)^{n-2}-C(k,3)*(k-3)^{n-2}…… kn2C(k,1)(k1)n2+C(k,2)(k2)n2C(k,3)(k3)n2
我们就会惊奇地发现没个式子对应的每一项是一个等比数列,所以我们可以用等比数列来化简式子,就只用枚举k了
接下来我来讲讲等比数列求和(也就是我们数学老师某DD经常说的错位相减法):
a i ai ai为数列的第i项,公比为t,整个数列的和为S,则
S = ∑ a i ( 1 ≤ i ≤ n ) S=∑a_i(1≤i≤n) S=ai(1in)
那么 t S = ∑ a i ∗ t ( 1 ≤ i ≤ n ) tS=∑a_i*t(1≤i≤n) tS=ait(1in),也就是 t S = ∑ a i ( 2 ≤ i ≤ n + 1 ) tS=∑a_i(2≤i≤n+1) tS=ai(2in+1)
之后我们将 t S − S tS-S tSS,得
( t − 1 ) S = a n + 1 − a 1 (t-1)S=a_{n+1}-a_1 (t1)S=an+1a1,即 S = a n + 1 − a 1 t − 1 S=\dfrac{a_{n+1}-a_1}{t-1} S=t1an+1a1
那么原式子推得:
a n s = k n + 1 − k k − 1 − C ( k , 1 ) ∗ ( k − 1 ) n + 1 − C ( k , 1 ) ∗ ( k − 1 ) k − 1 − 1 + C ( k , 2 ) ∗ ( k − 2 ) n + 1 − C ( k , 2 ) ∗ ( k − 2 ) k − 2 − 1 − C ( k , 3 ) ∗ ( k − 3 ) n + 1 − C ( k , 3 ) ∗ ( k − 3 ) k − 3 − 1 … … ans=\dfrac{k^{n+1}-k}{k-1}- \dfrac{C(k,1)*(k-1)^{n+1}-C(k,1)*(k-1)}{k-1-1}+ \dfrac{C(k,2)*(k-2)^{n+1}-C(k,2)*(k-2)}{k-2-1}- \dfrac{C(k,3)*(k-3)^{n+1}-C(k,3)*(k-3)}{k-3-1}…… ans=k1kn+1kk11C(k,1)(k1)n+1C(k,1)(k1)+k21C(k,2)(k2)n+1C(k,2)(k2)k31C(k,3)(k3)n+1C(k,3)(k3)
然后组合数以及除法又要用到费马小定理(其实组合数用杨辉三角就行了 ,但是既然打了快速幂还不如把费马打了
最后还要在 n = 1 n=1 n=1 k − i = 1 k-i=1 ki=1时打两个特判就行了(详见code)
反思: 我就不信我们这个水平的有人在考试可以想得出来! 很明显数学能力要加强,毕竟跟信息学关联很大(改题的时候公式化简有问题,公比提错了,耗了差不多2个小时……)

CODE

#include
using namespace std;
const long long mo=1234567891;
long long t,n,k,jc[50];
long long power(long long x,long long y)
{
	long long ans=1;
	while (y)
	{
		if (y&1) ans=ans*x%mo;
		x=x*x%mo;
		y>>=1;
	}
	return ans;
}
long long C(long long n,long long m)
{
	return jc[m]*power(jc[m-n]*jc[n]%mo,mo-2)%mo;
}
int main()
{
	scanf("%lld",&t);
	int i;
	jc[1]=1;
	for (i=2;i<=30;i++)
		jc[i]=jc[i-1]*i%mo;
	while (t--)
	{
		scanf("%lld%lld",&n,&k);
		long long tot=((power(k,n+1)-k)%mo*power(k-1,mo-2)%mo)%mo;
		if (k==1)
		{
			printf("%lld\n",n%mo);
			continue; 
		} 
		for (i=1;i<=k;i++)
		{
			int f;
			if (i&1) f=-1;
			else f=1;
			if (k-i==1) tot=(tot+mo+f*C(i,k)*(k-i)%mo*n%mo)%mo;
			else tot=(tot+f*C(i,k)*((power(k-i,n+1)-(k-i))%mo*power(k-i-1,mo-2)%mo)%mo)%mo;
		}
		printf("%lld\n",tot);
	}
	return 0;
} 

T3:戒指

Description

S计划送给J一枚戒指,以示他们的爱情天长地久
同时,S打算在戒指上刻写一些语句,以增加它的寓意。可惜戒指的大小有限,最多只能被刻入N个字母。细心的S非常了解J,知道她喜欢的单词,例如love, forever等等,同时他也知道这些单词都存在一个“愉悦值”,值越高的单词越能取悦J。
现在,S希望刻入一条字符串,使得各个单词在字符串中出现的次数 × 单词愉悦值之和尽可能的大。

Input

首行为一个整数T,表示数据个数。
每组数据第一行为两个整数N、M,分别表示能刻入戒指的字符串长度以及J喜欢的单词个数。
之后为M行,每行为一个单词Si,表示J喜欢的第i个单词。单词仅包含小写字母。
之后一行包含M个正整数Hi,表示第i个单词的取悦值。

[Technical Specifacation]

  1. 1 <= T <= 15
  2. 1 <= N <= 50,1 <= M <= 100
  3. 1 <= 单词长度 <= 10
  4. 1 <= Hi <= 100
  5. 数据保证不出现重复的单词

Output

对于每组数据输出一行,为在戒指上刻入的字符串。
如果有多解,则输出长度最短的解,若依然有多接则输出字典序最小的解。
答案有可能为空串。

Sample Input

2
7 2
love
ever
5 5
5 1
ab
5

Sample Output

lovever
abab

反思&题解

比赛思路: 懵,暴力骗0分
正解思路: DP,还是懵 (听说可以用AC自动机,不过反正我也不会)
反思: 难题要多刷???
这题就暂时不放code了,主要是因为我还不太会,得找人问一问

T4:五子棋

Description

WHU ACM的队员们迷上了五子棋游戏,他们决定组织一场队内友谊赛以便互相切磋棋艺。 比赛规则是这样的:每位选手都要和其他人进行一场比赛,每场比赛胜者将得到一定的积分,败者不得分,若和棋则双方都不得分。 每位队员都有一个经验值,我们可以认为比赛中经验值较高者获胜,若双方经验值相同则为和棋。 队员们都很聪明,他们会在比赛中不断进步。也就是说,和特定的对手进行比赛后,无论胜负,都会增加一定经验值。 小M作为WHU ACM集训队的队长有资格安排比赛顺序,而同时作为1号选手的他自然希望自己的积分能越高越好。因此,他请你根据相关信息写一个程序来帮助他。

Input

输入有多组数据,第一行为一个整数T(1 <= T <= 11),表示数据个数。 每组数据第一行为一个整数N(1 <= N <= 13)表示参赛选手数。 接下来有N行,每行包含N个范围为[0, 1000]的整数,第i 行的第j个数表示选手i与选手j比赛后选手i获得的经验值。 之后N行每行包含N个范围为[0, 10]的整数,第i行的第j个数表示选手i战胜选手j后得到的积分。 最后一行N个范围为[0, 1000]的整数,表示各位选手的初始经验值。

Output

输出T行(每行一个数),即小M能获得的最高积分。

Sample Input

2
2
0 1
1 0
0 1
1 0
1 1
3
0 5 2
0 0 0
0 0 0
0 10 1
1 0 1
1 0 0
1 10 5

Sample Output

0
1

反思&题解

比赛思路: 暴力全排列,结果因为估计拿不到分被我否定了,其实加一个小小的剪枝是可以过的
正解思路: 最适当的方法是状压DP,但是全排列暴力水法他不香吗???(主要是我太弱了,不想打状压了)
反思: 有时加了剪枝的暴力还真可以出奇迹,所以有时候不要想太多高大厦的方法……

CODE

#include
using namespace std;
int t,n,jf[15][15],jy[15][15],a[15],bz[15],ans,summ;
void dg(int k,int tot,int tot1,int sum)
{
	if (tot+sum<=ans) return;//小小的剪枝
	if (k>n)
	{
		if (tot>ans) ans=tot;
	}
	else
	{
		int i;
		for (i=2;i<=n;i++)
		{
			if (!bz[i])
			{
				bz[i]=true;
				if (tot1>a[i]) dg(k+1,tot+jf[1][i],tot1+jy[1][i],sum-jf[1][i]);
				else dg(k+1,tot,tot1+jy[1][i],sum-jf[1][i]);
				bz[i]=false;
			}
		}
	}
}
int main()
{
	scanf("%d",&t);
	while (t--)
	{
		scanf("%d",&n);
		int i,j;
		for (i=1;i<=n;i++)
		{
			for (j=1;j<=n;j++)
				scanf("%d",&jy[i][j]);
		}
		for (i=1;i<=n;i++)
		{
			for (j=1;j<=n;j++)
				scanf("%d",&jf[i][j]);
		}
		for (i=1;i<=n;i++)
			scanf("%d",&a[i]);
		summ=0;
		for (i=2;i<=n;i++)
			summ+=jf[1][i];
		ans=0;
		memset(bz,false,sizeof(bz));
		dg(2,0,a[1],summ);
		printf("%d\n",ans);
	}
	return 0;
}

你可能感兴趣的:(题解,反思)