题面见校内OJ4694
题解:
首先来看标程的不用费用流的没什么拓展性的做法。
考虑对于每一条狗,我们建立如下的七个点
然后对于每个能够打到这条狗的炮台,向这条狗的五个蓝点全部连边。
然后跑一般图最大匹配,考虑这条狗被攻击到若干次之后内部能够形成的最大匹配。
显然就是这个时候造成的伤害值就是内部能够形成的最大匹配。
800个点跑一般图最大匹配,随便写一个带花树或者Tutte矩阵就行了(由于图十分稠密,带花树跑得很快)。
接下来让我们忘记上面那个没有拓展性的做法。
仔细考虑一下这个问题,显然是一个冲突贪心,虽然贪心代价不太好算,我们还是考虑利用费用流。
每条狗建立这样五个点:
然后源点向每个炮台连容量为1的边,炮台向能够打到的狗的5号点连容量为1的边。
现在把每条狗的五个点连向T的边按照 5 − 1 5-1 5−1边权降序赋值,然后跑最小费用最大流。
接着把所有与T直接相连的边扫一遍,算贡献。显然这样每条狗流满了的边是一个前缀。
流了几条边就代表扣了几点耐久度。
现在有一个问题,我们发现流满两个4不如流一个4之后再流一个三。
考虑到需要退流重流,发现是一个一般图最大匹配。考场上写了Tutte矩阵的做法就A了。
(费用流or直接建图)+(带花树orTutte矩阵求秩)四种写法我全部写了一遍。
由于两种方式建出来的图都十分稠密,所以带花树求最大匹配收敛贼快,比Tutte矩阵快得多。
代码(费用流+带花树):
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
using std::cerr;
using std::cout;
cs int N=1e4+7;
int n,m,S,T,tot;
struct edge{int to,cap,w,rev;};
std::vector<edge> G[N];
typedef std::vector<edge>::iterator iter;
iter cur[N];
inline void adde(int u,int v,int cap,int cost){
G[u].push_back((edge){v,cap,cost,(int)G[v].size()});
G[v].push_back((edge){u,0,-cost,(int)G[u].size()-1});
}
inline int find_T(int u){
for(iter e=G[u].begin();e!=G[u].end();++e)if(e->to==T)return e->cap;
return 0;
}
int tot_cost,tot_flow;
int dis[N],vis[N];
inline bool SPFA(){
std::queue<int> q;
memset(dis+1,0x3f,sizeof(int)*tot);dis[S]=0;q.push(S);
for(int re i=1;i<=tot;++i)cur[i]=G[i].begin();
while(!q.empty()){
int u=q.front();vis[u]=false;q.pop();
for(iter e=G[u].begin();e!=G[u].end();++e)
if(e->cap&&dis[e->to]>dis[u]+e->w){
dis[e->to]=dis[u]+e->w;
if(!vis[e->to])vis[e->to]=true,q.push(e->to);
}
}
return dis[T]<1e9;
}
int st[N],tp;
int dfs(int u,cs int flow){
if(u==T){
tot_cost+=flow*dis[T];
tot_flow+=flow;
return flow;
}
vis[u]=true;int ans=0;
for(iter &e=cur[u];e!=G[u].end();++e){
if(e->cap&&dis[e->to]==dis[u]+e->w&&!vis[e->to]){
int delta=dfs(e->to,std::min(flow-ans,e->cap));
if(delta){
e->cap-=delta;
G[e->to][e->rev].cap+=delta;
if((ans+=delta)==flow)break;
}
}
}
vis[u]=false;
return ans;
}
inline void Flow(){//最小费用最大流
tot_cost=tot_flow=0;
while(SPFA())dfs(S,0x3f3f3f3f);
}
std::vector<int> pre[N],bin[N];
namespace G_Match{
cs int N=1e2+1;
int m;
std::vector<int> G[N];
int con[N][N];
inline void adde(int u,int v){if(con[u][v])return ;
con[u][v]=con[v][u]=true;
G[u].push_back(v),G[v].push_back(u);
}
int match[N],q[N],hd,tl;
int fa[N],pre[N],tim,tic[N],typ[N];
inline int gf(int u){return u==fa[u]?u:(fa[u]=gf(fa[u]));}
inline int LCA(int u,int v){
for(++tim;;std::swap(u,v))if(u){
u=gf(u);
if(tic[u]==tim)return u;
else tic[u]=tim,u=pre[match[u]];
}
}
inline void shrink(int u,int v,int p){
while(gf(u)!=p){
pre[u]=v,v=match[u];
if(typ[v]==2)typ[v]=1,q[++tl]=v;
if(gf(u)==u)fa[u]=p;
if(gf(v)==v)fa[v]=p;
u=pre[v];
}
}
inline int aug(int s){
for(int re i=1;i<=m;++i)fa[i]=i;
memset(typ,0,sizeof(int)*(m+1));
memset(pre,0,sizeof(int)*(m+1));
typ[q[hd=tl=1]=s]=1;
while(hd<=tl){
int u=q[hd++];
for(int re v:G[u]){
if(gf(v)==gf(u)||typ[v]==2)continue;
else if(!typ[v]){
typ[v]=2,pre[v]=u;
if(!match[v]){
for(int re t=v,las,tp;t;t=las){
las=match[tp=pre[t]];
match[t]=tp,match[tp]=t;
}
return true;
}
typ[match[v]]=1,q[++tl]=match[v];
}
else if(typ[v]==1){
int p=LCA(u,v);
shrink(u,v,p);
shrink(v,u,p);
}
}
}
return false;
}
inline int calc(){
int ans=0;
for(int re i=1;i<=m;++i)ans+=(!match[i]&&aug(i));
return ans;
}
inline void clear(){
for(int re i=1;i<=m;++i)G[i].clear(),memset(con[i],0,sizeof(int)*(m+1));
memset(match,0,sizeof(int)*(m+1));
}
inline int solve(){
int siz=0;
for(int re i=1;i<=n;++i)siz=std::max(siz,(int)bin[i].size());
if(siz<=1)return 0;
clear();
for(int re i=1;i<=n;++i){
for(int re j=0;j<bin[i].size();++j)
for(int re k=j+1;k<bin[i].size();++k)adde(bin[i][j],bin[i][k]);
}
return calc();
}
}
int idc[N],idd[N][6];
inline void solve(){
scanf("%d%d",&n,&m);tot=0;
for(int re i=1;i<=n;++i)idc[i]=++tot,bin[i].clear();
for(int re i=1;i<=m;++i)
for(int re j=1;j<=5;++j)idd[i][j]=++tot;
S=++tot,T=++tot;
for(int re i=1;i<=tot;++i)G[i].clear();
for(int re i=1;i<=n;++i)adde(S,idc[i],1,0);
for(int re i=1;i<=m;++i){
pre[i].clear();
adde(idd[i][5],idd[i][4],4,0);
adde(idd[i][4],idd[i][3],3,0);
adde(idd[i][3],idd[i][2],2,0);
adde(idd[i][2],idd[i][1],1,0);
adde(idd[i][5],T,1,-105);
adde(idd[i][4],T,1,-99);
adde(idd[i][3],T,1,-99);
adde(idd[i][2],T,1,-89);
adde(idd[i][1],T,1,-84);
}
for(int i=1;i<=n;++i){
int k,t;scanf("%d",&k);std::vector<int> v;
while(k--){scanf("%d",&t);v.push_back(t);pre[t].push_back(i);}
std::sort(v.begin(),v.end());
for(int re j=0;j<(int)v.size();++j)adde(idc[i],idd[v[j]][5],1,0);
}
Flow();assert(tot_flow==n);
int res=m*3;
for(iter e=G[T].begin();e!=G[T].end();++e){
if(e->cap&&(e->w==84||e->w==89||e->w==99)){
--res;
}
}int &cnt=G_Match::m;cnt=0;
for(int i=1;i<=m;++i){
int t1=find_T(idd[i][4]),t2=find_T(idd[i][3]);
if(!t1&&!t2)++res;
if(t1^t2){++cnt;
for(int re j=0;j<(int)pre[i].size();++j)bin[pre[i][j]].push_back(cnt);
}
}
res+=G_Match::solve();
std::cout<<res<<"\n";
}
signed main(){
#ifdef zxyoi
freopen("fortress.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
freopen("fortress.in","r",stdin);freopen("fortress.out","w",stdout);
#endif
#endif
int T;scanf("%d",&T);srand(time(0));
while(T--)solve();
return 0;
}
代码(直接建图跑一般图最大匹配,我写的Tutte矩阵求秩,慢得吓人):
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
using std::cerr;
using std::cout;
namespace G_Match{
cs int N=8e2+7;
int a[N][N],b[N][N],m;
cs int mod=1e9+7;
inline int add(int a,int b){a+=b-mod;return a+(a>>31&mod);}
inline int dec(int a,int b){a-=b;return a+(a>>31&mod);}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int power(int a,int b,int res=1){
for(;b;b>>=1,a=mul(a,a))(b&1)&&(res=mul(res,a));
return res;
}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline int calc(){//rank
for(int re i=1;i<=m;++i)
for(int re j=1;j<=m;++j){
if(i<j){
if(a[i][j])b[i][j]=mul(abs(rand()),abs(rand()));
else b[i][j]=0;
}
else if(i==j)b[i][j]=0;
else b[i][j]=dec(0,b[j][i]);
}
int ans=0;
for(int re i=1;i<=m;++i){
int p;for(p=i;p<=m;++p)if(b[p][i])break;
if(p>m)continue;++ans;
if(i!=p)for(int re j=i;j<=m;++j)std::swap(b[i][j],b[p][j]);
int inv=power(b[i][i],mod-2);
for(int re j=i;j<=m;++j)Mul(b[i][j],inv);
for(int re j=i+1,t;j<=m;++j)if(!!(t=b[j][i]))
for(int re k=i;k<=m;++k)Dec(b[j][k],mul(t,b[i][k]));
}
return ans>>1;
}
inline void adde(int u,int v){a[u][v]=a[v][u]=true;}
inline void clear(){
memset(a,0,sizeof a);
}
inline int solve(){return std::min(calc(),calc());}
}
using G_Match::adde;
cs int N=1e2+7;
int &tot=G_Match::m;
int n,m;
int idc[N],idd[N][10];
inline void solve(){
scanf("%d%d",&n,&m),tot=0;
for(int re i=1;i<=n;++i)idc[i]=++tot;
for(int re i=1;i<=m;++i)
for(int re j=0;j<7;++j)idd[i][j]=++tot;
G_Match::clear();
for(int re i=1;i<=m;++i){
adde(idd[i][0],idd[i][1]);
adde(idd[i][1],idd[i][2]);
adde(idd[i][2],idd[i][3]);
adde(idd[i][3],idd[i][4]);
adde(idd[i][4],idd[i][0]);
adde(idd[i][5],idd[i][0]);
adde(idd[i][5],idd[i][1]);
adde(idd[i][5],idd[i][2]);
adde(idd[i][5],idd[i][3]);
adde(idd[i][5],idd[i][4]);
adde(idd[i][6],idd[i][0]);
adde(idd[i][6],idd[i][1]);
adde(idd[i][6],idd[i][2]);
adde(idd[i][6],idd[i][3]);
adde(idd[i][6],idd[i][4]);
}
for(int re i=1;i<=n;++i){
int k,v;scanf("%d",&k);
while(k--){
scanf("%d",&v);
adde(idc[i],idd[v][0]);
adde(idc[i],idd[v][1]);
adde(idc[i],idd[v][2]);
adde(idc[i],idd[v][3]);
adde(idc[i],idd[v][4]);
}
}
std::cout<<G_Match::calc()-n<<"\n";
}
signed main(){
#ifdef zxyoi
freopen("fortress.in","r",stdin);
#endif
int T;scanf("%d",&T);srand(time(0));
while(T--)solve();
return 0;
}