6.19 杂题
【山东省选集训 2023】T1. 树染色
有多少种选出 \(\{(u_1,v_1),(u_2,v_2),...,(u_m,v_m)\}\) 的方法,使得:
- 任意 \(u_i\) 是 \(v_i\) 祖先;
- \(u_1=1\);对于任意 \(i\ge 2\),存在 \(j<i\) 使得 \(u_i\) 在 \(u_i\to v_i\) 的路径上;
- 所有边被至少一条路径 \(u_i\to v_i\) 覆盖。
- \(m\) jinkenengxiao
对每组 \((u_i,v_i)\) 指定黑或白的颜色,按照 \(i\) 从小到大考虑每对 \((u,v)\),若一条边第一次被染成黑则其价值为 \(a_i\),否则其价值为 \(b_i\)。对于一种染色方式,其价值定义为所有边的价值积。求所有方案(选 \(m\) 个二元组 + 指定颜色)的价值和。
\(n\le 5\times 10^5\)。
TBD
【山东省选集训 2023】T2. 关路灯
TBD
【山东省选集训 2023】T3. 树状数组
TBD
【He_Ren 模拟赛】T3. 兔子
在一棵 \(n\) 个点的树上,住着 \(m\) 只兔子,第 \(i\) 只住在 \(c_i\),\(c_i\) 不需要互异,\(c\) 未知。
有 \(q\) 条线索 \((r_i,a_i,b_i,x_i)\) 表示当将 \(r_i\) 视为根时,\(\operatorname{LCA}(c_{a_i},c_{b_i})=x_i\)。
\(n,m,q\le 250\)
看到这种限制,应该能有种 2-sat 的感觉,也可能有种网络流的感觉。到底是哪个呢?
观察1:将 \(r\) 视作根属于花里胡哨的条件,因为以不同点作为根,同一个点的子树只会是 \(T_u\) 或 \(\complement_{U}T_u\)。
观察2:线索的限制相当于是说 \(c_{a_i}\) 和 \(c_{b_i}\) 不能同时处于 \(x_i\) 的同一个“儿子”的“子树”内(父亲也算“儿子”),且 \(c_{a_i},c_{b_i}\) 都不在 \(r_i\) 所处的 \(x_i\) 的儿子子树内。
这样一来,我们需要设置 \(m\cdot n\) 个变量 \(v_{i,j}\) 表示兔子 \(i\) 是否可以在 \(j\) 的子树内。这里的子树是在以 \(1\) 为根的意义下。
观察3:\(v\) 之间存在隐藏的限制,即对于每只兔子而言,只有一个点是被居住的。这可以转化为对于每个点 \(u\),\(v_{i,u}\ge v_{i,son(u,j)}\),\(v_{i,son(u)}\) 中有至多一个为 \(1\),\(v_{i,1}=1\)。
尝试用“若……则……”命题表达这个关系组。关键在于“一个变量集合中只有至多一个真变量”的限制。当然可以直接说“如果我为1你们都为0”,但是这样对于每个儿子做一遍就是平方的建边,再乘以 \(m\) 就是立方,不够优秀。
【套路】当想用 2-sat 表达“一个变量集合中只有最多一个真变量”的限制时,可以借助前缀优化:另设 \(pre_i\) 表示前 \(i\) 个变量中是否有一个真变量,则限制等价于:
- 若 \(pre_{i-1}=1\),则 \(pre_i=1\)
- 若 \(pre_{i-1}=1\),则 \(v_i=0\)
- 若 \(v_i=1\),则 \(pre_{i-1}=0\)
- 若 \(v_i=1\),则 \(pre_i=1\)
(而 \(v_u\) 就等于 \(pre_{u,|son(u)|}\))。
你发现了其中的漏洞了吗?没错,可能出现 \(pre_{i-1}=0,v_i=0\),而 \(pre_i=1\) 的诡异情况。
但这个算法在本题是正确的。
仔细分析一下,其正确性恰恰依赖了这个“漏洞”。如果我们在 \(u\) 的儿子中间发现了这样一种矛盾,那我们就说,兔子住在了点 \(u\)。也就是说,一旦出现这种情况,这个“1”是会被默认成由 \(u\) 自身被住而贡献的。
另外,将限制转化为连边时需要特判掉 \(r=x\) 的情况。
// 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)
有 \(n\) 个人排成一排,\(i\) 的能力值为 \(a_i\),定义 \(f([a_1,a_2,...,a_n])=[b_1,b_2,...,b_n]\),\(b_i\in \{0,1\}\) 表示第 \(i\) 个人是否可能获胜,其中比赛规则为:
- 每次选定两个相邻的人进行决斗,若能力值不相等则能力高者胜,否则由你任意指定胜者;
- 随后败者被移出序列,空缺自动补齐;
- 不断重复上述过程直至只剩下一个人——胜者。
现在给出 \(n,m\)、一个序列 \(\{c_n\}(c_i\in [0,2])\) 和一个 \(n\time m\) 01矩阵 \(A\),你需要数出有多少个序列 ${a_n}$,满足 \(a_i\in [1,m],A_{i,a_i}=1\),且 \(f(a)_i=c_i\) 当且仅当 \(c_i\ne 2\)。
\(n\le 30,m\le 40\)。
重要观察:\(a_i\) 最大者必胜(设为 \(a_p\));左右两侧的人在 \([1,p-1]/[p+1,n]\) 没被消灭完之前不会越到另一侧去。
从而可知 \(\forall i\in [1,p-1]\) 能胜的充要条件是 \(a_i\ge a_p-(p-2)\) 且 \(i\) 能攻克 \([1,p-1]\) 中所有人。
据此可实现区间 DP(记忆化搜索)。记录当前区间 \([l,r]\),能胜的下界 \(i\) 和取值的上界 \(j\)(因为需要满足枚举的 \(p\) 取第一次最大值的前提)。朴素实现复杂度是 \(O(n^3m^3)\),可通过前缀和优化至更低复杂度.
点击查看代码
#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)
定义一个上升为一个 \(a_i<a_{i+1}\)。给定一个序列 \(\{a_n\}\),对于每个 \(k\in [0,n-1]\),求有多少个本质不同的 \(a\) 的排列,其上升数为 \(k\)。
\(n\le 5\times 10^5,1\le a_i\le n\)。
一、\(n^2\) DP