Luogu P4249 [WC2007]剪刀石头布

Link
先把题意抽象一下:给定一个存在一部分为定向的边的竞赛图,最大化它的三元环个数。
我们知道竞赛图的三元环个数为\({n\choose 3}-\sum\limits_{i=1}^n{deg_i\choose 2}\)
对于一条未定向的边\((u,v)\),它会使\(u,v\)其中一个点的度数加一。
对于一个点而言,随着度数的增加,度数加一使得三元环减少的个数也是在增加的,也就是说这是一个凸函数。
那么直接费用流就行了,建图就是凸函数差分的那一套方法。

#include<queue>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
const int N=107,V=6007,E=50007,inf=1e9;
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
int s,t,tot=1,a[N][N],deg[N],head[V],ver[E],next[E],edge[E],cost[E],dis[V],flow[V],inq[V],id[V];std::queue<int>q;struct node{int u,v,id;}p[N*N];
void add(int u,int v,int f,int c)
{
    ver[++tot]=v,next[tot]=head[u],head[u]=tot,edge[tot]=f,cost[tot]=c;
    ver[++tot]=u,next[tot]=head[v],head[v]=tot,edge[tot]=0,cost[tot]=-c;
}
int spfa()
{
    memset(dis+1,0x3f,t<<2),q.push(s),inq[s]=1,flow[s]=inf,dis[s]=0,id[t]=-1;
    for(int i,u,v;!q.empty();)
	for(i=head[u=q.front()],q.pop(),inq[u]=0;i;i=next[i])
	    if(edge[i]&&dis[v=ver[i]]>dis[u]+cost[i])
		if(dis[v]=dis[u]+cost[i],id[v]=i,flow[v]=std::min(flow[u],edge[i]),!inq[v])
		    q.push(v),inq[v]=1;
    return ~id[t];
}
int EK()
{
    int mincost=0;
    for(int p;spfa();) for(mincost+=flow[t]*dis[t],p=t;p^s;p=ver[id[p]^1]) edge[id[p]]-=flow[t],edge[id[p]^1]+=flow[t];
    return mincost;
}
int main()
{
    int n=read(),ans=n*(n-1)*(n-2)/6,cnt=n;s=n*(n+1)/2+1,t=s+1;
    for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) a[i][j]=read();
    for(int i=1;i<n;++i) for(int j=i+1;j<=n;++j) if(!a[i][j]) ++deg[j]; else if(a[i][j]==1) ++deg[i]; else add(s,++cnt,1,0),add(cnt,j,1,0),add(cnt,i,1,0),p[cnt]={i,j,tot};
    for(int i=1;i<=n;++i) for(int j=deg[i];j<n;++j) add(i,t,1,j);
    for(int i=1;i<=n;++i) ans-=deg[i]*(deg[i]-1)/2;
    printf("%d\n",ans-EK());
    for(int i=n+1;i<=cnt;++i) a[p[i].v][p[i].u]=!(a[p[i].u][p[i].v]=edge[p[i].id]);
    for(int i=1;i<=n;++i,puts("")) for(int j=1;j<=n;++j) printf("%d ",a[i][j]);
}
posted @ 2020-04-08 08:19  Shiina_Mashiro  阅读(115)  评论(0编辑  收藏  举报