CodeForces - 863F Almost Permutation(费用流)

F. Almost Permutation

time limit per test

3 seconds

memory limit per test

512 megabytes

input

standard input

output

standard output

Recently Ivan noticed an array a while debugging his code. Now Ivan can't remember this array, but the bug he was trying to fix didn't go away, so Ivan thinks that the data from this array might help him to reproduce the bug.

Ivan clearly remembers that there were n elements in the array, and each element was not less than 1 and not greater than n. Also he remembers q facts about the array. There are two types of facts that Ivan remembers:

  • li ri vi — for each x such that li ≤ x ≤ ri ax ≥ vi;
  • li ri vi — for each x such that li ≤ x ≤ ri ax ≤ vi.

Also Ivan thinks that this array was a permutation, but he is not so sure about it. He wants to restore some array that corresponds to the qfacts that he remembers and is very similar to permutation. Formally, Ivan has denoted the cost of array as follows:

, where cnt(i) is the number of occurences of i in the array.

Help Ivan to determine minimum possible cost of the array that corresponds to the facts!

Input

The first line contains two integer numbers n and q (1 ≤ n ≤ 50, 0 ≤ q ≤ 100).

Then q lines follow, each representing a fact about the array. i-th line contains the numbers tiliri and vi for i-th fact (1 ≤ ti ≤ 2, 1 ≤ li ≤ ri ≤ n, 1 ≤ vi ≤ nti denotes the type of the fact).

Output

If the facts are controversial and there is no array that corresponds to them, print -1. Otherwise, print minimum possible cost of the array.

Examples

input

Copy

3 0

output

Copy

3

input

Copy

3 1
1 1 3 2

output

Copy

5

input

Copy

3 2
1 1 3 2
2 1 3 2

output

Copy

9

input

Copy

3 2
1 1 3 2
2 1 3 1

output

Copy

-1

 

题意:每个位置只能填特定的数字,数字有费用,费用为次数的平方,求一个费用最少的填法。

 

解题思路:费用流裸题。某个位置可以填的数字向这个位置连边。S向所有位置连边,流量为INF,费用为1.所有位置向T连边,流量为1,费用为0.

网上处理费用平方的思路是新建50个节点。这里提供另外一个思路,在费用流跑dfs的时候,动态的修改边的费用即可。

 

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
const int MAXN = 7005;
const int INF = 0x3f3f3f3f;

struct edge{
    int u,v,cap,cost,next;
}e[MAXN*2];


int head[MAXN],edge_num=0;

void insert_edge(int u,int v,int cap,int cost){
    e[edge_num].u=u;
    e[edge_num].v=v;
    e[edge_num].cap=cap;
    e[edge_num].cost=cost;
    e[edge_num].next=head[u];
    head[u]=edge_num++;

    e[edge_num].u=v;
    e[edge_num].v=u;
    e[edge_num].cap=0;
    e[edge_num].cost=-cost;
    e[edge_num].next=head[v];
    head[v]=edge_num++;
}

int dis[MAXN];
int pre[MAXN*2];
bool vis[MAXN];
bool spfa(int s,int t){
    memset(dis,0x3f,sizeof(dis));
    memset(pre,-1,sizeof(pre));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    vis[s]=1;
    queue<int> que;
    que.push(s);
    while(!que.empty()){
        int tp=que.front();
        que.pop();
        vis[tp]=0;
        for(int i=head[tp];~i;i=e[i].next){
            int v=e[i].v;
            int cost=e[i].cost;
            if(e[i].cap&&dis[v]>dis[tp]+cost){
                dis[v]=dis[tp]+cost;
                pre[v]=i;
                if(!vis[v]){
                    vis[v]=1;
                    que.push(v);
                }
            }
        }
    }
    if(dis[t]==INF)
        return false;
    return true;

}


int wm[5006];
bool im[MAXN];//记录需要修改的正向边
bool om[MAXN];//记录需要修改的反向边

pair<int,int> MCMF(int s,int t){
    int maxflow=0;
    int mincost=0;
    int minc;
    while(spfa(s,t)){
        minc=INF;
        int cost=0;
        for(int i=pre[t];~i;i=pre[e[i].u])
            minc=min(minc,e[i].cap);
        for(int i=pre[t];~i;i=pre[e[i].u]){
            e[i].cap-=minc;
            e[i^1].cap+=minc;
            cost+=minc*e[i].cost;//正常计算

            if(im[i]){
                mincost-=(wm[e[i].cost]-1)*(wm[e[i].cost]-1);//把上次的去掉
                e[i].cost=(wm[e[i].cost]+1)*(wm[e[i].cost]+1);//修改边的费用
            }

            if(om[i]){
                mincost+=(wm[-e[i].cost]-1)*(wm[-e[i].cost]-1);//反向边的要注意
                e[i].cost=-(wm[-e[i].cost]+1)*(wm[-e[i].cost]+1);
            }

        }

        mincost+=cost;
        maxflow+=minc;
    }
    return {mincost,maxflow};
}

int minn[MAXN];
int maxx[MAXN];
int cnt=0;
int in[MAXN];
int out[MAXN];

int main()
{
    for(int j=1;j<5000;j++)
        for(int i=1;i<=50;i++)
            if(i*i==j)
                wm[j]=i;
    memset(head,-1,sizeof(head));
    edge_num=0;
    int N,Q;
    scanf("%d%d",&N,&Q);
    for(int i=1;i<=N;i++){
        minn[i]=1;
        maxx[i]=N;
    }

    int op,l,r,v;
    for(int i=0;i<Q;i++){
        scanf("%d%d%d%d",&op,&l,&r,&v);
        if(op==1){
            for(int x=l;x<=r;x++)
                minn[x]=max(minn[x],v);
        }
        else{
            for(int x=l;x<=r;x++)
                maxx[x]=min(maxx[x],v);
        }
    }
    int S=++cnt;
    int T=++cnt;
    for(int i=1;i<=N;i++){
        in[i]=++cnt;
        out[i]=++cnt;
    }
    for(int i=1;i<=N;i++){
        insert_edge(S,in[i],INF,1);
        om[edge_num-1]=1;
        im[edge_num-2]=1;
        insert_edge(out[i],T,1,0);
        if(minn[i]>maxx[i]){
            cout<<-1<<endl;
            return 0;
        }
        for(int x=minn[i];x<=maxx[i];x++){
            insert_edge(in[x],out[i],1,0);
        }
    }
    cout<<MCMF(S,T).first<<endl;
    return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值