6.19 杂题

【山东省选集训 2023】T1. 树染色

有多少种选出 \(\{(u_1,v_1),(u_2,v_2),...,(u_m,v_m)\}\) 的方法,使得:

  1. 任意 \(u_i\)\(v_i\) 祖先;
  2. \(u_1=1\);对于任意 \(i\ge 2\),存在 \(j<i\) 使得 \(u_i\)\(u_i\to v_i\) 的路径上;
  3. 所有边被至少一条路径 \(u_i\to v_i\) 覆盖。
  4. \(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

posted @ 2023-06-19 23:21  pengyule  阅读(18)  评论(0编辑  收藏  举报