题解 边数
没有部分分,而且找不到加边策略,连暴力都不会打……
然而首先有一个结论:最优情况下每条新加的边都是从节点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;
}