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("");
}