6.19 杂题
【山东省选集训 2023】T1. 树染色
有多少种选出 的方法,使得:
- 任意 是 祖先;
- ;对于任意 ,存在 使得 在 的路径上;
- 所有边被至少一条路径 覆盖。
- jinkenengxiao
对每组 指定黑或白的颜色,按照 从小到大考虑每对 ,若一条边第一次被染成黑则其价值为 ,否则其价值为 。对于一种染色方式,其价值定义为所有边的价值积。求所有方案(选 个二元组 + 指定颜色)的价值和。
。
TBD
【山东省选集训 2023】T2. 关路灯
TBD
【山东省选集训 2023】T3. 树状数组
TBD
【He_Ren 模拟赛】T3. 兔子
在一棵 个点的树上,住着 只兔子,第 只住在 , 不需要互异, 未知。
有 条线索 表示当将 视为根时,。
看到这种限制,应该能有种 2-sat 的感觉,也可能有种网络流的感觉。到底是哪个呢?
观察1:将 视作根属于花里胡哨的条件,因为以不同点作为根,同一个点的子树只会是 或 。
观察2:线索的限制相当于是说 和 不能同时处于 的同一个“儿子”的“子树”内(父亲也算“儿子”),且 都不在 所处的 的儿子子树内。
这样一来,我们需要设置 个变量 表示兔子 是否可以在 的子树内。这里的子树是在以 为根的意义下。
观察3: 之间存在隐藏的限制,即对于每只兔子而言,只有一个点是被居住的。这可以转化为对于每个点 ,, 中有至多一个为 ,。
尝试用“若……则……”命题表达这个关系组。关键在于“一个变量集合中只有至多一个真变量”的限制。当然可以直接说“如果我为1你们都为0”,但是这样对于每个儿子做一遍就是平方的建边,再乘以 就是立方,不够优秀。
【套路】当想用 2-sat 表达“一个变量集合中只有最多一个真变量”的限制时,可以借助前缀优化:另设 表示前 个变量中是否有一个真变量,则限制等价于:
- 若 ,则
- 若 ,则
- 若 ,则
- 若 ,则
(而 就等于 )。
你发现了其中的漏洞了吗?没错,可能出现 ,而 的诡异情况。
但这个算法在本题是正确的。
仔细分析一下,其正确性恰恰依赖了这个“漏洞”。如果我们在 的儿子中间发现了这样一种矛盾,那我们就说,兔子住在了点 。也就是说,一旦出现这种情况,这个“1”是会被默认成由 自身被住而贡献的。
另外,将限制转化为连边时需要特判掉 的情况。
复制// ubsan: undefined // accoders #include <bits/stdc++.h> using namespace std; namespace IO { const int buflen=1<<21; int x; bool f; char ch,buf[buflen],*p1=buf,*p2=buf,obuf[buflen],*p3=obuf; inline char gc(){return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;} inline void pc(char c){p3-obuf<buflen?(*p3++=c):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=c);} inline int read(){ x=0,f=1,ch=gc(); while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc(); return f?x:-x; } void print(int x){ if(x/10)print(x/10); pc(x%10+48); } void PP(){fwrite(obuf,p3-obuf,1,stdout);} } using IO::read; using IO::print; const int N=255,V=4*N*N; int n,m,q,o,ding,idx,is[N][N],pre[N][N],zhan[N],zai[N][N],fa[N],c[N]; vector<int>G[V],T[N]; int tp,dfc,Bcnt,dfn[V],low[V],stk[V],bel[V],instk[V]; struct tiaojian {int r,a,b,x;}lim[N]; inline void adde(int u,int v){//cerr<<u<<' '<<v<<'\n'; G[u].emplace_back(v); if(u<=o)u+=o; else u-=o; if(v<=o)v+=o; else v-=o; G[v].emplace_back(u); } void dfs(int x,int p){ zhan[++ding]=x; for(int i=1;i<=ding;i++)zai[x][zhan[i]]=1; int las=0; for(int y:T[x])if(y^p){ dfs(y,x); if(las){ for(int i=1;i<=m;i++){ adde(pre[i][las],pre[i][y]); adde(pre[i][las],is[i][y]+o); adde(is[i][y],pre[i][las]+o); adde(is[i][y],pre[i][y]); } } else { for(int i=1;i<=m;i++){ adde(is[i][y],pre[i][y]); } } las=y; } if(las){ for(int i=1;i<=m;i++){ adde(pre[i][las],is[i][x]); } } ding--; } void tarjan(int x){ dfn[x]=low[x]=++dfc; instk[x]=1,stk[++tp]=x; for(int y:G[x]){ if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); } else if(instk[y])low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]){ Bcnt++; while(tp){ instk[stk[tp]]=0; bel[stk[tp]]=Bcnt; if(stk[tp--]==x)break; } } } int sousuo(int id,int x,int p){ int tmp=0; for(int y:T[x])if(y^p){ if(tmp=sousuo(id,y,x))return tmp; } if(bel[is[id][x]]<bel[is[id][x]+o])return x; return 0; } namespace checker { int fa[N][N][9],dep[N][N]; void init(int rt,int x,int p){ fa[rt][x][0]=p; dep[rt][x]=dep[rt][p]+1; for(int i=1;i<=8;i++)fa[rt][x][i]=fa[rt][fa[rt][x][i-1]][i-1]; for(int y:T[x])if(y^p){ init(rt,y,x); } } inline int glca(int rt,int u,int v){ if(u==v)return u; if(dep[rt][u]>dep[rt][v])swap(u,v); for(int i=8;~i;i--)if(dep[rt][fa[rt][v][i]]>=dep[rt][u])v=fa[rt][v][i]; if(u==v)return u; for(int i=8;~i;i--)if(fa[rt][u][i]!=fa[rt][v][i])u=fa[rt][u][i],v=fa[rt][v][i]; return fa[rt][u][0]; } bool hefa(){ for(int i=1;i<=q;i++)if(glca(lim[i].r,c[lim[i].a],c[lim[i].b])!=lim[i].x)return 0; return 1; } } int main(){ freopen("rabbit.in","r",stdin);freopen("rabbit.out","w",stdout); n=read(),m=read(),q=read(); o=2*m*n; for(int i=1;i<n;i++)fa[i+1]=read()+1,T[i+1].emplace_back(fa[i+1]),T[fa[i+1]].emplace_back(i+1); for(int i=1;i<=q;i++)lim[i].r=read()+1,lim[i].a=read()+1,lim[i].b=read()+1,lim[i].x=read()+1; for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)is[i][j]=++idx,pre[i][j]=++idx; dfs(1,0); for(int i=1;i<=q;i++){ int arid=is[lim[i].a][lim[i].x]+o,brid=is[lim[i].b][lim[i].x]+o; for(int y:T[lim[i].x])if(y!=fa[lim[i].x]&&zai[lim[i].r][y]){ arid=is[lim[i].a][y]; brid=is[lim[i].b][y]; break; } if(lim[i].r!=lim[i].x){ G[arid].emplace_back(arid<=o?arid+o:arid-o);//cerr<<arid<<' '<<arid-o<<'\n'; G[brid].emplace_back(brid<=o?brid+o:brid-o);//cerr<<brid<<' '<<brid-o<<'\n'; } for(int y:T[lim[i].x])if(y!=fa[lim[i].x]){ adde(is[lim[i].a][y],is[lim[i].b][y]+o); } adde(is[lim[i].a][lim[i].x]+o,is[lim[i].b][lim[i].x]); } for(int i=1;i<=m;i++)G[is[i][1]+o].emplace_back(is[i][1]);//cerr<<is[i][1]+o<<' '<<is[i][1]<<'\n'; for(int i=1;i<=o*2;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=o;i++)if(bel[i]==bel[i+o]){puts("-1");return 0;} //for(int i=1;i<=o;i++)cerr<<bel[i]<<'_'<<bel[i+o]<<'\n'; for(int i=1;i<=m;i++){ c[i]=sousuo(i,1,0); cout<<c[i]-1<<' '; } //for(int i=1;i<=n;i++)checker::init(i,i,0); //assert(checker::hefa()); puts(""); return 0; } /* g++ -o rabbit.exe rabbit.cpp -O2 -lm -std=c++14 -Wall -Wextra ./rabbit.exe<in */
决斗(duel)
有 个人排成一排, 的能力值为 ,定义 , 表示第 个人是否可能获胜,其中比赛规则为:
- 每次选定两个相邻的人进行决斗,若能力值不相等则能力高者胜,否则由你任意指定胜者;
- 随后败者被移出序列,空缺自动补齐;
- 不断重复上述过程直至只剩下一个人——胜者。
现在给出 、一个序列 和一个 01矩阵 ,你需要数出有多少个序列 ,满足 ,且 当且仅当 。
。
重要观察: 最大者必胜(设为 );左右两侧的人在 没被消灭完之前不会越到另一侧去。
从而可知 能胜的充要条件是 且 能攻克 中所有人。
据此可实现区间 DP(记忆化搜索)。记录当前区间 ,能胜的下界 和取值的上界 (因为需要满足枚举的 取第一次最大值的前提)。朴素实现复杂度是 ,可通过前缀和优化至更低复杂度.
点击查看代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=45,mod=998244353; int n,m,ans,a[N],c[N]; char s[N][N]; inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);} namespace sub1{ void dfs(int x){ if(x==n+1){ for(int i=1;i<=n;i++){ if(c[i]==2)continue; int l=i,r=i,now=a[i]; int fl=1; for(int j=1;j<=n-1;j++){ if(l>1&&a[l-1]<=now)l--; else if(r<n&&a[r+1]<=now)r++; else {fl=0;break;} now++; } if(c[i]!=fl)return; } ans++; return; } for(int i=1;i<=m;i++)if(s[x][i]=='1')a[x]=i,dfs(x+1); } void solve(){ dfs(1); cout<<ans<<'\n'; } } namespace sub2 { bool pand(){ if(c[1]!=1)return 0; for(int i=2;i<=n;i++)if(c[i]!=2)return 0; return 1; } void solve(){ int ans=0; for(int i=1;i<=m;i++)if(s[1][i]=='1'){ int prod=1; for(int j=2;j<=n;j++){ int cnt=0; for(int k=1;k<=min(m,i+j-2);k++)cnt+=(s[j][k]=='1'); prod=(ll)prod*cnt%mod; } add(ans,prod); } cout<<ans<<'\n'; } } namespace zhengjie { const int N=35,M=45; int f[N][N][M][M]; void solve(){ for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++)for(int k=1;k<=m;k++){ if(c[i]!=0){ for(int t=j;t<=k;t++)if(s[i][t]=='1')f[i][i][j][k]++; } if(c[i]!=1){ for(int t=1;t<=min(j-1,k);t++)if(s[i][t]=='1')f[i][i][j][k]++; } } } for(int len=2;len<=n;len++){ for(int l=1,r=len;r<=n;l++,r++)for(int i=1;i<=m;i++)for(int j=1;j<=m;j++){ for(int k=l;k<=r;k++)for(int A=(c[k]==1?i:c[k]==2?1:1);A<=(c[k]==1?j:c[k]==2?j:min(i-1,j));A++)if(s[k][A]=='1'){ add(f[l][r][i][j],(ll)(l<k?f[l][k-1][max(i,A-(k-l-1))][min(j,A-1)]:1)*(k<r?f[k+1][r][max(i,A-(r-k-1))][min(j,A)]:1)%mod); } } } cout<<f[1][n][1][m]<<'\n'; } } int main(){ freopen("duel.in","r",stdin);freopen("duel.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&c[i]); for(int i=1;i<=n;i++){ scanf("%s",s[i]+1); } if(sub2::pand())sub2::solve(),exit(0); if(n<=5&&m<=5){ sub1::solve(); return 0; } zhengjie::solve(); return 0; } /* g++ -o duel.exe duel.cpp -O2 -lm -std=c++14 -Wall -Wextra ./duel.exe<ex_duel3.in */
序列(sequence)
定义一个上升为一个 。给定一个序列 ,对于每个 ,求有多少个本质不同的 的排列,其上升数为 。
。
一、 DP
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具