BZOJ 1854 游戏(二分图匹配或并查集)
此题的二分图匹配做法很容易想,就是把属性当做s集,武器当做t集,如果该武器拥有该武器则连一条边。
那么答案就是求该二分图的最大前i个匹配。将匈牙利算法改一改,当前找不到增广路就break。
但是过这个题需要常数优化,不能每次都fillchar一遍used数组。可以用队列将使用的used点加入,然后需要初始化的时候弹出即可。
# include <cstdio> # include <cstring> # include <cstdlib> # include <iostream> # include <vector> # include <queue> # include <stack> # include <map> # include <set> # include <cmath> # include <algorithm> using namespace std; # define lowbit(x) ((x)&(-x)) # define pi 3.1415926535 # define eps 1e-9 # define MOD 1000000009 # define INF 1000000000 # define mem(a,b) memset(a,b,sizeof(a)) # define FOR(i,a,n) for(int i=a; i<=n; ++i) # define FO(i,a,n) for(int i=a; i<n; ++i) # define bug puts("H"); # define lch p<<1,l,mid # define rch p<<1|1,mid+1,r # define mp make_pair # define pb push_back typedef pair<int,int> PII; typedef vector<int> VI; # pragma comment(linker, "/STACK:1024000000,1024000000") typedef long long LL; int Scan() { int res=0, flag=0; char ch; if((ch=getchar())=='-') flag=1; else if(ch>='0'&&ch<='9') res=ch-'0'; while((ch=getchar())>='0'&&ch<='9') res=res*10+(ch-'0'); return flag?-res:res; } void Out(int a) { if(a<0) {putchar('-'); a=-a;} if(a>=10) Out(a/10); putchar(a%10+'0'); } const int N=1000005; //Code begin... struct Edge{int to, next;}edge[N<<1]; int head[N], tot, linker[N], uN; bool used[N]; queue<int>Q; void init(){tot=0; mem(head,-1);} void addedge(int u, int v){ edge[tot].to=v; edge[tot].next=head[u]; head[u]=tot++; } bool dfs(int u){ for (int i=head[u]; i!=-1; i=edge[i].next) { int v=edge[i].to; if (!used[v]) { used[v]=true; Q.push(v); if (linker[v]==-1||dfs(linker[v])){linker[v]=u; return true;} } } return false; } int hungary(){ int res=0; mem(linker,-1); FOR(u,1,uN) { while (!Q.empty()) { int v=Q.front(); Q.pop(); used[v]=0; } if (dfs(u)) res++; else break; } return res; } int main () { int n, u, v; n=Scan(); init(); FOR(i,1,n) { u=Scan(); v=Scan(); uN=max(uN,u); uN=max(uN,v); addedge(u,i); addedge(v,i); } printf("%d\n",hungary()); return 0; }
这题的并查集做法还是神啊。。。
把一个有a,b两种属性的武器看成点a,b之间的无向边
对于一个联通块,假如不含环(就是一棵树),那么必定可以满足其中任意的p-1个点。
对于一个联通块,假如含环,那么必定全部的p个点都能满足。
那么合并并查集的时候可以利用一个vis来维护这个性质,把权值看成点,把武器看成边。如果每次加入的边是合并两个联通块
就把权值小的联通块并到权值大的联通块,然后给权值小的vis=true.如果不是,就把该联通块的顶点的vis=true
这样就可以保证,如果一个大小为N联通块1.=N-1条边构成,最大点的vis=false,其他为true
2.≥N条边构成,所有点的vis=true。
那么对于当前最小的i使得vis[i]=false.那么i-1一定是可以满足的最大数量。
简单证明:从小到大来看,对于没有关联边的点,显然vis[i]=false. 关联边的点,我们每次都可以选择一个联通块上的点,且这些点占用的边可以保持不同。
# include <cstdio> # include <cstring> # include <cstdlib> # include <iostream> # include <vector> # include <queue> # include <stack> # include <map> # include <set> # include <cmath> # include <algorithm> using namespace std; # define lowbit(x) ((x)&(-x)) # define pi 3.1415926535 # define eps 1e-9 # define MOD 1000000009 # define INF 1000000000 # define mem(a,b) memset(a,b,sizeof(a)) # define FOR(i,a,n) for(int i=a; i<=n; ++i) # define FO(i,a,n) for(int i=a; i<n; ++i) # define bug puts("H"); # define lch p<<1,l,mid # define rch p<<1|1,mid+1,r # define mp make_pair # define pb push_back typedef pair<int,int> PII; typedef vector<int> VI; # pragma comment(linker, "/STACK:1024000000,1024000000") typedef long long LL; int Scan() { int res=0, flag=0; char ch; if((ch=getchar())=='-') flag=1; else if(ch>='0'&&ch<='9') res=ch-'0'; while((ch=getchar())>='0'&&ch<='9') res=res*10+(ch-'0'); return flag?-res:res; } void Out(int a) { if(a<0) {putchar('-'); a=-a;} if(a>=10) Out(a/10); putchar(a%10+'0'); } const int N=1000005; //Code begin... int fa[N]; bool vis[N]; int find(int x){ return x==fa[x]?x:fa[x]=find(fa[x]); } void un(int x, int y){ if (x<y) swap(x,y); vis[y]=1; fa[y]=x; } int main () { int n, u, v, p, q; n=Scan(); FOR(i,1,n+1) fa[i]=i; FOR(i,1,n) { u=Scan(); v=Scan(); p=find(u), q=find(v); if (p==q) vis[p]=1; else un(p,q); } FOR(i,1,n+1) if (!vis[i]) {printf("%d\n",i-1); break;} return 0; }