• [省选集训2022] 模拟赛5


    A

    题目描述

    给定 \(n\) 个数 \(a_i\),其中 \(k\)\(a_i\) 是奇数,再给定一个 \(n\times n\) 的矩阵 \(\{c_{i,j}\}\),都保证是非负整数,你可以做下列操作任意次:

    • \(a_i\)\(1\)\(a_j\)\(1\),花费 \(c_{i,j}\) 的代价,\(i=j\) 是被允许的。

    问把所有 \(a_i\) 都变成 \(0\) 的最小花费什么,无解输出 \(-1\)

    \(1\leq n\leq 50,k\leq8,c_{i,j}\leq10^5,a_i\leq 100\)

    解法

    一般图最大匹配是不在我能力范围内的,但是鉴于本题还是匹配问题,我们尽量把问题化归到二分图上去,并且我们合理揣测出题人的意图,把奇数点作为关键点

    首先考虑没有奇数点的情况,如果 \((i,j)\) 之间操作了一次就把他们之间连一条无向边,最后得到一个可能有重边和自环的图,并且满足每个点的度数都是偶数。那么这个图是个欧拉图,也就是我们能找到一种边定向方案,使得所有点的入度等于出度。那么可以得到关键结论:存在最优方案,使得对应的图可以找到一种边定向方案使得所有点入度等于出度

    那么我们拆点,把点 \(i\) 拆成左部点和右部点,分别代表了一个点的入度和出度。那么两部之间可以直接连费用为 \(c_{i,j}\) 的完全二分图,源点向 \(x_{in}\) 连容量 \(\frac{a_i}{2}\) 的边,\(x_{out}\) 向汇点连容量为 \(\frac{a_i}{2}\) 的边,对这个图跑费用流即可。

    那么如果有奇数点怎么办?如果我们在图上通过加边的方式把奇数点补成偶数点,那么可以类似地得到结论:存在最优方案,奇数点的入度和出度相差 \(1\)

    因为奇数点的数量很少,所以我们花 \(O(2^k)\) 来枚举奇数点的度数,然后跑费用流即可。

    总结

    二元操作可以通过建边转化到图上去思考。

    往已知的问题上化归,这时一定要坚信题目具有某种特殊性。

    #include <cstdio>
    #include <iostream>
    #include <queue>
    using namespace std;
    const int M = 105;
    const int inf = 0x3f3f3f3f;
    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,ans,a[M],b[M],c[M][M],p[M];
    int S,T,tot,f[M],dis[M],pre[M],lst[M],flow[M];
    struct edge{int v,f,c,next;}e[M*M];
    void add(int u,int v,int F,int c)
    {
    	e[++tot]=edge{v,F,c,f[u]},f[u]=tot;
    	e[++tot]=edge{u,0,-c,f[v]},f[v]=tot;
    }
    int bfs()
    {
    	queue<int> q;
    	for(int i=0;i<=T;i++) dis[i]=inf,flow[i]=0;
    	q.push(S);dis[S]=0;flow[S]=inf;
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		for(int i=f[u];i;i=e[i].next)
    		{
    			int v=e[i].v,c=e[i].c;
    			if(e[i].f>0 && dis[v]>dis[u]+c)
    			{
    				dis[v]=dis[u]+c;
    				pre[v]=u;lst[v]=i;
    				flow[v]=min(flow[u],e[i].f);
    				q.push(v);
    			}
    		}
    	}
    	return flow[T]>0;
    }
    int work()
    {
    	int res=0;
    	tot=1;S=0;T=2*n+1;
    	for(int i=0;i<=T;i++) f[i]=0;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			add(i,j+n,inf,c[i][j]);
    	for(int i=1;i<=n;i++)
    	{
    		add(S,i,(a[i]+b[i])/2,0);
    		add(i+n,T,(a[i]-b[i])/2,0);
    	}
    	while(bfs())
    	{
    		res+=flow[T]*dis[T];int u=T;
    		while(u)
    		{
    			e[lst[u]].f-=flow[T];
    			e[lst[u]^1].f+=flow[T];
    			u=pre[u];
    		}
    	}
    	return res;
    }
    signed main()
    {
    	freopen("match.in","r",stdin);
    	freopen("match.out","w",stdout);
    	n=read();ans=1e9;
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		if(a[i]%2) p[m++]=i;
    	}
    	if(m%2) {puts("-1");return 0;}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			c[i][j]=read();
    	for(int i=0;i<(1<<m);i++)
    	{
    		int cnt=0;
    		for(int j=0;j<m;j++)
    		{
    			if(i>>j&1) b[p[j]]=1;
    			else b[p[j]]=-1,cnt++;
    		}
    		if(cnt!=m/2) continue;
    		ans=min(ans,work());
    	}
    	printf("%d\n",ans);
    }
    

    C

    题目描述

    \(n\) 个点的树,边有边权,每个点有一个范围为 \(r_i\) 的炸弹,如果引爆它会连锁引爆和它距离 \(\leq r_i\) 的所有炸弹,问初始最少引爆多少炸弹可以使得所有炸弹都被引爆。

    \(n\leq 3\cdot 10^5\),所有权值 \(\leq 10^9\)

    解法

    显然这题是 炸弹 搬到了树上,所以我们可以沿用那题的做法。

    每个点向距离 \(\leq r_i\) 的点连边,边表示引爆关系,然后对这个图跑 \(\tt tarjan\),发现引爆度数为 \(0\) 的点是必要的,因为没人可以覆盖它们;同时也是充分的,因为引爆它们所有点都会被覆盖,所以缩点后度数为 \(0\) 的点的个数就是答案。

    暴力跑是 \(O(n^2)\) 就算剪枝了也会被卡,考虑在序列上我们是用线段树优化建图,那么搬到树上可以考虑点分治优化建图,当然中心思想还是通过虚点减少连边。

    考虑连边是一对点的路径关系,所以我们在分治中心 \(u\) 取出分治子树内的所有点,然后按两个关键字排序:\(A\) 是深度 \(dep[u]\)\(B\) 是向自己子树外的覆盖能力 \(a[u]-dep[u]\)

    那么 \(u\)\(v\) 连有向边当且仅当 \(a[u]-dep[u]\geq dep[v]\),注意子树内部的连边不用考虑,因为就算连出来了不合法的边也是没有影响的(到中心再回到子树覆盖能力白白减小)

    排序之后就可以双指针了,\(B\) 数组的每个元素都建一个虚点,这个虚点需要完成连接 \(A\) 前缀的功能,所以它要连上一个虚点,再连向新增的 \(A\) 中的元素即可。具体实现中有一些小细节。

    建完图之后就可以无脑跑 \(\tt tarjan\) 了,时间复杂度 \(O(n\log^2n)\),瓶颈是排序。

    总结

    一定要对路径这个东西有敏锐的感觉,当式子中涉及到路径就可以考虑掏出点分治了。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <stack> 
    using namespace std;
    const int M = 300005;
    const int N = M*50;
    #define pb push_back
    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,sz,rt,tot,ans,f[M],a[M],vis[M],siz[M],mx[M];
    int m1,m2,Ind,cnt,low[N],dfn[N],col[N],d[N],in[N];
    vector<int> g[N];stack<int> s;
    struct edge{int v,c,next;}e[M<<1];
    struct node
    {
    	int u,c;
    	bool operator < (const node &b) const
    		{return c<b.c;}
    }pa[M],pb[M];
    void find(int u,int fa)
    {
    	siz[u]=1;mx[u]=0;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa || vis[v]) continue;
    		find(v,u);
    		siz[u]+=siz[v];
    		mx[u]=max(mx[u],siz[v]);
    	}
    	mx[u]=max(mx[u],sz-siz[u]);
    	if(mx[u]<mx[rt]) rt=u;
    }
    void dfs(int u,int fa,int d)
    {
    	pa[++m1]=node{u,d};
    	if(a[u]>=d) pb[++m2]=node{u,a[u]-d};
    	if(d>1e9) return ;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa || vis[v]) continue;
    		dfs(v,u,d+e[i].c);
    	}
    }
    void solve(int u)
    {
    	vis[u]=1;m1=m2=0;dfs(u,0,0);
    	sort(pa+1,pa+1+m1);
    	sort(pb+1,pb+1+m2);
    	for(int i=1,j=1,lst=0;i<=m2;i++)
    	{
    		int x=lst;
    		while(j<=m1 && pa[j].c<=pb[i].c)
    		{
    			if(x==lst) x=++n;
    			g[x].pb(pa[j].u),j++;
    		}
    		if(x!=lst && lst) g[x].pb(lst);
    		if(x) g[pb[i].u].pb(x);lst=x;
    	}
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(vis[v]) continue;
    		rt=0;sz=siz[v];
    		find(v,0);solve(rt);
    	}
    }
    void tarjan(int u)
    {
    	dfn[u]=low[u]=++Ind;
    	s.push(u);in[u]=1;
    	for(int v:g[u])
    	{
    		if(!dfn[v])
    		{
    			tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(in[v])
    			low[u]=min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u])
    	{
    		int v=0;cnt++;
    		do
    		{
    			v=s.top();s.pop();
    			col[v]=cnt;in[v]=0;
    		}while(v!=u);
    	}
    }
    signed main()
    {
    	freopen("infect.in","r",stdin);
    	freopen("infect.out","w",stdout);
    	n=read();
    	for(int i=1;i<=n;i++)
    		a[i]=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read(),c=read();
    		e[++tot]=edge{v,c,f[u]},f[u]=tot;
    		e[++tot]=edge{u,c,f[v]},f[v]=tot;
    	}
    	mx[0]=sz=n;find(1,0);solve(rt);
    	for(int i=1;i<=n;i++)
    		if(!dfn[i]) tarjan(i);
    	for(int u=1;u<=n;u++) for(int v:g[u])
    		if(col[u]!=col[v]) d[col[v]]++;
    	for(int i=1;i<=cnt;i++) ans+=!d[i];
    	printf("%d\n",ans);
    }
    
  • 相关阅读:
    Linux如何对文件内容中的关键字进行查找
    Gitlab如何进行备份恢复与迁移?
    Centos7上传文件和下载文件命令
    Linux下如何查看系统启动时间和运行时间
    您应该知道的35个绝对重要的Linux命令
    rabbitMq可靠消息投递之交换机备份
    rabbitMq可靠性投递之配置(消息至交换机,至队列不通的回调)
    springcloud超时重试机制的先后顺序
    mysql 8.0 1405的坑
    linux安装mysql8.0
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15881238.html
Copyright © 2020-2023  润新知