Atcoder Grand Contest 025

025E Walking on a Tree

题目描述

点此看题

解法

\(c_i\) 表示边 \(i\) 被路径覆盖的次数,考虑答案的上界是 \(\sum\min(2,c_i)\)

从叶子开始构造,考虑叶子 \(u\) 和它的父亲 \(v\):如果 \(c_{(u,v)}=0\),那么不需要覆盖;如果 \(c_{(u,v)}=1\),那么覆盖方向是没有关系的;下面考虑 \(c_{(u,v)}\geq 2\) 的情况。

任取两条路径 \([u,a],[u,b]\)(需要先调整成左端点是 \(u\)),那么使得 \(u\) 父边带来的限制就是两条路径方向相反,然后我们可以把这两条路径等效成 \([a,b]\),考虑那些没有被正反覆盖的边,它们的 \(c\) 都不会边,而被正反覆盖的边它们的 \(c\) 虽然变了但也无关紧要,所以可以归纳下去。

直接模拟删叶子即可,时间复杂度 \(O(nm)\)

总结

构造答案,常见的思路是使用归纳法,往往需要等效法的结合,使用等效法是注意要先观察限制,然后思考解决这个限制需要问题中哪些结构的变化,这些变化能不能等效到子问题?

#include <cstdio>
#include <bitset>
#include <vector>
#include <iostream>
using namespace std;
const int M = 2005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,fa[M],a[M],b[M],c[M],d[M],x[M],y[M],rev[M],f[M];
vector<int> g[M];bitset<M> z[M];int ans,tp,s[M],dep[M];
void dfs(int u,int p)
{
	dep[u]=dep[p]+1;fa[u]=p;
	for(int v:g[u]) if(v^p) dfs(v,u);
}
void walk(int u,int v,int id)
{
	while(u!=v)
	{
		if(dep[u]>dep[v]) z[id][u]=1,c[u]++,u=fa[u];
		else z[id][v]=1,c[v]++,v=fa[v];
	}
}
void del(int o)
{
	for(int i=1,lst=0,fl=0;i<=m;i++) if(z[i][o])
	{
		if(y[i]==o) rev[i]^=1,swap(x[i],y[i]);
		z[i][o]=0;x[i]=fa[o];
		if(!lst || fl) {lst=i;continue;}
		z[lst]=z[i]^z[lst];rev[i]^=rev[lst]^1;
		z[i].reset();f[i]=lst;x[lst]=y[i];lst=0;fl=1;
	}
}
void get(int u)
{
	rev[u]^=rev[f[u]];
	for(int i=1;i<=m;i++)
		if(f[i]==u) get(i);
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		g[u].push_back(v);
		g[v].push_back(u);
		d[u]++;d[v]++;
	}
	dfs(1,0);
	for(int i=1;i<=m;i++)
	{
		a[i]=x[i]=read();
		b[i]=y[i]=read();
		walk(x[i],y[i],i);
	}
	for(int i=2;i<=n;i++)
		if(d[i]==1) s[++tp]=i;
	for(int i=1;i<n;i++)
	{
		int u=s[tp--],v=fa[u];
		del(u);
		if((--d[v])==1 && v!=1) s[++tp]=v;
	}
	for(int i=2;i<=n;i++) ans+=min(2,c[i]);
	for(int i=1;i<=m;i++) if(!f[i]) get(i);
	printf("%d\n",ans);
	for(int i=1;i<=m;i++)
	{
		if(rev[i]) swap(a[i],b[i]);
		printf("%d %d\n",a[i],b[i]);
	}
}

025F Addition and Andition

题目描述

点此看题

解法

考虑拆分法,我们考虑初始的每对 \((1,1)\),如果不考虑其它数字的情况下,它们是会一直向前移动的。如果 \((1,1)\) 的前面有 \((1,0)/(0,1)\) 之类的数对,就会阻拦 \((1,1)\) 的移动,例子:

start -> 01001   01010   01100  crash -> 10000  end
         00001   00010   00100           01000

考虑从高位到低位来处理 \((1,1)\) 的移动,我们可以暴力把它往前移动至第一个有 \(1\) 的位置,然后考虑进位,如果进位之后还是 \((1,1)\) 可以继续移动,否则移动停止。

那么暴力做时间复杂度是否合理呢?我们每次 \((1,1)\) 和第一个有 \(1\) 的位置碰撞,都会造成至少一个 \(1\) 的减少,而 \(1\) 的个数永不增加,所以我们用栈维护第一个有 \(1\) 的位置,时间复杂度 \(O(n)\)

#include <cstdio>
#include <iostream>
using namespace std; 
const int M = 2000005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,tp,x[M],y[M],a[M],tmp[M];char s[M],t[M];
signed main()
{
	n=read();m=read();k=read();
	scanf("%s%s",s+1,t+1);
	for(int i=1;i<=n;i++) x[i]=s[n-i+1]-'0';
	for(int i=1;i<=m;i++) y[i]=t[m-i+1]-'0';
	a[++tp]=max(n,m)+k+1;
	for(int i=max(n,m);i>=1;i--)
	{
		int cnt=0;
		for(int z=k,j=i;;)
		{
			while(tp && a[tp]<=j) tp--;
			int nxt=a[tp];tmp[++cnt]=j;
			if(x[j]==1 && y[j]==1 && z)//shift
			{
				x[j]=y[j]=0;
				if(z>=nxt-j) z-=nxt-j;
				else nxt=j+z,z=0;
				x[nxt]++;y[nxt]++;j=nxt;
			}
			else if(x[j]==2 || y[j]==2)//carry bit
			{
				x[j+1]+=x[j]>>1;x[j]&=1;
				y[j+1]+=y[j]>>1;y[j]&=1;j++;
			}
			else break;//end
		}
		for(int j=cnt;j>=1;j--)
			if(x[tmp[j]] || y[tmp[j]]) a[++tp]=tmp[j];
	}
	for(n+=k;!x[n];n--);for(m+=k;!y[m];m--);
	for(int i=n;i>=1;i--) printf("%d",x[i]);
	puts("");
	for(int i=m;i>=1;i--) printf("%d",y[i]);
	puts("");
}
posted @ 2022-03-20 23:02  C202044zxy  阅读(127)  评论(0编辑  收藏  举报