当前位置: 代码迷 >> 综合 >> [赛后总结]COCI2016/2017 Round#2题解
  详细解决方案

[赛后总结]COCI2016/2017 Round#2题解

热度:59   发布时间:2023-10-22 21:56:35.0

文章目录

  • Go
    • 题面
      • 描述
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Tavan
    • 题面
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Nizin
    • 题目
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Prosjecni
    • 题目
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Zamjene
    • 题目
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Burza
    • 题目
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现

Go

题面

描述

Mirko很快厌倦了Jetpack Joyride并开始在他的手机上玩神奇宝贝GO。这个游戏的玩点之一就是神奇宝贝的进化。
为了进化物种为PiP_iPi?的神奇宝贝,Mirko必须提供KiK_iKi?个 用于该物种神奇宝贝的糖果。在神奇宝贝进化后,他能拿回222个糖果。神奇宝贝只能在为他们的物种准备的糖果的帮助下进化。
Mirko有NNN种神奇宝贝,并且为物种PiP_iPi?的神奇宝贝提供MiM_iMi?个糖果,他想知道他可以进化多少次神奇宝贝。
他还想知道哪个神奇宝贝可以进化最多次 。 如果有多个这样的神奇宝贝 , 输出一个编号最小的神奇宝贝。换句话说,在输入数据中最早出现的那个。
输入
第一行输入包含整数NNN(1≤N≤701≤N≤701N70)。表示神奇宝贝种类的数量。接下来2N2N2N行包含NNN组数据,其中包含:
●第2i2i2i行包含字符串PiP_iPi?,由最多202020个字组成。表示第iii种神奇宝贝的名称;
●第2i+12i+12i+1行包含整数KiK_iKi?(12≤Ki≤40012 ≤K_i ≤ 40012Ki?400)和MiM_iMi?(1≤Mi≤1041≤M_i ≤ 10^41Mi?104),分别第iii种神奇宝贝的进化所需的糖果数量和 o Mirko 为这种神奇宝贝准备的糖果总数。

输出

第一行输出一个整数。表示Mirko可以进化的神奇宝贝的总次数。
第二行输出一个字符串。表示可以进化最多次的神奇宝贝的名称。

分数分布

对于32%的数据, N=3 。
对于所有数据,正确输出第一行可以得到50%的分数, 正确输出第二行可以
得到另外50%的分数。

题解

签到题。
可以将每次进化送的两个糖果理解为:
第一次升级需要KiK_iKi?个糖果,之后升级只需Ki?2K_i-2Ki??2个糖果。
因此可得答案为max(0,?Mi?2Ki?2?)max(0,\lfloor \frac{M_i-2}{K_i-2} \rfloor)max(0,?Ki??2Mi??2??)
由于C++是向0取整,以此只需用int计算Mi?2Ki?2\frac{M_i-2}{K_i-2}Ki??2Mi??2?即可。
复杂度O(NL)O(NL)O(NL)

实现

//T1
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 70
#define MAXL 20
int n,ans;
char name[MAXN+5][MAXL+5];
int K[MAXN+5],M[MAXN+5];
int mid;
int get_val(int id){
    return (M[id]-2)/(K[id]-2);}
int main()
{
    //freopen("go.in","r",stdin);//freopen("go.out","w",stdout);scanf("%d",&n);for(int i=1;i<=n;i++){
    scanf("%s%d%d",name[i]+1,&K[i],&M[i]);if(get_val(i)>get_val(mid))mid=i;ans+=get_val(i);}printf("%d\n%s",ans,name[mid]+1);
}

Tavan

题面

描述

小Zeljko一直在阁楼里读他奶奶的旧信,并且发现了一个长度为NNN的单词 。
不幸的是,由于溢出的墨水,他不知道单词的内容。他把看不清的MMM个字母每个字母都用一个字符 替换后,在一张纸上重写了这个词。他把那张纸递给了他的奶奶,对于每个看不清的字母,奶奶给了他KKK个不同的可能 。 在那之后,Zeljko在笔记本中写下了所有可能的单词,并决定仔细查看他们的属性,以确定原始单词是什么。在看到笔记本上写下的单词后,他的奶奶意识到他们正在寻找的是按字典序排列的第XXX个单词。Zeljko在他们学校学习字母表的那天生病了,所以他要求你帮助他确定原来的单词。

输入

第一行输入包含整数NNN,MMM,KKKXXX(1≤N≤5001≤N≤5001N500,1≤M≤N1≤M ≤N1MN,1≤K≤261≤K≤261K26,1≤X≤1091≤X≤10^91X109)。分别表示单词的长度,看不清的字母的数量,奶奶给出的字母的数量和原单词是字典序的第几个。
第二行输入包含一个长度为NNN 的字符串,由小写英文字母和字符 组成 。
表示Zeljko找到的单词,其中字符表示看不清的字母。
接下来MMM行中的每一行包含一个长度为KKK的字符串,由KKK个不同的小写英文字母组成。第2+i2+i2+i行的 KKK个字母表示第iii个看不清的字母的KKK种可能。
保证XXX总是小于等于能构造出的单词的总数。

输出

输出一个字符串。表示原本的单词。

分数分布

对于30%的数据,M=1M=1M=1并且K=3K=3K=3
对于另外30%的数据,M=1M=1M=1

题解

简单题。
首先注意到看的清的字母对字典序没有任何影响,直接原样输出即可。
将每个看不清的字母的kkk种可能按字典序排好。显然字典序最小的一个串就是每个字母的第一种可能,之后每次求下一个字典序就让最后一个字母移向下一种可能,若已到达第kkk种就让前一个字母移向移向下一种可能,自己回到第一种可能。
这实际上就是kkk进制数进位的过程,因此直接将XXX转换为kkk进制就能确定现在每个字母取到那种可能了。
复杂度O(mk+n)O(mk+n)O(mk+n)

实现

//T2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;
#define MAXN 500
int n,m,k,x;
char s[MAXN+5];
char p[MAXN+5][26+5];
int main()
{
    //freopen("tavan.in","r",stdin);//freopen("tavan.out","w",stdout);scanf("%d%d%d%d",&n,&m,&k,&x);scanf("%s",s+1);for(int i=1;i<=m;i++){
    scanf("%s",p[i]);sort(p[i],p[i]+k);}x--;for(int i=strlen(s+1),j=m;i>=1;i--)if(s[i]=='#'){
    s[i]=p[j][x%k];x/=k;j--;}printf("%s",s+1);
}

Nizin

题目

描述

Do Geese See God?或者,Was It A Rat I Saw?没关系,这只是展示Mislav对回文的热爱的不必要的介绍。帮他解决以下任务!
AAANNN个整数构成的数组。我们说AAA是回文,如果Ai=AN?i+1A_i=A_{N-i+1}Ai?=AN?i+1? 对于每个iii都成立,其中AiA_iAi?表示数组AAA的第iii个元素,并且数组中的第一个元素编号是111
Mislav可以通过以下方式修改数组:在一次操作中,他选择该数组的两个相邻元素并用它们的和替换它们。注意,每次操作后数组中元素的数量将减少111
Mislav想知道为了让数组变为回文的,他必须做出最少多少次操作。

输入

第一行输入包含整数NNN(1≤N≤1061≤N≤ 10^61N106),表示数组中的元素个数。
第二行输入包含NNN个用空格分隔的正整数,表示Mislav数组中的元素。数
组中的数字最多为10910^9109

输出

输出一个整数。表示Mislav要把数组变为回文所需的最少操作次数。

分数分布

对于30%的数据,N≤10N≤10N10
对于60%的数据,N≤1000N ≤ 1000N1000

题解

简单题。
考虑从两边往中间解决这个问题。
Al&lt;ArA_l&lt;A_rAl?<Ar?因为两边必须相等,显然位置lll必须操作一次。
Al&gt;ArA_l&gt;A_rAl?>Ar?,同理。
Al=ArA_l=A_rAl?=Ar?满足条件,转移到到区间[l+1,r?1][l+1,r-1][l+1,r?1]
复杂度:O(n)O(n)O(n)

实现

//T3
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;
#define MAXN 1000000
#define LL long long
int n,l,r,cnt;
LL a[MAXN+5];
int main()
{
    //freopen("nizin.in","r",stdin);//freopen("nizin.out","w",stdout);scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%lld",&a[i]);l=1,r=n;while(l<r){
    if(a[l]==a[r])l++,r--;else if(a[l]<a[r])a[l+1]+=a[l],l++,cnt++;else a[r-1]+=a[r],r--,cnt++;}printf("%d",cnt);
}

Prosjecni

题目

描述

Slavko很无聊,所以他把正整数填到N?NN*NN?N的方阵中。
如果他填出来的方阵满足以下条件,他会特别高兴:
● 每行中的数字的平均值是一个位于同一行的整数。
● 每列中的数字的平均值是一个位于同一列的整数。
● 表中的所有数字都不同。
帮助Slavko找到一种会让他开心的填法。

输入

第一行输入包含整数N(1≤N≤1001≤N≤1001N100)。表示方阵的行数和列数。

输出

输出NNN行,每行输出NNN个由空格分隔的整数。令第iii行中的第jjj个数字对应于Slavko将在方阵的第iii行第jjj列写下的值。
所有数字必须大于000且小于10910^9109
如果有多个解决方案,则输出任意一个。
如果没有任何解决方案,则输出-1

分数分布

无特殊分数分布。

题解

吐血构造题。
首先nnn为奇数的情况使显然的,按顺序输出即可。
现在考虑nnn为偶数的情况。
考虑打n=4n=4n=4情况表。
写好暴力代码后,你会发现符合条件的矩阵相当的多,而题目只要求我们需找一个合法的解。
我们可以自己再添加一些条件使得构造更加方便。
首先再前面的大部分解中,都满足第一行为
1 2 3 6
容易发现6=n?(n?1)26=\frac{n*(n-1)}{2}6=2n?(n?1)?,我们显然可以规定第一行就为1,2,3,....,n?1,n?(n?1)21,2,3,....,n-1,\frac{n*(n-1)}{2}1,2,3,....,n?1,2n?(n?1)?
可以证明平均数就为n?1n-1n?1
类比nnn为奇数的情况,我们可以发现这个方阵的行和列都有一个固定的差。
显然nnn为偶数时差值是无法固定的,但仅有最后一行与倒数第二行的差值不同,是可以实现的。
考虑暴力枚举差值KKK,为了保证每个数都不同,规定K≥n?(n?1)2K≥\frac{n*(n-1)}{2}K2n?(n?1)?,再暴力枚举最后一行,得到一个合法的答案。
可以发现K=6K=6K=6时就有一个合法的解
1 2 3 6
7 8 9 12
13 14 15 18
31 32 33 36
通过找规律可以得到最后一行就是倒数第二行乘以n减去整行的和。
这样我们就可以归纳出整个矩阵的构造方法了。
复杂度:O(n2)O(n^2)O(n2)
然而实际上构造并没有这么简单,毕竟有很多奇奇怪怪的矩阵都有这些规律。
这里放一个最神奇的:
1 2 6 15
4 9 13 10
7 14 8 3
16 11 5 12

实现

//T4
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;
#define MAXN 1000000
#define LL long long
int n;
int p[105][105];
int main()
{
    //freopen("prosjecni.in","r",stdin);//freopen("prosjecni.out","w",stdout);scanf("%d",&n);if(n==2)printf("-1\n");else if(n%2){
    for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++)printf("%d ",n*(i-1)+j);printf("\n");}}else{
    for(int i=1;i<n;i++)p[1][i]=i;p[1][n]=n*(n-1)/2;for(int i=2;i<n;i++)for(int j=1;j<=n;j++)p[i][j]=p[i-1][j]+n*(n-1)/2;for(int j=1;j<=n;j++){
    p[n][j]=p[n-1][j]*n;for(int i=1;i<n;i++)p[n][j]-=p[i][j];}for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++)printf("%d ",p[i][j]);printf("\n");}}/*for(int i=1;i<=n;i++){int sum=0;for(int j=1;j<=n;j++)sum+=p[i][j];printf("...%d\n",sum/n);}for(int i=1;i<=n;i++){int sum=0;for(int j=1;j<=n;j++)sum+=p[j][i];printf("%d ",sum/n);}*/
}
/* 4 1 2 3 6 5 10 11 14 7 16 9 4 15 12 13 81 2 6 15 4 9 13 10 7 14 8 3 16 11 5 12 */

Zamjene

题目

描述

Dominik想象出了一系列正整数P1P_1P1?,P2P_2P2? ,.........,PNP_NPN?
让我们将他们排序之后的版本称为Q1,Q2,...,QNQ_1,Q_2 ,...,Q_NQ1?,Q2?,...,QN?
此外,他想象出了一个允许替换集合。如果一对(X,Y)(X,Y)(X,Y) 是允许替换集合的成员,Dominik可以交换数组PPP中位于XXXYYY处的数字。
Marin向Dominik进行了TTT次查询,每个查询都属于以下类型之一:

  1. 把数组PPP中位于AAABBB位置的两个数字交换。
  2. (A,B)(A,B)(A,B)添加到允许替换集合中。Marin可能给出已经在允许替换集合中的数对(A,B)(A,B)(A,B)
  3. 看看是否可以仅使用允许替换集合中的替换进行排序?可以以任意顺序使用替换,并且可以使每个替换任意次数。
  4. 如果位于AAA位置的数字可以仅使用允许的替换转移到位置BBB, 那么我们称AAABBB是链接的。我们把所有和AAA链接的位置构成的集合称为AAA的云。如果对于一个云中的每个元素jjj,都能在仅使用允许替换集合中的替换使得Pj=QjP_j=Q_jPj?=Qj?,那么我们称这个云是好的 。 你必须回答有多少对不同的满足以下条件的位置(A,B)(A,B)(A,B)
    i. 位置AAABBB不是链接的
    ii. AAABBB的云都不是好的
    iii. 如果我们将对(A,B)(A,B)(A,B)添加
    到允许的替换集合中,AAA的云(通过使AAABBB成为链接的来得到)变为好的。
    请注意:对(A,B)(A,B)(A,B)(B,A)(B,A)(B,A)被认为是完全相同的一对位置。

输入

第一行输入包含整数NNNMMM(1≤N,M≤1061≤N,M ≤ 10^61N,M106)。分别表示数组PPP的大小和查询的次数。
第二行输入包含NNN个整数P1,P2,...,PNP_1,P_2 ,...,P_NP1?,P2?,...,PN?(1≤Pi≤1061≤P_i≤ 10^61Pi?106)。表示数组PPP
接下来MMM行中的每一行都包含以下格式的查询:
● 行中的第一个数字是查询的类型TTT。类型有1,2,3,4四种,对应题目中说的四种查询。
● 如果是查询类型TTT12,会跟着两个不同的整数AAABBB(1≤A,B≤N1≤A,B≤N1A,BN) 。
表示前两种询问中的(A,B)(A,B)(A,B)

输出

对于每个类型TTT34的查询,在单独的一行中输出答案。
查询类型3的答案是DA(表示可以只通过替换排序)或NE(表示不可以只通过替换排序),没有引号。
查询类型4的查询答案是一个非负整数。表示满足条件的位置对的数量。

分数分布

对于50%的数据,N≤1000N≤1000N1000M≤1000M≤1000M1000

题解

有些恶心的优化数据结构题。
首先读懂题目就是一个大问题,现在来翻译一下

操作 要求
1 交换两个位置上的元素
2 合并两朵云
3 所有的云是否都是
4 计算有多少组的云合在一起之后就成了的,权值是两个云大小的乘积

4个操作是相当难以解决的,试着先解决前3个操作的情况。
通过操作2,我们很容易想到用并查集。
并且该题没有位置的分离操作,不需要可持久化。
现在的关键问题就是如何在支持一朵云单点修改的同时快速判定一朵云的好坏。
我们知道只要一朵云内包含元素种类和个数与目标序列QQQ中对应的云都相同,那么这朵云就是好的。
然而我们并不能直接开数组统计,毕竟时间和空间复杂度都无法承受。
考虑使用玄学的哈希来检验。
这里使用cntnPn+...+cnt2P2+cnt1P+cnt0cnt_nP^n+...+cnt_2P^2+cnt_1P+cnt_0cntn?Pn+...+cnt2?P2+cnt1?P+cnt0?统计(取P=1000003P=1000003P=1000003)。
合并时将两个数直接相加即可,单点修改也类似。
考虑先预处理数列QQQ的hash值,将其与云种PPP的hash值比较,就可以判断这朵云的好坏了。每次修改时统计云好坏的个数,操作3也完成了。
现在我们再来考虑操作4
设两朵云AAABBB
设它们的PPP值,QQQ值分别为PAP_APA?,QAQ_AQA?,PBP_BPB?,QBQ_BQB?
如果要计入显然要满足条件PA+PB=QA+QBP_A+P_B=Q_A+Q_BPA?+PB?=QA?+QB?PA≠QA,PB≠QBP_A≠Q_A,P_B≠Q_BPA???=QA?,PB???=QB?
发现式子和AAA,BBB都有关,这样做显然是O(n2)O(n^2)O(n2)的。
因此需要变换一下,得到QA?PA=PB?QBQ_A-P_A=P_B-Q_BQA??PA?=PB??QB?PA?QA≠0,PB?QB≠0P_A-Q_A≠0,P_B-Q_B≠0PA??QA???=0,PB??QB???=0
我们只需要统计Q?PQ-PQ?P互为相反数,且Q?PQ-PQ?P不为零的对的个数就可以了。
这显然可以用map进行维护。[下标为Q?PQ-PQ?P,值为云的大小的总和]
每次进行1,2操作时,我们只需要动态维护一下Q?PQ-PQ?P的值,同时增加或删除对数的个数即可。
这样,整道题就顺利地解决了。
复杂度O(nlogn+nα(n))O(nlogn+nα(n))O(nlogn+nα(n))(这里假设nnn,mmm同阶)
常数相当大。

实现

//T5
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
#include<map>
using namespace std;
#define LL long long
#define MAXN 1000000
#define HASH 1000003
int n,Q,p[MAXN+5],q[MAXN+5],bad[MAXN+5],fa[MAXN+5],siz[MAXN+5];
LL pows[MAXN+5],nid[MAXN+5],gol[MAXN+5],cpairs;
map<LL,int> DIC;
void add_dic(int vd,LL val,int pn)
{
    if(val!=0)cpairs+=vd*pn*DIC[-val];DIC[val]+=vd*pn;
}
void add_hash(int vd,int u,int pos,int val)
{
    gol[u]+=vd*pows[pos];nid[u]+=vd*pows[val];siz[u]+=vd;
}
int xfind(int x){
    return fa[x]=(x==fa[x])?x:xfind(fa[x]);
}
void merge(int x,int y)
{
    x=xfind(x),y=xfind(y);if(x==y)return ;add_dic(-1,gol[x]-nid[x],siz[x]);add_dic(-1,gol[y]-nid[y],siz[y]);fa[y]=x;gol[x]+=gol[y],nid[x]+=nid[y],siz[x]+=siz[y];add_dic(1,gol[x]-nid[x],siz[x]);
}
int main()
{
    //freopen("zamjene.in","r",stdin);//freopen("zamjene.out","w",stdout);scanf("%d%d",&n,&Q);pows[0]=1;for(int i=1;i<=MAXN;i++)pows[i]=pows[i-1]*HASH;for(int i=1;i<=n;i++){
    scanf("%d",&p[i]);q[i]=p[i];fa[i]=i;siz[i]=1;}sort(q+1,q+n+1);for(int i=1;i<=n;i++){
    gol[i]=pows[q[i]];nid[i]=pows[p[i]];add_dic(1,gol[i]-nid[i],siz[i]);}while(Q--){
    int kdf,a,b;scanf("%d",&kdf);if(kdf==1){
    scanf("%d%d",&a,&b);int u=xfind(a),v=xfind(b);if(u==v){
    swap(p[a],p[b]);continue;}add_dic(-1,gol[u]-nid[u],siz[u]);add_dic(-1,gol[v]-nid[v],siz[v]);add_hash(-1,u,q[a],p[a]);add_hash(-1,v,q[b],p[b]);swap(p[a],p[b]);add_hash(1,u,q[a],p[a]);add_hash(1,v,q[b],p[b]);add_dic(1,gol[u]-nid[u],siz[u]);add_dic(1,gol[v]-nid[v],siz[v]);}if(kdf==2){
    scanf("%d%d",&a,&b);merge(a,b);}if(kdf==3)printf((DIC[0]==n)?"DA\n":"NE\n");if(kdf==4)printf("%lld\n",cpairs);}
}

Burza

题目

描述

Daniel厌倦了找工作,所以他决定去拜访他的朋友 Stjepan。 令人惊讶的是,当他进入Stjepan 的家时,他遇到了一棵有NNN个节点的树,节点用111NNN的数字表示。1 号节点有一枚硬币。
Stjepan 告诉他 : “ 戴上这个眼罩 , 我们开始玩游戏吧 ! ”l Daniel 给了他一个奇怪的表情,但决定按他说的做。然后Stjepan告诉他游戏规则:
●Daniel选择一个节点并标记它
●Stjepan将硬币移动到一个与硬币当前所在的节点相邻的未被标记的节点
●Stjepan标记硬币刚刚离开的那个节点
这三个步骤重复,直到Stjepan再也无法移动。由于被蒙了住眼睛,Daniel并不清楚某个时刻硬币在哪个节点上。然而,他很清楚树的轮廓以及硬币在游戏开始时的位置。
Daniel突然意识到他在过去的两个小时里没有申请过一份工作,所以他希望能够快速完成游戏。现在他想知道他是否能以这样一种方式进行比赛:无论Stjepan做出什么动作,比赛都在最多kkk次动作前结束 。换句话说,Stjepan使硬币移动的次数少于kkk次。
帮助Daniel确定他是否能按时完成游戏,然后回去将他的简历发送给他从未听说过的公司。

输入

第一行输入包含两个整数NNNKKK(1≤K≤N≤4001≤K≤N≤4001KN400)。分别表示树的节点个数和Daniel要求必须小于的移动次数。
接下来N?1N-1N?1行每行包含两个整数AAABBB(1≤A,B≤N1≤A,B≤N1A,BN)。表示AAA号点和BBB号点之间有一条无向边。保证给定的图形是树。

输出

如果Daniel能按时完成游戏,那么输出DA 。否则输出NE 。输出均不含括号。

分数分布

无特殊分数分布。

题解

一道神奇的题目。
首先我们需要处理一下这棵树,设一号节点深度为0,那么深度大于kkk的所有点都可以删除,另外,我们规定所有深度为kkk的节点为叶子节点。
可以注意到的是,在深度越高的地方标记就越优,随着硬币的移动,那么在除第0层的每一层,都有且只有一个标记。
接下来就是玄学操作了。
注意到k,mk,mk,m同阶,而仅从k=mk=mk=m来看,我们发现kkk取一些值时总是必胜的。
我们可以考虑缩小kkk的范围。
考虑以下两种极端情况:
[赛后总结]COCI2016/2017 Round#2题解
[赛后总结]COCI2016/2017 Round#2题解
显然,图2要比图1更为极端一些,虽然图1的节约了两个点,但第1层一旦被堵,就会多损失k?1k-1k?1个节点。因此我们应该用图二来算极端情况。
此时的叶子节点数量为?n?1k?\lfloor \frac{n-1}{k} \rfloor?kn?1??,当?n?1k?≤k\lfloor \frac{n-1}{k} \rfloor≤k?kn?1??k时,就肯定为必胜策略。
可得?n?1k?≤nk≤k\lfloor \frac{n-1}{k} \rfloor≤\frac{n}{k}≤k?kn?1??kn?k从而得出n≤k2n≤k^2nk2
因此kkk的范围立即缩小为了k&lt;20k&lt;20k<20
因为每一层都有且只有一个标记,深度小于20,这明显是可以用状压dp解决的问题。
由于状压允许深度是无序的,我们就可以规定叶子的dp是有序的。
因此设计dp[i][S]dp[i][S]dp[i][S]表示前iii个叶子是否可以通过堵住SSS中每个数值标记为1的对应深度上的其中一个节点来实现。
设深度d可以堵住的区间集合为{(Ld,1,Rd,1],(Ld,2,Rd,2],...,(Ld,p,Rd,p]}\{ (L_{d,1},R_{d,1}],(L_{d,2},R_{d,2}],...,(L_{d,p},R_{d,p}]\}{ (Ld,1?,Rd,1?],(Ld,2?,Rd,2?],...,(Ld,p?,Rd,p?]}(其中仅能任选其一堵住)
我们将Ld,kL_{d,k}Ld,k?作为下标将编号插入vector中。
那么dp转移式就可以写为:
dp[Rd,k][S∣1&lt;&lt;(d?1)]∣=dp[i][S](i=Ld,k)dp[R_{d,k}][S|1&lt;&lt;(d-1)]|=dp[i][S](i=L_{d,k})dp[Rd,k?][S1<<(d?1)]=dp[i][S](i=Ld,k?)
最后检查dp[ct][S]dp[ct][S]dp[ct][S](ct为叶子数量)是否有一个为1即可。
时间复杂度:O(n2n)O(n2^{\sqrt n})O(n2n ?)

实现

//T6
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
#define MAXN 400
#define MAXK 19
int n,k,u,v,dep[MAXN+5];
int Lid[MAXN+5],Rid[MAXN+5],ct;
char dp[MAXN+1][1<<MAXK];
vector<int> G[MAXN+5],P[MAXN+5];
void dfs(int x,int fa)
{
    dep[x]=dep[fa]+1;if(dep[x]==k+1){
    Lid[x]=ct++;Rid[x]=ct;return ;}Lid[x]=ct;for(int i=0;i<G[x].size();i++)if(G[x][i]!=fa)dfs(G[x][i],x);Rid[x]=ct;
}
int main()
{
    //freopen("burza.in","r",stdin);//freopen("burza.out","w",stdout);scanf("%d%d",&n,&k);if(k*k>=n){
    printf("DA\n");return 0;}for(int i=1;i<n;i++){
    scanf("%d%d",&u,&v);G[u].push_back(v);G[v].push_back(u);}dep[0]=0;dfs(1,0);dp[0][0]=1;for(int i=1;i<=n;i++)if(dep[i]>1)P[Lid[i]].push_back(i);for(int i=0;i<ct;i++)for(int S=0;S<(1<<k);S++){
    if(!dp[i][S])continue;for(int j=0;j<P[i].size();j++)if(!(S>>(dep[P[i][j]]-2) & 1))dp[Rid[P[i][j]]][S|(1<<(dep[P[i][j]]-2))]=1;}for(int S=0;S<(1<<k);S++)if(dp[ct][S]){
    printf("DA\n");return 0;}printf("NE");
}
  相关解决方案