这场比赛都打的不太好,对我来说问题主要在于各个知识点的熟练程度不够,学过一个知识点刷的题少,而且早期学的知识点也没有进行总结,考场上脑袋里面根本就没有想到。所以说多复习多总结。
「雅礼集训 2017 Day8」共
description
问你有多少种本质不同的树满足深度为奇数的点有个。
solution
每条树边两端一定一端深度为奇数,另一端为偶数。
这样把点按照深度奇偶分成两类,发现表面上的树其实是个二分图。
因此问题转化为了文艺自动姬
若二分图左边点数,右边点数,二分图生成树的个数为。
因为根据prufer序共序列共选个点,因为二分图且最后两条边一定有边相连(两端一定左右不同类),所以左边删过次,每次右边加入序列都有种选法,同理右边删过次,左边加入序列有种选法。
这道题还要考虑点的排布,1号点已经固定了。所以答案为:
code:
戳我
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e6+5;
ll n,k,mod,jc[N];
ll ksm(ll a,ll b) {ll mul=1;for(;b;b>>=1,a=a*a%mod)if(b&1)mul=mul*a%mod;return mul;}
ll C(ll n,ll m) {return jc[n]*ksm(jc[n-m]*jc[m]%mod,mod-2)%mod;}
int main() {
scanf("%lld%lld%lld",&n,&k,&mod);
jc[0]=1;for(int i=1;i<=n;i++) {jc[i]=jc[i-1]*i%mod;}
printf("%lld\n",ksm(k,n-k-1)*ksm(n-k,k-1)%mod*C(n-1,k-1)%mod);
return 0;
}
[湖北省队互测week1]独钓寒江雪
description
求一棵无根树上本质不同的独立集的个数
solution
首先预处理各子树的树hash值。
表示选/不选的方案数。
树上dp时,如果两个子树的hash值不同(说明不同构),它们直接相乘即可。
如果两个子树的hash值相同,不过同构会带来重复。
递进一步,如果有个同构的子树,每个都可从种方案中选一种的组合
这和正常的组合数不同的地方在于,一个方案可以被多次选择。
这个问题的本质是集合(里面种元素,每种有无限个)的元组合数。(我在《组合数学》上面看到的,也可以用插板法理解)
套公式:答案是
这样,发现第三个样例输出了15,想了很久,想到老师上课说了要用重心当根,为什么呢?如果有两个重心呢?
于是我抱着疑问打开了题解……(转载于@WeLikeStudying)
发现选不同根可能会同构。(就会出现向上面那样算重)
如果选重心为根,那么如果和同构,也必将是重心。就不会存在选非重心的点与重心同构的情况。
如果是两个重心(它们之间肯定会有一条边)。
把这条边断掉,分别对两个联通块做dp.合并的时候还是判同构一下(处理跟上面是一样的)。
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=1e6+5;
const int mod=1e9+7;
int smx[N],rt,rt2,sz[N],nxt[N],to[N],head[N],ecnt,p[N],ptot,n;
ll dp[N][2];
bool is_p[N*10];
ull f[N];
ll ksm(ll a,ll b) {ll mul=1;for(;b;b>>=1,a=a*a%mod)if(b&1)mul=mul*a%mod;return mul;}
ll C(int x,int y) { //x is big but y is small
ll res=1,r2=1;
x%=mod;
for(int i=0;i<y;i++) {res=res*(x-i)%mod;r2=r2*(i+1)%mod;}
return res*ksm(r2,mod-2)%mod;
}
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void _xxs() {
int up=n*20;
for(int i=2;ptot<n;i++) {
if(!is_p[i]) {p[++ptot]=i;}
for(int j=1,x;j<=ptot&&(x=p[j]*i)<=up;j++) {
is_p[x]=1;
if(x%p[j]==0) break;
}
}
random_shuffle(p+1,p+1+ptot);
}
void gt_rt(int u,int fa) {
sz[u]=1;smx[u]=0;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];if(v==fa)continue;
gt_rt(v,u);sz[u]+=sz[v];
smx[u]=max(smx[u],sz[v]);
}
smx[u]=max(smx[u],n-sz[u]);
if(smx[u]<smx[rt]) {rt=u;}
}
void init(int u,int fa) {
f[u]=1;sz[u]=1;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];if(v==fa)continue;
init(v,u);
sz[u]+=sz[v];f[u]+=f[v]*p[sz[v]];
}
}
struct node {
int p;ull hs;
bool operator<(const node &u) const{return hs<u.hs;}
}A[N];
void DP(int u,int fa) {
dp[u][0]=dp[u][1]=1;
int atot=0;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
if(v!=fa)DP(v,u);
}
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];if(v==fa)continue;
A[++atot]=(node){v,f[v]};
}
sort(A+1,A+1+atot);
for(int i=1;i<=atot;i++) {
int x=A[i].p,cnt=1;
while(i<atot&&A[i+1].hs==A[i].hs) {cnt++;i++;}
// printf("!%d %d\n",x,cnt);
if(cnt==1) {
dp[u][0]=dp[u][0]*(dp[x][0]+dp[x][1])%mod;
dp[u][1]=dp[u][1]*dp[x][0]%mod;
}
else {
//C(t-1+k,k)
dp[u][0]=dp[u][0]*C(dp[x][0]+dp[x][1]+cnt-1,cnt)%mod;
dp[u][1]=dp[u][1]*C(dp[x][0]+cnt-1,cnt)%mod;
}
}
// printf("%d: %lld %lld\n",u,dp[u][0],dp[u][1]);
}
int main() {
scanf("%d",&n);
_xxs();
for(int i=1;i<n;i++) {int u,v;scanf("%d%d",&u,&v);add_edge(u,v),add_edge(v,u);}
rt=0;smx[0]=1e9;gt_rt(1,0);
// printf("!%d\n",rt);
for(int i=head[rt];i;i=nxt[i]) {
int v=to[i];
if(smx[v]==smx[rt]) {rt2=v;break;}
}
init(rt,rt2);DP(rt,rt2);
if(!rt2) {printf("%lld\n",((dp[rt][0]+dp[rt][1])%mod+mod)%mod);return 0;}
init(rt2,rt);DP(rt2,rt);
if(f[rt]!=f[rt2]) {
printf("%lld\n",((dp[rt][0]*(dp[rt2][0]+dp[rt2][1])+dp[rt][1]*dp[rt2][0])%mod+mod)%mod);
}
else {
// printf("!(%lld %lld) (%lld %lld)\n",dp[rt][0],dp[rt][1],dp[rt2][0],dp[rt2][1]);
printf("%lld\n",((dp[rt][0]*dp[rt2][1]+C(dp[rt][0]+1,2))%mod+mod)%mod);
}
return 0;
}
[2017 山东一轮集训 Day3]第二题
description
给一棵树,问满足存在同构(考虑子树顺序)的深度为抠出来的两个子树的最大的。
solution
正常的同构的定义是:交换子树后会相同,而这里会考虑子树顺序。
不像我卡在这里不会了,有的同学就自创了新的树hash。
当然正常的树hash肯定用不了了。比较冷门的括号序就可以恰当而完美地配合字符串hash解决这个问题。
然后就二分。找到每个点下层的所有点(dfs时)就可以找到。
每个点求它的子树hash,把那些刚刚找到的不要的部分删掉,相当于hash的合并,也挺好写的,不过还是对hash不熟调了很久。
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef unsigned long long ull;
int n,nn,dfn[N],Time,nxt[N],to[N],head[N],ecnt,In[N],Out[N];
vector<int> V[N];
ull Hash[N],seed=13,pw[N],H[N];
int tp,st[N],mxd[N],dep[N];
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void init(int u) {
dfn[++Time]=1;In[u]=Time;
// printf("%d ",u);
for(int i=head[u];i;i=nxt[i]) {
int v(to[i]);
mxd[v]=dep[v]=dep[u]+1;
init(v);
mxd[u]=max(mxd[u],mxd[v]);
}
dfn[++Time]=2;Out[u]=Time;
}
ull Sum(int l,int r) {return (l>r)?0:Hash[r]-Hash[l-1]*pw[r-l+1];}
void dfs(int u,int k) {
if(tp>k) V[st[tp-k]].push_back(u);
st[++tp]=u;
for(int i=head[u];i;i=nxt[i]) dfs(to[i],k);
tp--;
}
bool check(int mid) {
// printf("%d:~~~~~~~ \n",mid);
dfs(1,mid);
int htot=0;
for(int i=1;i<=n;i++) {
if(mxd[i]<dep[i]+mid) continue;
++htot;H[htot]=0;
int len=0,pre=In[i]; //(len)the digit of Hx
for(int j=0;j<V[i].size();j++) {
int x=V[i][j];
H[htot]=H[htot]*pw[In[x]-pre]+Sum(pre,In[x]-1);
pre=Out[x]+1;
}
H[htot]=pw[Out[i]-pre+1]*H[htot]+Sum(pre,Out[i]);
// printf("[%d,%d] %llu\n",pre,Out[i],H[htot]);
V[i].clear();
}
sort(H+1,H+1+htot);
for(int i=2;i<=htot;i++)if(H[i-1]==H[i])return 1;
return 0;
}
void solve() {
int l=0,r=n,bst;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) {bst=mid;l=mid+1;}
else r=mid-1;
}
printf("%d",bst);
}
void _init() {
init(1);nn=Time;
Hash[1]=dfn[1];for(int i=2;i<=nn;i++) Hash[i]=Hash[i-1]*seed+dfn[i];
pw[0]=1;for(int i=1;i<=nn;i++)pw[i]=pw[i-1]*seed;
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
int x;scanf("%d",&x);
for(int j=1;j<=x;j++) {int y;scanf("%d",&y);add_edge(i,y);}
}
_init();
solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人