atcoder 杂题 #05

atcoder 杂题 #05

abc340_g

独立想出了这道题。

如果我们确定了子图的叶子,那么这个子图就确定了。又由于叶子的颜色要相同,所以每种颜色的贡献是互相独立的。

首先如果一种颜色有 \(x\) 个点,那么这种颜色的贡献并不是 \(2^x-1\),因为可能有一个合法的子图中,这种颜色的点不在叶子处,答案就会多算。

考虑枚举颜色,把这种颜色的点取出来,建出虚树。然后在虚树上 DP。接下来如果一个点的颜色为我们枚举的这个颜色,我们称这个点有颜色。

\(f_i\) 表示子树 \(i\) 内的合法子图数,这里的合法子图指包含 \(i\) 的合法子图或者是空集,如果是空集就要求 \(i\) 有颜色,这样才能不多算。那么就有

\[f_i=\prod_{j\in son_i} f_j \]

接下来,对于一个有颜色的点 \(i\),它可以作为合法子图的叶子,所以它的度数没有限制,答案直接加上 \(f_i\),再让 \(f_i\) 加一,表示可以为空集。

而对于一个没有颜色的点 \(i\),它不可以作为合法子图的叶子,即度数要大于一,于是答案在加 \(f_i\) 的基础上还要减去度数为一的子图和空集。即

\[ans+f_i-1-\Big(\sum_{j\in son_i}f_j-1\Big)\to ans \]

里面的 \(f_j-1\) 是因为要减去 \(j\) 子树为空集的情况。

时间复杂度的瓶颈在于建虚树,时间复杂度 \(O(n\log n)\)

AC 代码:

const int N=2e5+5;
vector<int> g[N],h[N];
int n,co[N];
vector<int> c[N];
int fa[N][18],dep[N],dfn[N],dfn1;
void dfs1(int x,int y){
	dep[x]=dep[y]+1,fa[x][0]=y,dfn[x]=++dfn1;
	fu(i,1,18)fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int v:h[x])if(v!=y)dfs1(v,x);
}
int lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=dep[x]-dep[y],j=0;i;i>>=1,++j)if(i&1)x=fa[x][j];
	if(x==y)return x;
	fd(i,17,0)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
int d[N*2],tot,Co;
const int mod=998244353;
int f[N],ans;
void dfs2(int x,int y){
	f[x]=1;
	int sum=0;
	for(int v:g[x]){
		if(v==y)continue;
		dfs2(v,x);
		f[x]=(ll)f[x]*f[v]%mod;
		sum=(sum+f[v])%mod;
		sum=(sum-1)%mod;
	}
	if(co[x]==Co){
		ans=(ans+f[x])%mod;
		f[x]=(f[x]+1)%mod;
	}
	else{
		ans=(ans+f[x])%mod;
		ans=(ans+mod-sum-1)%mod;
	}
}
signed main(){
	cin>>n;
	fo(i,1,n)cin>>co[i],c[co[i]].push_back(i);
	fu(i,1,n){
		int u,v; cin>>u>>v;
		h[u].push_back(v),h[v].push_back(u);
	}
	dfs1(1,0);
	fo(i,1,n){
		if(!c[i].size())continue;
		d[tot=1]=1;
		for(auto j:c[i])d[++tot]=j;
		auto cmp=[](int x,int y)->bool{return dfn[x]<dfn[y];};
		sort(d+1,d+1+tot,cmp);
		for(int o=tot,i=2;i<=o;++i)d[++tot]=lca(d[i],d[i-1]);
		sort(d+1,d+1+tot,cmp);
		tot=unique(d+1,d+1+tot)-d-1;
		fo(i,1,tot)g[d[i]].clear();
		auto add=[](int x,int y)->void{g[x].push_back(y),g[y].push_back(x);return;};
		fo(i,2,tot)add(d[i],lca(d[i],d[i-1]));
		Co=i;
		dfs2(1,0);
	}
	cout<<ans<<'\n';
	return 0;
}

abc340_f

除夕夜的 abc 在 12 月才改出来。

假设 \((x,y),(a,b),(0,0)\) 组成了面积为 \(1\) 的三角形。用正方形减去三个三角形得

\[ay-\frac{xy}2-\frac{ab}2-\frac{(a-x)(y-b)}2=1 \]

\[\frac {ay-bx} 2=1 \]

\[ay-bx=2 \]

用 exgcd 解出 \(a,b\) 即可,根据 exgcd 的技巧,当 \(\gcd(y,x)\nmid2\) 时无解,记得最后要输出 \(a,-b\)

AC 代码:

#define int ll
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return d;
}
signed main(){
	int a,b;
	cin>>a>>b;
	int x,y;
	int d=exgcd(a,b,y,x);
	if(2%d!=0){
		cout<<"-1";
		return 0;
	}
	x*=2/d,y*=2/d;
	cout<<-x<<" "<<y<<"\n";
	return 0;
}

abc361_g

还记得是上个学期 2019pzr 还在的时候他讲的这道题,当时他好像还讲复杂了,没有听懂。

其实并不难写。

首先有一个 \(O(V^2)\) 的暴力,对于平面上的每一个点用并查集合并。

优化。考虑对于每一个 \(x\),给定的点把 \(y\) 分成了很多个段,发现一共是 \(O(n+V)\) 个段,用并查集合并这些段,用双指针扫一遍合并相邻 \(x\) 的每个段即可。

AC 代码:

const int N=1e6+5;
int n;
struct arr{
	int l,r,num;
};
int tot;
int fa[N],sz[N];
int getfa(int x){
	if(fa[x]==x)return x;
	return fa[x]=getfa(fa[x]);
}
vector<arr> a[N];
vector<int> pt[N];
signed main(){
	cin>>n;
	fo(i,1,n){
		int x,y;
		cin>>x>>y;
		pt[x].push_back(y);
	}
	fo(i,0,2e5){
		sort(pt[i].begin(),pt[i].end());
		if(pt[i].size()==0){
			a[i].push_back({0,(int)2e5,++tot});
			fa[tot]=tot,sz[tot]=2e5+1;
		}
		else if(pt[i][0]>0){
			a[i].push_back({0,pt[i][0]-1,++tot});
			fa[tot]=tot,sz[tot]=pt[i][0];	
		}
		fu(j,0,pt[i].size()){
			if(j+1==pt[i].size())continue;
			int u=pt[i][j],v=pt[i][j+1];
			if(u+1<v){
				a[i].push_back({u+1,v-1,++tot});
				fa[tot]=tot,sz[tot]=v-u-1;
			}
		}
		if(pt[i].size()>0){
			a[i].push_back({pt[i][pt[i].size()-1]+1,(int)2e5,++tot});
			fa[tot]=tot,sz[tot]=2e5-pt[i][pt[i].size()-1];
		}
	}
	fo(i,0,2e5){
		if(a[i].front().l==0)fa[a[i].front().num]=1;
		if(a[i].back().r==(int)2e5)fa[a[i].back().num]=1;
	}
	for(auto i:a[0])fa[i.num]=1;
	for(auto i:a[(int)2e5])fa[i.num]=1;
	fu(i,0,2e5){
		int k=0;
		for(auto j:a[i]){
			while(k<a[i+1].size()&&a[i+1][k].r<j.l)++k;
			if(k<a[i+1].size()&&a[i+1][k].l<=j.r){
				int u=getfa(j.num),v=getfa(a[i+1][k].num);
				if(u!=v)fa[u]=v;
			}
			while(k+1<a[i+1].size()&&a[i+1][k+1].l<=j.r){
				++k;
				int u=getfa(j.num),v=getfa(a[i+1][k].num);
				if(u!=v)fa[u]=v;
			}
		}
	}
	ll ans=0;
	fo(i,1,tot)if(getfa(i)!=getfa(1))ans+=sz[i];
	cout<<ans<<'\n';
	return 0;
}

abc386_f Operate K

题目大意:给定串 \(A,B\),可以做至多 \(K(K\le 20)\) 次以下操作之一:

  • \(A\) 中插入一个字符。
  • 删除 \(A\) 的一个字符。
  • 替换 \(A\) 的一个字符。

问最终能否将 \(A\) 变成 \(B\)

解题思路:首先暴力地想,设\(f_{i,j}\) 表示 \(A\) 的前 \(i\) 个考虑完后,匹配到 \(B\) 的第 \(j\) 个字符最少做多少次操作。

注意到只有 \(i-K\le j\le i+K\)\(j\) 是有用的,所以我们设新的 DP 状态 \(f_{i,j}(-K\le j\le K)\) 表示 \(A\) 的前 \(i\) 个考虑完后,匹配到 \(B\) 的第 \(i+j\) 个字符最少做多少次操作。

那么转移就是:

  • 插入 \(B\) 的下一个字符:\(f_{i,j}+1\to f_{i,j+1}\)
  • 删除 \(A\) 的下一个字符:\(f_{i,j}+1\to f_{i+1,j-1}\)
  • 替换 \(A\) 的下一个字符为 \(B\) 的下一个字符:\(f_{i,j}+1\to f_{i+1,j}\)
  • \(A_{i+1}=B_{i+j+1}\) 则直接匹配:\(f_{i,j}\to f_{i+1,j}\)

转移过程中要求操作数始终不大于 \(K\) 即可。

时间复杂度 \(O(nK)\),其中 \(n\) 为字符串的长度。

AC 代码:

int K;
const int N=5e5+5;
char a[N],b[N];
int n,m;
int f[N][45];
void get(int &x,int y){
	if(y<x)x=y;
}
signed main(){
	cin>>K;
	scanf("%s%s",a+1,b+1);
	n=strlen(a+1),m=strlen(b+1);
	memset(f,0x3f,sizeof f);
	f[0][0+K]=0;
	fo(i,0,n){
		fo(j,-K,K){
			if(f[i][j+K]>K||i+j<0||i+j>m)continue;
			if(j+K-1>=0)get(f[i+1][j+K-1],f[i][j+K]+1);
			get(f[i][j+K+1],f[i][j+K]+1);
			get(f[i+1][j+K],f[i][j+K]+1);
			if(a[i+1]==b[i+j+1])get(f[i+1][j+K],f[i][j+K]);
		}
	}
	if(m-n<=K&&m-n>=-K&&f[n][m-n+K]<=K)cout<<"Yes\n";
	else cout<<"No\n";
	return 0;
}
posted @ 2025-01-05 21:17  dengchengyu  阅读(18)  评论(0)    收藏  举报