Atcoder agc025E

解决Atcoder agc025E构造问题的思路分享,探讨如何将两条路径合并成一条以优化答案。通过分析度为1的节点,推导出答案可达∑i=1N−1min(2,ci)的上界,并给出O(NM)时间复杂度的解决方案。" 108101210,9277897,解密算法:二战伤员名单加密挑战,"['信息安全', '加密技术', '算法', '字符串操作']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自闭选手又做不出构造题了,关键是没想到把两条路径合并成一条。
先猜测结论,答案可以达到∑i=1N−1min⁡(2,ci)\sum_{i=1}^{N-1} \min(2,c_i)i=1N1min(2,ci)的上界,其中cic_ici是经过第iii条边的路径数目。
考虑每次缩掉一个度为111的节点uuu的构造。
设跟uuu直接相连的边为iii。若ci=0c_i=0ci=0可以直接缩,ci=1c_i=1ci=1的话那条边怎么定向没有区别,可以将它的端点缩到跟uuu相连的点,ci≥2c_i\geq 2ci2的话,我们取两条经过iii的路径(u,x)(u,x)(u,x)(u,y)(u,y)(u,y),我们可以可以将它们替换为一条路径(x,y)(x,y)(x,y),若最终定向为xxx->yyy,我们令xxx->uuuuuu->yyy,否则令yyy->uuuuuu->xxx。这样做的话,对于(x,y)(x,y)(x,y)路径上的边覆盖次数不变,对于(u,x)∩(u,y)(u,x)\cap(u,y)(u,x)(u,y)上的边一定达到上界,因此显然不会改变答案。
时间复杂度O(NM)\mathcal O(NM)O(NM)

#include <bits/stdc++.h>
#define FR first
#define SE second

using namespace std;

typedef pair<int,int> pr;

vector <int> e[2005];
int d[2005];

int fa[2005],dep[2005];

void dfs1(int x) {
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x]) {
    	int u=e[x][i];
    	fa[u]=x;dep[u]=dep[x]+1;
    	dfs1(u);
	}
}

int lca(int x,int y) {
  if (dep[x]<dep[y]) swap(x,y);
  while (dep[x]>dep[y]) x=fa[x];
  while (x!=y) {
  	x=fa[x];
  	y=fa[y];
  }
  return x;
}

int sum[2005];

void dfs2(int x) {
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x]) {
    	int u=e[x][i];
    	dfs2(u);
    	sum[x]+=sum[u];
	} 
}

int p[2005];
bool col[2005],vis[2005];

pr num[2005],fir[2005];

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  for(int i=1;i<n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e[x].push_back(y);
  	e[y].push_back(x);
  	d[x]++;d[y]++;
  }
  dfs1(1);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	fir[i]=num[i]=pr(x,y);
  	sum[x]++;sum[y]++;
  	sum[lca(x,y)]-=2;
  }
  dfs2(1);
  int ans=0;
  for(int i=2;i<=n;i++) ans+=min(sum[i],2);
  printf("%d\n",ans);
  for(int i=2;i<=n;i++) {
  	int u=0;
  	for(int j=2;j<=n;j++)
  	  if (d[j]==1&&!vis[j]) {
  	  	  u=j;
  	  	  break;
		}
	vis[u]=1;
	int v=0;
	for(int j=0;j<e[u].size();j++)
	  if (!vis[e[u][j]]) {
	  	v=e[u][j];
	  	break;
	  }
	d[v]--;
	int id1=0,id2=0;
	for(int j=1;j<=m;j++)
	  if ((num[j].FR==u)^(num[j].SE==u)) {
	  	if (!id1) id1=j;
	  	else if (!id2) id2=j;
	  }
	if (!id1&&!id2) continue;
	if (!id2) {
		if (num[id1].FR==u) num[id1].FR=v;
		else num[id1].SE=v; 
		continue;
	}
	if (num[id1].FR==u) {
		if (num[id2].FR==u) {
			num[id1].FR=num[id2].SE;
			col[id2]^=1;
		}
		else num[id1].FR=num[id2].FR;
	}
	else {
		if (num[id2].SE==u) {
			num[id1].SE=num[id2].FR;
			col[id2]^=1;
		}
		else num[id1].SE=num[id2].SE;
	}
	num[id2].FR=num[id2].SE;
	p[id2]=id1;
	for(int j=1;j<=m;j++)
	  if ((num[j].FR==u)^(num[j].SE==u)) {
	  	if (num[j].FR==u) num[j].FR=v;
	  	else num[j].SE=v;
	  }
  }
  for(int i=1;i<=m;i++)
    if (p[i]) col[i]^=col[p[i]];
  for(int i=1;i<=m;i++) {
  	if (col[i]) swap(fir[i].FR,fir[i].SE);
  	printf("%d %d\n",fir[i].FR,fir[i].SE);
  }
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值