夏令营501-511NOIP训练18

T1 高一学堂

高一学堂,因为有了 y x r yxr yxr,就成了现在这个样子 = =。由于 y x r yxr yxr 的语言太过雷人,每次他发微往往都会有一石激起千层浪的效果,具体就是所有关注他的人都会转发,同时 @ @ @他,接着关注这些人的人也会转发,同时 @ @ @他关注的人(注意转发内容本身会有 @ @ @ y x r yxr yxr),以此类推。这样导致每次 y x r yxr yxr 发微博都会被 @ @ @上兆次,而 y x r yxr yxr 又特别喜欢发,sina 支持不了如此庞大的数据量,特出规定,每次转发时, @ @ @的人不能超过K 人,好友在转发时如果超过了,就把最早那人删掉。现在 y x r yxr yxr 刚发了一条微博“求满分”,他想知道每个与他有联系的人分别会被 @ @ @多少次。


输入格式:

输入第一行有三个整数, N N N K K K,表示人数和 K K K
接下来 N − 1 N-1 N1 行,每行有2 个整数 a a a b b b,表示 a a a b b b 有关注关系。
输入给出一棵以 1 1 1 号点为根的树,一号点代表 y x r yxr yxr,对于任意一个点,他的儿子都关注他。

输出格式:

输出有 N N N 行,每行有一个整数,这个人会被 @ @ @ 多少次。


样例输入:
5 2
1 2
2 3
2 4
4 5
样例输出:
3
3
0
1
0

数据范围:
对于 30 % 30\% 30%的数据, N N N ≤100;
对于 60 % 60\% 60%的数据, N N N ≤2000, K K K ≤100;
对于 100 % 100\% 100%的数据, N N N ≤100000, K K K N N N


题解:
  • 其实就是求每个结点深度差不超过 K K K 的儿子个数
  • 每个结点的儿子个数 - 它 k + 1 k+1 k+1 层每个儿子的儿子个数
#include <bits/stdc++.h>
using namespace std;
vector<int> a[100010];
vector<int> b[100010];
int d[100010],h[100010],k;
void dfs(int u,int fa,int dep)
{
	d[u]=dep;
	for(int i=0; i<a[u].size(); i++)
	{
		int v=a[u][i];
		if(v==fa) continue;
		dfs(v,u,dep+1);
		h[u]+=h[v]+1;
	}
}
void dfs_2(int u,int fa,int dep)
{
	b[d[u]].push_back(u); // 当前子树下,此深度的结点
	if(b[dep-k-1].size()&&dep-k-1>0)  // 减掉它k+1儿子结点的儿子个数
	{
		for(int i=0; i<b[dep-k-1].size(); i++)
		{
			int v=b[dep-1-k][i];
			h[v]=h[v]-h[u]-1;
		}
	}
	for(int i=0; i<a[u].size(); i++)
	{
		int v=a[u][i];
		if(v==fa) continue;
		dfs_2(v,u,dep+1);
	}
	// 回溯时把当前结点删除
	vector<int>::iterator it;
	for(it=b[d[u]].begin();it!=b[d[u]].end();it++)
		if(*it==u)
		{
			b[d[u]].erase(it);
			break;
		}
}
int main()
{
	int n,i,x,y;
	scanf("%d%d",&n,&k);
	for(i=1; i<n; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(1,0,1);    //  记录深度和儿子结点的个数
	dfs_2(1,0,1);
	for(i=1; i<=n; i++) printf("%d\n",h[i]);
	return 0;
}

T2 高二学堂

Wayne 有一副加强版的扑克牌,强大到任意取一个自然数x,在牌堆里都恰
4 4 4 张数值为 x x x 的牌。每次,Wayne 随机生成两个正整数 n n n 和k,然后在牌堆里选取不超过 k k k 张牌,使得牌面数字之和恰为 n n n。已知Wayne 玩了若干盘,每盘都算出了对应的方案数,他想请你也算出各盘方案数,以验算他的结果是否正确。结果可能比较大,你只需要求出方案数 m o d mod mod 1000000009(1e9+9) 的值即可。


输入格式:

输入文件包含不超过 10 10 10 组数据。
每行包含两个整数,表示上文中的 n n n k k k
输入数据以两个 0 0 0 表示结束。

输出格式:

输出文件中,每组数据输出一行,为对应的方案数。


样例输入:
2 1
2 2
2 3
50 5
0 0
样例输出:
4
10
10
1823966

数据范围:
对于 10 % 10\% 10%的数据, k k k=1;
对于 20 % 20\% 20%的数据, n n n ≤10, k k k ≤4;
对于 40 % 40\% 40%的数据, n n n ≤1000;
对于 60 % 60\% 60%的数据, n n n ≤100000;
对于另外 20 % 20\% 20% 的数据,只有 1 1 1 组数据;
对于 100 % 100\% 100%的数据, n n n ≤10^9, k k k ≤10。


题解
  • 不好意思目前本蒟蒻只会 40 40 40分做法,等以后会 100 100 100分了再回来更新(也许你等不到这一天)
  • (题意)选取不超过 k k k 个数,每个数可以取至多 4 4 4个,不考虑 0 0 0,使得数字之和为 n n n
  • d p [ i ] [ j ] dp[ i ][ j ] dp[i][j] : 和为 i i i ,取 j j j 个数的方案数
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
long long d[100010][12],c[10][10];
int main()
{
	int n,k,i,j,l;
	memset(d,0,sizeof(d));
	c[4][1]=c[4][3]=4;
	c[4][2]=6;
	c[4][4]=1;
	d[0][0]=1;
	for(i=1; i<=1000; i++)  //  当前正在取 i 
		for(j=10;j>=1;j--)  // 取 j 个数
			for(l=0; l<=1000; l++) // 和为 l
				for(k=1; k<=min(4,j)&&k*i+l<=1000; k++)  // i 取 k 个
					d[k*i+l][j]=(d[k*i+l][j]+(d[l][j-k]*c[4][k])%mod)%mod;
		
	while(scanf("%d%d",&n,&k)!=EOF)
	{
		if(!n&&!k) break;
		long long s=0;
		for(i=1; i<=k; i++) s=(s+d[n][i])%mod;
		printf("%lld\n",s);
	}
	return 0;
}

T3 高三楼

一个 n ∗ n n * n nn 的矩阵,其中,每行每列都有两个特殊的点。
"重复"的定义为矩阵 a a a ,通过任意次行列变换,变成了矩阵 b b b,矩阵 a a a b b b就视为重复。
例如:对于 3 ∗ 3 3*3 33 的矩阵,其中矩阵 a a a 与矩阵 b b b 被视为“重复”矩阵。
在这里插入图片描述
对于一个 n n n,可以有多少种不“重复”的矩阵,输出答案 m o d mod mod100000007(1e8+7) 的值。


输入格式:

第一行,一个整数 t t t,表示数据组数。
接下来 t t t 行,每行一个整数 n n n,表示一组数据。

输出格式:

T T T 行,每行一个整数,表示方案数。由于答案可能很大,只需要输出方案数
m o d 100 , 000 , 007 mod 100,000,007 mod100,000,007 的值就可以了。


样例输入:
3
2
3
4
样例输出:
1
1
2

数据范围:
对于 10 % 10\% 10%的数据 N N N 5 5 5
对于 50 % 50\% 50%的数据 N N N 150 150 150
对于 100 % 100\% 100%的数据 T T T 5 5 5 N N N 2000 2000 2000


题解
  • 此题解法神奇,因为每行每列只有两个标记,所以我们可以把,想成一个二分图的两条边
  • 两条边之间的连线等价于某一个标记
  • 两条边上的点的移动等价于标记的行列变换
  • 所以我们只要考虑有多少种不同的连法即可
  • e.g. (可以手模以下 n = 5 n = 5 n=5
    n = 4
    • 然后——惊奇地发现 ( ๑ ╹ ◡ ╹ ) ノ (๑╹◡╹)ノ () 2 ∗ x ( 1 &lt; x &lt; = n ) 2*x(1 &lt; x &lt;= n) 2x1<x<=n点就会构成一个环
    • 每种环的组成,代表一种方案,排列并不重要
    • So—— n = a 1 + a 2 + . . . . . . + a m n = a_1+a_2+......+a_m n=a1+a2+......+am ( a m &gt; 1 ) (a_m &gt; 1) (am>1)
    • 于是就转化成了正整数拆分的问题 ︿ (  ̄ ︶  ̄ ) ︿ ︿( ̄︶ ̄)︿ ︿()︿
#include <bits/stdc++.h>
using namespace std;
const int mod=1e8+7;
int d[100010];
int main()
{
	int i,j;
	memset(d,0,sizeof(d));
	d[0]=1;
	for(i=2;i<=2000;i++)
		for(j=i;j<=2000;j++) d[j]=(d[j]+d[j-i])%mod;
	//  j = i + ( j - i )
	scanf("%d",&i);
	while(i--)
	{
		scanf("%d",&j);
		printf("%d\n",d[j]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值