[CF1525F]Goblins And Gnomes

题解

我们可以先考虑如何求出一个图的最小路径覆盖。
我们可以将一个有向图的每个点拆成入点与出点两个点,在入点与出点两点之间连边。
于是,我们就将我们的图变成了一个二分图,由于二分图的 最 小 路 径 覆 盖 = 点 数 − 最 大 匹 配 最小路径覆盖=点数-最大匹配 =,所以我们可以通过匈牙利求出原图的最小路径覆盖。
此时我们的删边操作就变成了从二分图中删去一个出点或入点。

很明显,我们删去一个点最多只会让最小路径覆盖增加 1 1 1,也就是让最大匹配减 1 1 1,因为它最多只会对一对匹配造成影响。
而对于任意一个最大匹配不为 0 0 0 的二分图,一定存在一个节点,使得我们将这个节点删去后,最大匹配刚好减 1 1 1 ,即所有的最大匹配存在一个公共的匹配点。
如果不存在一个公共点,那么必定有点可以向一条增广路径的末尾连边,使得最大匹配增大。
所以所有的最大匹配必然存在这样的一个公共点。

那么假设我们最开始的最大匹配为 x x x,那么我们就要去找到 k + 1 − x k+1-x k+1x 次这样的公共点,并将他删去。
找公共点可以直接一个一个点地枚举,如果删去这个点后二分图的最大匹配减小,那么这个点就是所有最大匹配的公共点。
在找到这些公共点后,我们只需要将它们在合适的时候删去即可。

至于删点的顺序,我们可以通过 dp,来实现,即 d p i , j dp_{i,j} dpi,j 表示哥布林袭击了 i i i 次,已经删去了 j j j 个点。
我们只对 i < x + j i<x+j i<x+j d p i , j dp_{i,j} dpi,j 进行 dp 即可,直接枚举每次多删几个点暴力 dp 就行了。
最后我们只需要对 d p k , k + 1 − x dp_{k,k+1-x} dpk,k+1x 按原路返回输出答案即可。

时间复杂度 O ( n 5 ) O\left(n^5\right) O(n5),如果不一个一个点删去后找,而是一次找完的话是可以做到 O ( n 3 ) O\left(n^3\right) O(n3),不过 O ( n 5 ) O\left(n^5\right) O(n5) 已经可以过了

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 2505
#define lowbit(x) (x&-x)
#define reg register
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const LL INF=0x7f7f7f7f7f7f7f7f;
const LL jzm=2333;
const int zero=2500;
const int mo=998244353;
const double Pi=acos(-1.0);
typedef pair<int,int> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int n,m,k,sta[55],stak,pre[55][55];bool link[55][55];LL dp[55][55];
vector<int> ans[55];
struct ming{int x,y;}s[55];
class Graph{
	private:
		int head[55],tot,p[55];bool mp[55][55],vis[55];
	public:
		void Remove(int u){
			if(u<=n)for(int i=1;i<=n;i++)mp[u][i]=0;
			else for(int i=1;i<=n;i++)mp[i][u-n]=0;
		}
		void Insert(int u){
			if(u<=n)for(int i=1;i<=n;i++)mp[u][i]=link[u][i];
			else for(int i=1;i<=n;i++)mp[i][u-n]=link[i][u-n];
		}
		void Copy(int u){
			if(u<=n)for(int i=1;i<=n;i++)link[u][i]=mp[u][i];
			else for(int i=1;i<=n;i++)link[i][u-n]=mp[i][u-n];
		}
		bool misaka(int x){
			vis[x]=1;
			for(int i=1;i<=n;i++){
				if(!mp[x][i])continue;
				if(p[i]==-1||(!vis[p[i]]&&misaka(p[i])))
					return (p[i]=x,1);
			}
			return 0;
		}
		int sakura(){
			for(int i=1;i<=n;i++)p[i]=-1;int res=0;
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++)vis[j]=0;
				if(misaka(i))res++;
			}
			return res;
		}
}G;
signed main(){
	read(n);read(m);read(k);
	for(int i=1,u,v;i<=m;i++)read(u),read(v),link[u][v]=1;
	for(int i=1;i<=n;i++)G.Insert(i),G.Insert(i+n);
	for(int i=1;i<=k;i++)read(s[i].x),read(s[i].y);int now=n-G.sakura(),tmp=now;
	if(now>k){printf("%d\n",k);while(k--)printf("0 ");puts("");return 0;}
	printf("%d\n",k+k+1-now);
	while(now<=k){
		for(int i=1;i<=2*n;i++){
			G.Remove(i);
			if(n-G.sakura()>now){now++,G.Copy(i);sta[++stak]=i;break;}
			else G.Insert(i);
		}
	}
	memset(dp,-0x7f,sizeof(dp));dp[0][0]=0;
	for(int i=0;i<k;i++){
		for(int j=0;j<=i-tmp;j++)dp[i][j]=-INF;
		for(int j=max(0,i+1-tmp);j<=k+1-tmp;j++){
			int num=s[i+1].x;
			for(int d=0;d<=k+1-j-tmp;d++){
				if(dp[i+1][j+d]<dp[i][j]+1ll*num)
					dp[i+1][j+d]=dp[i][j]+1ll*num,pre[i+1][j+d]=j;
				num=max(num-s[i+1].y,0);
			}
		}
	}
	now=k+1-tmp;
	for(int i=k;i>0;i--){
		for(int j=pre[i][now]+1;j<=now;j++)
			if(sta[j]>n)ans[i].push_back(-(sta[j]-n));
			else ans[i].push_back(sta[j]);
		now=pre[i][now];
	}
	for(int i=1;i<=k;i++){
		for(int j=0;j<ans[i].size();j++)printf("%d ",ans[i][j]);
		printf("0 ");
	}
	puts("");
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值