得分: \(100+27+20=147\)(\(T1\)巨水,\(T2,T3\)只能写暴力分)
\(T1\):深邃
比较套路的一眼题,显然是一个二分+贪心,感觉就是\(NOIP2018Day1T3\)弱化版。
我们可以考虑,对于两个祖先关系的关键点之间路径上的一个节点,我们如果选择了它,就必须选择它所有不含关键点的子树(显然,证明略)。
因此,我们便可以将一个关键点作为整棵树的根节点,然后对于每个节点,将其与它所有不含关键点的子树合并在一起,这样就方便操作了。
然后,易发现每个非关键点只可能与和它路径上不存在其他关键点的某一关键点被分在同一部分中。
而贪心的思想,每个点肯定尽量放到它子树中的某一关键点中(因为它的子节点能造成贡献的点集被它的祖先节点能造成贡献的点集包含)。
而对于要选择哪一个关键点,显然是选择还能放下的节点数最多,即当前节点数最少的。
但是要注意,在某一子树中,一旦选定了一个关键点,就不能再做修改了!
代码也是比较简单的:
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
using namespace std;
int n,k,flag,a[N+5],p[N+5],u[N+5],sz[N+5],ns[N+5];
vector<int> v[N+5];
class Tree
{
private:
int ee;
public:
int lnk[N+5];struct edge {int to,nxt;}e[N<<1];
I void add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
}T,NT;
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
I void dfs(CI x,CI lst,CI f)//第一遍DFS,预处理
{
for(RI i=(sz[x]=1,u[x]=p[x],T.lnk[x]);i;i=T.e[i].nxt)
{
if(!(T.e[i].to^lst)) continue;dfs(T.e[i].to,x,p[x]?x:f),
u[T.e[i].to]?(NT.add(x,T.e[i].to),u[x]=1):sz[x]+=sz[T.e[i].to];//对于每个节点,将其与它所有不含关键点的子树合并在一起
}
}
I int Check(CI x,CI val)//验证答案是否合法
{
RI i,t,s;for(v[x].clear(),ns[x]=sz[x],i=NT.lnk[x];i;i=NT.e[i].nxt)
{
if(t=Check(NT.e[i].to,val),!flag) return -1;//如果出现不合法情况,返回false
t&&(v[x].push_back(t),0),!p[NT.e[i].to]&&(ns[x]+=ns[NT.e[i].to]);//把关键点加入一个vector存储,如果遇到没被选择的非关键点,就将当前节点大小加上这个非关节点的大小
}
if(p[x]) return ns[x]>val&&(flag=0),x;if(!v[x].size()) return 0;//对于关键点,我们无法将它划入某一部分,因此直接判断它的大小是否大于val,然后退出;对于没有返回得到任何关键点的点,我们无需操作,直接退出
for(t=v[x][0],s=v[x].size(),i=1;i^s;++i) ns[v[x][i]]<ns[t]&&(t=v[x][i]);//找到这些节点中最小的节点
ns[t]+ns[x]<=val&&(ns[t]+=ns[x],ns[x]=0);return t;//若能合并则合并,并将其返回
}
int main()
{
freopen("deep.in","r",stdin),freopen("deep.out","w",stdout);
RI i,x,y,res,STO,hl,ORZ;for(F.read(n,k),i=1;i^n;++i) F.read(x,y),T.add(x,y),T.add(y,x);//读入,建树
for(i=1;i<=k;++i) F.read(a[i]),p[a[i]]=1;dfs(a[1],0,0),STO=1,ORZ=n;//读入,初始化
W(STO<=ORZ) flag=1,Check(a[1],hl=STO+ORZ>>1),flag?(res=hl,ORZ=hl-1):STO=hl+1;//二分答案
return printf("%d",res),0;//输出答案
}
\(T2\):黑暗
推\(DP\)式子推了很久没推出来,最后毅然交打表\(27\)分:(不会订正)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 30000
#define X 998244353
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
#define Dec(x,y) ((x-=(y))<0&&(x+=X))
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
int n,m;
class ListSolver//打表
{
private:
const int f[8][8]={{0},{0,1},{0,1,1},{0,4,3,1},{0,38,19,6,1},{0,728,230,55,10,1},{0,26704,5098,825,125,15,1},{0,1866256,207536,20818,2275,245,21,1}};
I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}//快速幂
public:
I int GetAns(CI x,CI y) {RI i,res=0;for(i=1;i<=x;++i) Inc(res,1LL*f[x][i]*Qpow(i,y)%X);return res;}//求答案
}L;
int main()
{
freopen("dark.in","r",stdin),freopen("dark.out","w",stdout);
RI Qtot,x,y;scanf("%d",&Qtot);W(Qtot--) scanf("%d%d",&x,&y),printf("%d\n",L.GetAns(x,y));//读入+输出
return 0;
}
\(T3\):幻想
直接\(KMP\)来\(O(n^2)\)大暴力。
正解是神仙般的后缀平衡树,懒得订正了。