AtCoder Grand Contest 004F: Naromi 题解

Model Transformation

如果不考虑到这个模型的转换,这道题可能很难入手:

题目要求如果相邻两个的颜色相同,就把它们都换成另一种颜色

我们先考虑树的情况,树是一个二分图,所以可以先黑白染色

我们考虑这样一个模型:
现在有一些池塘,池塘的连接结构和题中的图是一样的,刚开始有些池塘有水,有些池塘没有水(对应二分图)每次操作可以把一个池塘的水流到相邻的池塘里,要求最后原来有水的池塘没水,原来没水的池塘有水,求最少操作数

我们会发现这两个问题是等价的,最后池塘是否有水的状态全部改变,相当于每个池塘都被包含在了奇数次操作中,换在原题中就是所有的点的颜色都发生了改变,我们又发现相邻的两个池塘一个有水一个没水,就对应了原题中两个点颜色相同


NetWork Flow?

这个转换后的问题看上去简单了一些,我们先考虑树的情况

首先水在流动的过程中总量不变,所以如果刚开始有水的池塘数量不是 N 2 的话答案肯定是-1

这样一个在池塘之间移水的问题让我想起了之前做过的一道TopCoder的题TheSquareDivOne,因为不同的池塘里的水是没有分别的,所以如果两个池塘的水撞到一起,可以由某一股水代替另一股水,换句话说,事实上水是可以穿过一个有水的池塘的

所以我们可以建立一个网络流的模型:超级源点向所有刚开始有水的池塘连流量是1费用是0的边,所有没水的池塘向超级汇点连流量是1费用是0的边,二分图左边和右边两两连流量是INF费用是它们在树上的距离的边,然后跑费用流

考虑到这样建图边数太多,可以按照原树的结构建图,这样是等价的

这个版本我写了一遍,TLE了

这个故事告诉我们不能什么时候都寄希望于网络流过1e5


Tree Case

这个网络流的思路是有可取之处的,我们至少明白了这个问题等价于一个匹配问题

我们考虑利用树的性质:如果某两个点可以在某棵子树内匹配掉,那么他们一定不会分别和子树外的点匹配

这样如果一棵子树的有水池塘数是X1,无水池塘数是X2,那么最后一定有且仅有 X 1 X 2 个点没有被匹配

我们考虑对每条边算它被走了多少次,按照上一行的算法如果它的子树中有X个点没有被匹配,那么这些点必须和子树外的点匹配,所以这条边会被走X次

必要性显然,充分性根据树的性质可得


Odd Cycle Case

n=e的时候是一个基环外向树
如果是偶环,则原图仍是二分图,如果是奇环就不是
考虑这两个问题的思路是相似的:我们都希望算出环中一条边的贡献后拆掉这条边,这样就能套用树的算法了

我们先考虑奇环

我们考虑奇环里连接两个都有水(都没水)的池塘的那条边,看看走这条边意味着什么

这两条边的状态一起改变,相当于向两个池塘同时加水/减水

所以刚开始只要有水池塘数和无水池塘数奇偶性相同就有解

我们可以利用这两个池塘补水来使得两种池塘数量相同,然后把这条边拆掉,就能套用书上的解法了


Even Cycle Case

我们发现虽然这个图还是一个二分图但比奇环更不好做了

我们考虑拆対原图dfs之后的那条返祖边

我们要先考虑这条边走几次

我们渐渐发现这条边走的次数只会对环上的其他边产生影响,而且这个影响貌似是与这条边走的次数有关的一个式子,设这条边是 u v ,走了 x 次(反过来走算负的),环上的其他边是 u u 1 u 2 . . . u n v ,我们可以发现因为在u那里有x个未匹配的点通过 u v 走掉了,所以 u , u 1 , u 2 . . . u n 的所有点的未匹配个数都会相应的减少 x ,设我们对原图dfs之后 u , u 1 , u 2 . . . u n 的子树对应的未匹配个数分别是 c , c 1 , c 2 . . . c n ,则 u v x 次会使得环上的其它边的答案变成这样(这里再算上走 u v 这条边的代价 x )

f ( x ) =∣ x + c x + c 1 x + c 2 x + . . . + c n x

我们要求的是 min x = N N f ( x )

有的人的解法是:绝对值函数是一个下凸函数,所以若干个绝对值函数的和也是下凸函数,所以可以三分求最小值

不过事实上初中我们就学过若干个绝对值函数的和的最小值,考虑在数轴上的几何意义,最小值应该取在最中间的一段,所以把 0 , c , c 1 , c 2 . . . c n 排个序和取中间的一个就是 x 的值


Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=1e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

int n,e;
vector<int> v[100048];
bool visited[100048];
int depth[100048],cnt[100048][2],fa[100048];
Pair cycle;

inline int myabs(int x) {return x>=0?x:-x;}

namespace tree
{
    inline void dfs(int cur,int father)
    {
        int i,y;
        cnt[cur][0]=cnt[cur][1]=0;cnt[cur][depth[cur]]=1;
        for (i=0;i<int(v[cur].size());i++)
        {
            y=v[cur][i];
            if (y!=father)
            {
                depth[y]=depth[cur]^1;dfs(y,cur);
                cnt[cur][0]+=cnt[y][0];cnt[cur][1]+=cnt[y][1];
            }
        }
    }
    inline int solve()
    {
        int i,x,y;
        depth[1]=1;dfs(1,-1);
        if (cnt[1][0]!=cnt[1][1]) return -1;
        int res=0;
        for (i=2;i<=n;i++) res+=myabs(cnt[i][0]-cnt[i][1]);
        return res;
    }
}

inline void dfs(int cur,int father)
{
    int i,y;visited[cur]=true;fa[cur]=father;
    cnt[cur][0]=cnt[cur][1]=0;cnt[cur][depth[cur]]=1;
    for (i=0;i<int(v[cur].size());i++)
    {
        y=v[cur][i];
        if (y!=father)
        {
            if (visited[y]) {if (cycle.x==0) cycle=mp(y,cur);continue;}
            depth[y]=depth[cur]^1;dfs(y,cur);
            cnt[cur][0]+=cnt[y][0];cnt[cur][1]+=cnt[y][1];
        }
    }
}

namespace even
{
    vector<int> cir;bool incir[100048];
    inline int solve()
    {
        if (cnt[1][0]!=cnt[1][1]) return -1;
        int cur,i,ans=0;cir.clear();
        memset(incir,false,sizeof(incir));
        for (cur=cycle.y;cur!=cycle.x;cur=fa[cur]) incir[cur]=true,cir.pb(cnt[cur][1]-cnt[cur][0]);
        cir.pb(0);
        sort(cir.begin(),cir.end());int nm=int(cir.size());
        int coef=cir[nm/2-1];
        for (i=0;i<int(cir.size());i++) ans+=myabs(cir[i]-coef);
        for (i=2;i<=n;i++)
            if (!incir[i]) ans+=myabs(cnt[i][1]-cnt[i][0]);
        return ans;
    }
}

namespace odd
{
    int w[100048],sz[100048],sum[100048],ans;
    inline void dfs(int cur,int father)
    {
        sz[cur]=(depth[cur]==0);sum[cur]=w[cur];int i,y;
        for (i=0;i<int(v[cur].size());i++)
        {
            y=v[cur][i];
            if (y==father || (y==cycle.x && cur==cycle.y) || (y==cycle.y && cur==cycle.x)) continue;
            dfs(y,cur);sz[cur]+=sz[y];sum[cur]+=sum[y];
        }
        if (cur!=1) ans+=myabs(sum[cur]-sz[cur]);
    }
    inline int solve()
    {
        if (cnt[1][0]%2!=cnt[1][1]%2) return -1;
        ans=myabs(cnt[1][0]-cnt[1][1])/2;int delta=(cnt[1][0]-cnt[1][1])/2,i;
        for (i=1;i<=n;i++) w[i]=depth[i];
        w[cycle.x]+=delta;w[cycle.y]+=delta;
        dfs(1,-1);
        return ans;
    }
}

int main ()
{
    int i,x,y;
    n=getint();e=getint();
    for (i=1;i<=e;i++) x=getint(),y=getint(),v[x].pb(y),v[y].pb(x);
    if (e==n-1) {printf("%d\n",tree::solve());return 0;}
    depth[1]=1;cycle=mp(0,0);dfs(1,-1);
    if (depth[cycle.x]!=depth[cycle.y]) printf("%d\n",even::solve()); else printf("%d\n",odd::solve());
    return 0;
}

猜你喜欢

转载自blog.csdn.net/IcePrincess_1968/article/details/80050215