2022年合肥市信息学市赛初中组

本文详细介绍了2022年合肥市信息学市赛初中组的比赛题目,包括T1计6器、T2牛了个牛、T3小C爱食堂和T4圣诞节四个题目。涉及字符串枚举、位运算、前缀和、模拟等算法知识点,附带输入输出样例和解析,以及解题参考代码。
摘要由CSDN通过智能技术生成

2022年合肥市信息学市赛初中组-T1-计6器(six)

题目描述

小 C 十分喜欢数字“6”。

一天,大数据专家小 L 给了小 C 一个整数。小 C 一眼望去发现这个整数出现了很多“6”,于是想统计整数中出现了多少次“6”。

这个数字可能很大,详情请认真阅读【数据范围】。

输入格式

从文件 six.in 中读取数据。

一行,一个正整数 n,表示小 L 给小 C 的整数。

输出格式

输出到文件 six.out 中。

仅一行一个数,表示整数中“6”的个数。

输入输出样例

输入样例1:

123456

输出样例1:

1

输入样例2:

122615163618616116

输出样例2:

6

说明

【数据范围】

对于 30% 的数据:1 ≤ n ≤ 10^9;

对于 60% 的数据:1 ≤ n ≤ 10^18;

对于 100% 的数据:1 ≤ n ≤ 10^1000000。

耗时限制1000ms  内存限制512MB

解析

考点:字符串枚举

参考代码

#include <bits/stdc++.h>
using namespace std;
string s;
int ans;
int main(){
    cin >> s;
    for(int i=0;i<s.size();i++) {
        if(s[i] == '6') ans++;
    }
    cout << ans;
    return 0;
}

2022年合肥市信息学市赛初中组-T2-牛了个牛(ox)

题目描述

曾经一款微信小程序“羊了个羊”大火,它凭借地狱难度让无数人沉迷其中。小 C 也是如此。

“羊了个羊”中有一个牌堆,每次选择顶端的牌到槽子里,如果槽子里出现 3 张相同的牌就可以消去。

由于“羊了个羊”难度太大,且多数情况无解,于是小 C 想自己开发一款类似的游戏,叫做“牛了个牛”。

“牛了个牛”有一排牌,从 1 到 n,每张牌有个数字 ai。总共有 Q 轮游戏,每轮游戏有两个数 l、r。玩家需要将 l 到 r 的所有牌放入槽子中,如果槽子里有 2 张相同的牌即可消除,且槽子无容量限制。如果槽子里还剩卡牌,游戏失败,否则游戏胜利。注意一轮游戏结束后,无论是输是赢,n 张牌会恢复原样。

请注意两款游戏的区别。

现在小 C 让你玩“牛了个牛”,而你聪明绝顶,请回答他 Q 轮游戏的胜负情况。

输入格式

从文件 ox.in 中读取数据。

第一行两个整数 n、Q,表示牌数和游戏轮数;

第二行共 n 个数,第 i 个数为 ai,表示第 i 张牌上的数字;

接下来 Q 行,每行两个数 l 和 r,如题所述。

输出格式

输出到文件 ox.out 中。

共 Q 行,每行用 Yes、No 表示该轮游戏的胜负(第一个字母大写,后面全为小写)。

输入输出样例

输入样例1:

6 3

1 2 2 3 3 1

2 3

1 4

1 6

输出样例1:

Yes

No

Yes

输入样例2:

6 3

1 1 1 2 2 2

1 4

2 5

3 6

输出样例2:

No

Yes

No

说明

【数据范围】

对于 20% 的数据:n, Q ≤ 10;

对于 40% 的数据:n, Q ≤ 10^3;

对于另外 50% 的数据:n, Q ≤ 10^5;

其中有 40% 的数据满足:n, Q ≤ 10^5,且 ai 为 2 的幂,即 ai = 2^k,其中 k 为非负整

数;

对于所有数据:1 ≤ n, Q ≤ 10^6,1 ≤ ai ≤ n。

耗时限制1000ms   内存限制512MB

解析

考点:位运算,前缀和

1.数组标记

暴力枚举区间,查询时,用数组标记,查看区间内的数字出现的次数是否都是偶数次。由于时间复杂度O(Qn),超时,40分左右。

2.前缀异或(60分)

观察数据范围,发现 n,Q≤10^6。因此正解应是 O(n+Q) 或者 O(nlogn+Q) 的。

每次区间查询必须 O(1) 查询,因此必然预处理,由于异或运算具有 x⊗x=0 的特性,即,对于一个区间[l,r],“其上每个数字均出现偶数次” ,这个区间内的数字就可以消掉,因此考虑利用前缀异或处理。

#include<bits/stdc++.h>
using namespace std;
int n,q,l,r;
int  a[1000010],x[1000010]; 
int main(){

	scanf("%lld %lld",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		x[i]=x[i-1]^a[i];
	}
	while(q--){
		scanf("%lld %lld",&l,&r);
		if((x[r]^x[l-1])==0){//注意位运算的优先级低于关系运算符,要加上()
			cout<<"Yes"<<endl;
		}
		else{
			cout<<"No"<<endl;
		}
	}
	return 0;
}

3.正解:哈希+前缀异或

问题:对于一个区间 [l,r],“其上每个数字均出现偶数次” 和 xo[r]⊗xo[l−1]=0” 是否互为充要条件?

记原数组的前缀异或数组为 xo,对于区间[l,r],如果其上每个数字均出现偶数次,那么必有 xo[r]⊗xo[l−1]=0。但是,我们能否就说 “xo[r]⊗xo[l−1]=0” 可以推出 “区间[l,r] 上每个数字均出现偶数次” ?

答案是否定的。比如,我们可以构造一个长度为 4 的区间][l,r],其上数字a,b,c,d 不是两两成对的(即 a=b,c=d 或者 a=c,b=d),且满足a⊗b⊗c⊗d=0,那么就可以使得该区间满足 xo[r]⊗xo[l−1]=0。比如:

8 2
7 23 5 21 7 38 5 36
1 4
5 8

7⊗23=16=5⊗21,7⊗38=33=5⊗36此时就会误判。

单纯的前缀异或无法解决本问题的原因:

那怎么办呢?注意到问题中的牌上的数字范围是在 [1,n] 内的,在这种情况下,我们可以轻易构造类似上面的区间元素。为什么说是“轻易构造”呢?——因为异或运算是“不带进位的加法运算”。因此,在取值范围集中在 [1,n] 上时,太容易找到数字满足三个、四个甚至更多个数字满足在这种“不带进位的加法运算”了。

比如,我们随机找一个数字 e,再随机找两个数字 a,c,即可构造 b=e⊗a,d=e⊗c 即可满足 a⊗b⊗c⊗d=0。

基于这个原因,我们甚至可以构造 5,6,7,... 个数字构成区间,并使得区间异或为 0。

如何解决这种问题?

找到问题的症结,那么就成功了一半。此时,我们要么换别的做法,要么优化现有做法,“避免”前缀异或的这个问题。既然这样的构造依赖于异或运算的“半加”性质导致的,那么我们做一个 哈希,将原本的[1,n] 区间上的数字 映射 到一个更大的区间上,来破坏其 半加/半减 性质。

至于哈希函数的选择,则任意啦,唯一的要求就是映射后的区间尽可能大,来避免出现哈希冲突和异或的冲突。比如:h(x)=2xmodp,p 为大质数即可。如果担心出现哈希冲突,多搞几个质数做哈希即可。

本题解中的做法即选用上面的哈希函数。

时间复杂度:O(nlogn),llogn 源自哈希函数的快速幂。

参考代码


#include <bits/stdc++.h>
using namespace std;

const int N = 1e6+5, mod = 1e9+7;
int n, q, a[N], xo[N];

int qpow(int x, int p) {
    if(x == 0) return 1;
    long long t = qpow(x/2, p);
    return t*t*(x&1?2:1) % p;
}

int main(){
    
    
    cin >> n >> q;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] = qpow(a[i], mod);
        xo[i] = xo[i-1]^a[i];
    }
    while(q--) {
        int l, r;
        cin >> l >> r;
        cout << ((xo[r]^xo[l-1])?"No":"Yes") <<'\n';
    }
    return 0;
}

也可以使用rand函数来完成映射

#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int n,Q,s[N],b[N] ;
int main(){
	freopen("ox.in","r",stdin);
	freopen("ox.out","w",stdout);
	for(int i=1;i<=N;i++) b[i]=rand()*rand();
	cin>>n>>Q;
	for(int i=1;i<=n;i++){
		int a;
		cin>>a;
		s[i]=s[i-1]^b[a];
	}
	while(Q--){
		int l,r;
		cin>>l>>r;
		if(s[r]^s[l-1]){
			cout<<"No"<<endl;
		}else{
			cout<<"Yes"<<endl;
		}
	}
	return 0;
}

2022年合肥市信息学市赛初中组-T3-小C爱食堂(canteen)

题目描述

小 C 每到午饭时间,总是第一个冲向食堂。

食堂有一排 n 个窗口,第 i 号窗口有一个坐标 xi,不一定满足坐标递增

小 C 爱食堂,小朋友们也爱食堂。

食堂里总共有 m 位小朋友,第 i 位小朋友的坐标为 yi,不一定满足坐标递增 。由于避免拥挤,所以有 m ≤ n,即每位小朋友至少能找到一个窗口自己独享。

总而言之,可以将食堂看成一个数轴,窗口和小朋友看成若干个坐标点。

在小朋友的世界中,他们遵循着一套规则:从 0 时刻开始,每过 1 秒钟,每位小朋友朝向最近的、还没小朋友停留的窗口移动 1 的距离,如果到两个窗口的距离相等,选择编号最小的窗口;当一个窗口有小朋友的时候,在该处编号最小的小朋友将在此窗口停留;当小朋友停留后,就不会再移动了。

现在小 C 掌握了这套规则,他想知道每位小朋友停留的时刻以及停留的窗口,这样小 C 就能赶在小朋友到达某个窗口前抢到饭。

保证窗口两两坐标不同,不保证小朋友两两坐标不同,不保证初始时小朋友与窗口坐标不同。

输入格式

从文件 canteen.in 中读取数据。 

共三行,第一行两个正整数 n 和 m,表示窗口数和小朋友个数;

第二行共 n 个整数,第 i 个数为 xi,描述了窗口的坐标;

第三行共 m 个整数,第 i 个数为 yi,描述了小朋友的坐标。

窗口和小朋友的坐标不一定满足递增。

输出格式

输出到文件 canteen.out 中。 

共 m 行,每行两个整数,第 i 行的描述编号为 i 的小朋友,依次表示小朋友停留的时刻、停留的窗口的编号。

输入输出样例

输入样例1:
4 3  
3 -3 9 -11  
2 2 0 
输出样例1:
1 1 
15 3 
5 2
输入样例2:
6 6 
2 -1 0 3 5 -6  
1 1 -1 -1 3 4  
输出样例2:
1 1 
9 6 
0 2 
1 3 
0 4 
1 5

说明

【样例1 解释】

0时刻时,食堂情况如图:

不难看出 1、2、3 号小朋友的目标为 1 号窗口。 

1 时刻时,食堂情况如图:

根据规则,1 号小朋友将在 1 号窗口停留,2 号和 3 号小朋友重新选择 2 号窗口为目标(2 号小朋友虽然跟 3 号距离同时最短,但优先选择编号小的窗口)。

5 时刻时,食堂情况如图:

此时 3 号小朋友将在 2 号窗口停留,2 号小朋友将重新选择 3 号窗口为目标。 

15 时刻时,食堂情况如图:

 2 号 小朋友最终在 3 号洞口停留。 

【数据范围】

对于所有数据:1 ≤ m ≤ n,| xi |, | yi | ≤ 10^9。

耗时限制1000ms  内存限制512MB

解析

考点:模拟,链表

思路:

大模拟题。在理解题意后,可以分析出,要处理以下几个细节:

  1. 记录时间,按照时间模拟,直到所有小朋友都占领了一个窗口。—— 外层的代码框架可以是一个 while(cnt)
  2. 在每个时刻,要为每个小朋友确定左边和右边距离他/她最近的窗口。—— 对每个小朋友可以开两个数组去记录其左右最近的窗口
  3. 对每个小朋友,要根据左右窗口的编号和距离确定小朋友移动的方向和距离。—— 上面记录了之后,这个细节就好处理了
  4. 对于每个窗口,如果有多个小朋友同时到达,要优先选择让编号小的朋友先占领它。—— 我们可以按编号顺序去对小朋友遍历,让小朋友先去移动,这样他们就能优先占领
  5. 对于已经被占领了的窗口,在后续处理时,要能够跳过(忽略)它。—— 这种逻辑,链表最适合啦

在处理移动时,可以每次移动“所有小朋友中距离其最近窗口的距离”中的最小距离。

时间复杂度:O(n+m2)

空间复杂度:O(n+m)

参考代码

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3+5;
int n, m, vis[N], lt[N], rt[N]; // lt,rt: 小朋友左侧和右侧窗口
long long y[N], dis[N];
pair<long long, int> x[N];  // 窗口和小朋友  first: 坐标 second: 原始编号
int xl[N], xr[N];  // 窗口链表
long long tim[N], xid[N];  // 停留时刻和窗口编号,注意时间可能超 int 开 long long

inline int find(int p) {  // 二分查找 lower_bound()
    int l = 1, r = n, mid, ans = n+1;
    while(l <= r) {
        mid = (l+r) >> 1;
        if(x[mid].first >= p) ans = mid, r = mid-1;
        else l = mid+1;
    }
    return ans;
}

inline void del(int p) {  // 链表删除
    int s = xl[p], t = xr[p];
    xr[s] = t; xl[t] = s;
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) { cin >> x[i].first; x[i].second = i; }
    for(int i = 1; i <= m; i++) cin >> y[i];
    // 用链表存储窗口,方便后面删除被占的窗口
    sort(x+1, x+n+1);
    for(int i = 1; i <= n; i++) {  
        xl[i] = i - 1;
        xr[i] = i + 1;
    } xr[0] = 1; xl[n+1] = n;
    // 两端设置两个超级远的窗口,方便后续计算最近窗口
    // 注意,不可设为 2e9,否则最近窗口可能会误判
    // 比如左侧为 x[0]=-20亿,y[i]=-9亿,右侧为 x[.]=9亿,则会误判 0 窗口最近
    x[0].first = -2e10; x[n+1].first = 2e10;
    // 初始化小朋友的左右窗口信息
    long long dis_min = 2e10;
    for(int i = 1; i <= m; i++) {
        int t = find(y[i]);
        if(t == n+1) lt[i] = n, rt[i] = n+1;  // 右侧没有窗口
        else if(x[t].first == y[i]) lt[i] = rt[i] = t;  // 初始就在窗口上
        else if(t == 1) lt[i] = 0, rt[i] = 1;  // 左侧无窗口
        else lt[i] = t-1, rt[i] = t;  // 左右均有窗口
        dis[i] = min(y[i]-x[lt[i]].first, x[rt[i]].first-y[i]); 
        dis_min = min(dis_min, dis[i]);
    }
    
    long long cnt = m, clock = 0;  // cnt: 现有未占窗口的小朋友数量,clock:现在时间
    while(cnt) {
        // 开始移动,找到会占窗口的小朋友,并标记对应窗口
        clock += dis_min;
        for(int i = 1; i <= m; i++) {
            if(xid[i]) continue;  // 已占好一个窗口了,跳过
            int t = (y[i]-x[lt[i]].first==dis[i]?lt[i]:rt[i]); // 编号小优先
            if(x[rt[i]].first-y[i] == y[i]-x[lt[i]].first && x[rt[i]].second < x[lt[i]].second) {
                t = rt[i];            
            }
            if(dis[i] == dis_min && !vis[t]) { // 因为先遍历编号小的,因此此时可以直接占用
                tim[i] = clock; xid[i] = x[t].second;  // 记录下来占用的时间和窗口
                vis[t] = 1;
                del(t);
                // cout << y[i].second << " ocu " << x[t].second << endl;
                cnt--;
            }
            int dir = (t==lt[i]?-1:1);
            y[i] += dir*dis_min;  // 更新位置
        }
        // 更新小朋友的 lt, rt, dis
        dis_min = 2e10;
        for(int i = 1; i <= m; i++) {
            if(xid[i]) continue;  // 已占好一个窗口了,跳过
            while(vis[lt[i]]) lt[i] = xl[lt[i]];  // 左右跳过已被占领的窗口
            while(vis[rt[i]]) rt[i] = xr[rt[i]];
            
            dis[i] = min(y[i]-x[lt[i]].first, x[rt[i]].first-y[i]);
            dis_min = min(dis_min, dis[i]);
        }
        
    }
    for(int i = 1; i <= m; i++)
        cout << tim[i] << ' ' << xid[i] << endl;
    return 0;
}

写法2

#include<iostream>
#include<algorithm> 
using namespace std;
const int N=1010;
const long long INF=1E12; 
struct node{
	long long  x,no;  //窗口坐标 编号 
}a[N]; 
int n,m;
long long y[N],w[N],d[N],s,t[N],c[N];  //y[i] 第i个小朋友的位置 w数组表示小朋友想去的窗口编号 d数组表示方向 
bool st[N];
bool cmp(node u,node v){  //窗口按坐标从小到大排序 
	return u.x<v.x;
}
//左边界,找到第一个>=k的窗口下标
int bsearch(int l,int r,long long k){
	while(l<r){
		int mid=(l+r)>>1;
		if(a[mid].x>=k){
			r=mid;
		}else{
			l=mid+1;
		}
	}
	return l;
} 
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i].x,a[i].no=i;
	for(int i=1;i<=m;i++) cin>>y[i];
	sort(a+1,a+1+n,cmp);//窗口按坐标从小到大排序 
	//给右边添加一个边界
	//1.后面的二分查找要找到第一个>=y[i]的窗口,如果所有窗口都在第i个小朋友的左边,就会找不到
	//2.为什么要这是为1e12?// 注意,不可设为 2e9,否要比这个值大 
    // 比如左侧为 x[0]=-1e9,y[i]=1e9,右侧为 x[.]=2e9亿,那么y[i]离右边更近,但右边实际上是一个不存在的值
	a[n+1].no=n+1,a[n+1].x=1e12;
	int cnt=0;  //记录找到窗口的小朋友的坐标
	while(cnt<m){ //每次处理一个小朋友 
		//计算每个小朋友的左边和右边距离他/她最近的窗口 ,以及这些小朋友中第一个到达目标窗口的最短距离 
		long long dis=INF;
		for(int i=1;i<=m;i++){
			if(st[i]==true) continue;  //已经找到窗就跳出 
			int u=bsearch(1,n+1,y[i]);
			long long l=INF,r=INF,k;
			//没越界,计算左边最近的距离 
			if(u-1>=1) l=y[i]-a[u-1].x;
			if(u<=n) r=a[u].x-y[i];  //右边的距离
			//w数组表示小朋友想去的窗口编号 d数组表示方向 
			if(l<r){
				w[i]=u-1,k=l,d[i]=0;  //0左移 
			}else if(l>r){
				w[i]=u,k=r,d[i]=1; //1右移 
			}else{
				if(a[u].no<a[u-1].no){
					w[i]=u,k=r,d[i]=1; //k记录左边和右边距离的最小值 
				}else{
					w[i]=u-1,k=l,d[i]=0;
				}
			}
			dis=min(dis,k); //当前剩余小朋友第一个到达目的窗口所花费的最小时间 
		}
		s+=dis;//每一次到达目标窗口小朋友花费的时间
		int idx=0; //idx 当前一次移动确定窗口的下朋友的序号
		//移动操作 
		for(int i=1;i<=m;i++){
			if(st[i]==true) continue;  //已经找到窗就跳出
			if(d[i]==0)
				y[i]-=dis;
			else
				y[i]+=dis;
			if(idx==0&&y[i]==a[w[i]].x){
				idx=i,t[idx]=s,c[idx]=a[w[i]].no; //t[i]第i位小朋友到达窗口所需要的时间 c[i] 第iWie小朋友停留的窗口 
			} 
		} 
		cnt++;
		//删除被当前小朋友占用的窗口
		for(int i=w[idx];i<=n;i++) a[i]=a[i+1];
		st[idx]=true;  //后面就不用再移动了 
	}
	for(int i=1;i<=m;i++) cout<<t[i]<<" "<<c[i]<<endl;
	return 0;
}

2022年合肥市信息学市赛初中组-T4圣诞节(christmas)

题目描述

一年一度的圣诞节快到了,小 C 要在家门口准备一棵圣诞树。 

这个圣诞树可以看成一个树结构,即由 n 个点、n − 1 条边构成的一个连通图。 

小 C 要在每个节点处放置一个铃铛。铃铛上写有一个 1 到 W 之间的正整数 x,其中 W 是小 C 预先给定的。 

设第 i 个节点处的铃铛上的正整数为 wi,对于树上所有的边 (u, v),如果都满足 |wu − wv| ≤ k,那么称这棵圣诞树是优美的,其中 k 也是小 C 预先给定的。 

现在小 C 想知道有多少种方案能得到优美的圣诞树。答案对 10^9 + 9 取模。

输入格式

从文件 christmas.in 中读取数据。

第一行三个整数,分别为 n、k、W,n 表示树的大小,k、W 如题所述;

接下来 n − 1 行,每行两个数 u、v,表示一条树边 (u, v),描述了树的形态。

输出格式

输出到文件 christmas.out 中。

仅一行,一个数表示方案数对 1000000009(= 10^9 + 9)取模的结果。

输入输出样例

输入样例1:
5 1 3 
1 2 
1 3 
2 4 
2 5
输出样例1:
103
输入样例2:
10 2 4 
1 2 
1 3 
1 4 
3 5 
3 6 
4 7 
4 8 
2 9 
5 10
输出样例2:
379990

说明

耗时限制1500ms  内存限制512MB

解析

考点:动态规划,树形DP

思路:

时间复杂度:O(n2k)

由于常数较大,会超时部分点。

参考代码

#include<bits/stdc++.h>
using namespace std;

const int maxn = 820;
const int mod = 1e9+9;

int n,W,k;
vector<int> T[maxn];
int len[maxn],f[maxn][maxn*100],g[maxn][maxn*100],mid[maxn];
int low[maxn],upp[maxn];
// f weight near zero
// g weight near W

void dfs1(int now,int fa){
    for(auto x:T[now]){
    if(x != fa) {
        dfs1(x,now);
        len[now] = max(len[now],len[x]);
    }
    }
    len[now]++;
}

int getsum(int x,int l,int r){
    int sum = 0;
    for(int j=l;j<=r;j++){
    if(j <= low[x]) sum = sum + f[x][j];
    else if(j >= upp[x]) sum = sum + g[x][j-upp[x]];
    else sum = sum + mid[x];
    if(sum >= mod) sum -= mod;
    }
    return sum;
}

void dfs2(int now,int fa){
    mid[now] = 1;
    for(auto x:T[now])
    if(x != fa) {
        dfs2(x,now);
        mid[now] = 1ll*mid[now]*mid[x]%mod;
        mid[now] = 1ll*mid[now]*k%mod;
    }
    low[now] = min(W,(len[now]-1)*k+1);
    upp[now] = max(W-(len[now]-1)*k,1);
    if(upp[now] <= low[now]) upp[now] = low[now]+1;
    for(int i=1;i<=low[now];i++){
    f[now][i] = 1;
    for(auto x:T[now]){
        if(x == fa) continue;
        int sum = getsum(x,max(i-k,1),min(W,i+k));
        f[now][i] = 1ll*f[now][i]*sum%mod;
    }
    }
    for(int i=upp[now];i<=W;i++){
    g[now][i-upp[now]] = 1;
    for(auto x:T[now]){
        if(x == fa) continue;
        int sum = getsum(x,max(i-k,1),min(W,i+k));
        g[now][i-upp[now]] = 1ll*g[now][i-upp[now]]*sum%mod;
    }
    }
}

int main(){
    cin >> n >> k >> W;
    for(int i=1;i<n;i++){
    int x,y; cin >> x >> y;
    T[x].push_back(y);
    T[y].push_back(x);
    }
    dfs1(1,0);
    dfs2(1,0);
    int ans = 0;
    for(int i=1;i<=low[1];i++){
    ans = (ans + f[1][i])%mod;
    }
    for(int i=upp[1];i<=W;i++){
    ans = (ans + g[1][i-upp[1]])%mod;
    }
    ans = ans + 1ll*mid[1]*(upp[1]-low[1]-1)%mod;
    ans %= mod;
    cout << ans << endl;
    return 0;
}

DP 前缀和优化为 O(nk)

#include<bits/stdc++.h>
using namespace std;

const int maxn = 820;
const int mod = 1e9+9;

int n,W,k;
vector<int> T[maxn];
int len[maxn],mid[maxn];
vector<int> f[maxn],g[maxn];
int low[maxn],upp[maxn];
// f weight near zero
// g weight near W

void dfs1(int now,int fa){
    for(auto x:T[now]){
    if(x != fa) {
        dfs1(x,now);
        len[now] = max(len[now],len[x]);
    }
    }
    len[now]++;
}

int getsum(int x,int l,int r){
    int sum = 0;
    //Caculate near 0
    int lrange = min(l,low[x]+1);
    int rrange = min(r,low[x]);
    sum += (f[x][rrange]-f[x][lrange-1]+mod)%mod;
    if(sum >= mod) sum -= mod;
    //Caculate near W
    lrange = max(l,upp[x]);
    rrange = max(r,upp[x]-1);
    sum += (g[x][rrange-upp[x]+1]-g[x][lrange-upp[x]]+mod)%mod;
    if(sum >= mod) sum -= mod;
    //Caculate middle
    lrange = max(low[x]+1,l);
    rrange = min(upp[x]-1,r);
    if(lrange > rrange) return sum;
    else return (sum+1ll*(rrange-lrange+1)*mid[x]%mod)%mod;
}

void dfs2(int now,int fa){
    mid[now] = 1;
    for(auto x:T[now])
    if(x != fa) {
        dfs2(x,now);
        mid[now] = 1ll*mid[now]*mid[x]%mod;
        mid[now] = 1ll*mid[now]*(2*k+1)%mod;
    }
    low[now] = min(W,(len[now]-1)*k+1);
    upp[now] = max(W-(len[now]-1)*k,1);
    
    f[now].resize(low[now]+5);
    g[now].resize(W-upp[now]+5);

    if(upp[now] <= low[now]) upp[now] = low[now]+1;
    for(int i=1;i<=low[now];i++){
    f[now][i] = 1;
    for(auto x:T[now]){
        if(x == fa) continue;
        int sum = getsum(x,max(i-k,1),min(W,i+k));
        f[now][i] = 1ll*f[now][i]*sum%mod;
    }
    }
    for(int i=upp[now];i<=W;i++){
    g[now][i-upp[now]+1] = 1;
    for(auto x:T[now]){
        if(x == fa) continue;
        int sum = getsum(x,max(i-k,1),min(W,i+k));
        g[now][i-upp[now]+1] = 1ll*g[now][i-upp[now]+1]*sum%mod;
    }
    }
    for(auto x:T[now]){
    if(x == fa) continue;
    f[x].clear();
    g[x].clear();
    }
    //Get prefix sum
    for(int i=1;i<=low[now];i++){
    f[now][i] = f[now][i-1]+f[now][i];
    if(f[now][i] >= mod) f[now][i] -= mod;
    }
    for(int i=1;i<=W-upp[now]+1;i++){
    g[now][i] = g[now][i-1]+g[now][i];
    if(g[now][i] >= mod) g[now][i] -= mod;
    }
}

int main(){

    cin >> n >> k >> W;
    for(int i=1;i<n;i++){
    int x,y; cin >> x >> y;
    T[x].push_back(y);
    T[y].push_back(x);
    }
    dfs1(1,0);
    dfs2(1,0);
    
    int ans = ((f[1][low[1]]+g[1][W-upp[1]+1])%mod+1ll*mid[1]*(upp[1]-low[1]-1)%mod)%mod;
    cout << ans << endl;
    return 0;
}

一等200,二等140

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值