题解 边数

传送门

没有部分分,而且找不到加边策略,连暴力都不会打……

然而首先有一个结论:最优情况下每条新加的边都是从节点1连出去的
然后除1以外每个没有入度的点都需要连一条边
以及这是一棵内向基环树
于是树的部分连边方法就固定了,树形DP即可
现在的问题是给定若干个环,环上有一些点已经被覆盖,求用长度为 \(k-1\) 的线段将它们完全覆盖所需的最小线段数
环很不好处理,考虑断环为链并倍长
接下来可以枚举断点,\(O(\frac{n}{k})\) check,但会T掉
来自@yspm的优化:发现一个点最多只会被 \(k\) 种线段覆盖,而一旦环上有一条线段被确定了,最优策略下剩下的线段的放置方法可以贪心得到
于是我们找到一个当前未被覆盖的点,check能覆盖它的 \(k\) 种线段即可
细节较多,而且不会写拍
复杂度 \(O(k*\frac{n}{k})=O(n)\)

Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 500010
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, k;
int head[N], size, cnt[N], fa[N], ans, dp[N], sta[N<<1], top, nxt[N<<1];
bool ring[N], vis[N], del[N], vis2[N], cov[N];
struct edge{int to, next; bool back;}e[N<<1];
inline void add(int s, int t, bool typ) {e[++size].to=t; e[size].back=typ; e[size].next=head[s]; head[s]=size;}
inline int find(int p) {return fa[p]==p?p:fa[p]=find(fa[p]);}
int getring(int u) {
	vis[u]=1;
	for (int i=head[u],v; ~i; i=e[i].next) if (!e[i].back) {
		v = e[i].to;
		if (vis[v]) {ring[u]=1; return v;}
		int tem=getring(v);
		if (~tem) {ring[u]=1; return tem==u?-1:tem;}
	}
	return -1;
}
void dfs(int u) {
	for (int i=head[u],v; ~i; i=e[i].next) if (e[i].back) {
		v = e[i].to;
		if (ring[v]) continue;
		dfs(v);
		dp[u]=max(dp[u], dp[v]-1);
	}
	if (u==1) dp[u]=k;
	else if (!ring[u] && dp[u]==-1) ++ans, dp[u]=k-1;
}
void dfs2(int u) {
	vis2[u]=1; sta[++top]=u;
	for (int i=head[u],v; ~i; i=e[i].next) if (!e[i].back) {
		v = e[i].to;
		if (ring[v] && !vis2[v]) dfs2(v);
	}
}

signed main()
{
	freopen("d.in", "r", stdin);
	freopen("d.out", "w", stdout);

	memset(head, -1, sizeof(head));
	memset(dp, -1, sizeof(dp));
	n=read(); k=read();
	for (int i=1; i<=n; ++i) fa[i]=i;
	for (int i=1,u,v; i<=n; ++i) {
		u=read(); v=read();
		add(u, v, 0); add(v, u, 1); ++cnt[v];
		fa[find(u)]=find(v);
	}
	for (int i=1; i<=n; ++i) if (!del[find(i)]) {
		getring(i); del[find(i)]=1;
	}
	// cout<<"ring: "; for (int i=1; i<=n; ++i) cout<<ring[i]<<' '; cout<<endl;
	if (ring[1]) dp[1]=k;
	for (int i=1; i<=n; ++i) if (ring[i]) dfs(i);
	for (int i=1; i<=n; ++i) if (ring[i] && del[find(i)]) {
		top=0;
		dfs2(i);
		// cout<<"top: "<<top<<endl;
		for (int i=1; i<=top; ++i) sta[i+top]=sta[i], cov[i+top]=cov[i]=0;
		// cout<<"sta: "; for (int i=1; i<=top*2; ++i) cout<<sta[i]<<' '; cout<<endl;
		// cout<<"dp: "; for (int i=1; i<=top*2; ++i) cout<<dp[sta[i]]<<' '; cout<<endl;
		int lst=0;
		for (int i=1; i<=top; ++i) {
			if (lst>0 || ~dp[sta[i]]) cov[i]=1;
			--lst; lst=max(lst, dp[sta[i]]);
		}
		for (int i=1; i<=top; ++i) {
			if (lst>0 || ~dp[sta[i]]) cov[i]=1;
			--lst; lst=max(lst, dp[sta[i]]);
		}
		bool all_covered=1;
		for (int i=1; i<=top; ++i) {
			cov[i+top]=cov[i];
			if (!cov[i]) all_covered=0;
		}
		if (all_covered) {del[find(i)]=0; continue;}
		// cout<<"cov: "; for (int i=1; i<=top*2; ++i) cout<<cov[i]<<' '; cout<<endl;
		int pos1=top*2, pos2=INF;
		for (int i=1; i<=top*2; ++i) nxt[i]=INF;
		for (int i=top*2-(k-1)-1; i>0; --i,--pos1) {
			if (!cov[pos1]) pos2=pos1;
			nxt[i]=pos2;
		}
		// cout<<"nxt2: "; for (int i=1; i<=top*2; ++i) cout<<nxt[i]<<' '; cout<<endl;
		// cout<<"nxt2: "; for (int i=1; i<=top*2; ++i) cout<<(nxt[i]>top*2?INF:sta[nxt[i]])<<' '; cout<<endl;
		int dlt=INF, st=1, cnt=0;
		while (cov[st]) ++st;
		for (int i=st; i<=top&&cnt<=k; ++i) {
			int tem=1;
			for (int p=nxt[i]; p<i+top; p=nxt[p],++tem) ;
			dlt=min(dlt, tem);
			// cout<<"i: "<<i<<' '<<tem<<endl;
		}
		// cout<<"dlt: "<<dlt<<endl;
		ans+=dlt;
		del[find(i)]=0;
	}
	printf("%d\n", ans);
	
	return 0;
}
上一篇:【luogu P5787】graph / 二分图 /【模板】线段树分治(扩展域并查集)(线段树分治)


下一篇:洛谷 P1879 [USACO06NOV]Corn Fields G