2019雅礼集训 D4T1 w [费用流]

题目描述:

样例:

input1:
4 1 2
1 2 3 4
1 2
1 3
3 4
1 2
2 3
1 4
2
1 3
4 1
1
2 3

output1:
9

input2:
5 1 1
3 99 99 100 2
1 2
1 3
3 4
3 5
1 3
1 2
2 4
2 5
2
1 2
3 1
2
1 2
2 1

output2:
198

数据范围:



先放个原题地址:CF1061E

毒瘤出题人搬原题差评

毒瘤出题人题目翻译出锅差评

这题看到如此不伦不类的问法,似乎不是dp、贪心等算法,而且数据范围有如此之小,于是自然(个鬼)想到了费用流。

虽然如此,我由于做的题太少,赛场上仍然想不出如何建模。(其实赛场上就没想到费用流)

注意到题目保证根节点一定会有限制,我们记有限制的点为关键点,不关键的点的贡献可以算在关键点上。

可以想到,每个关键点\(p\)实际能够操控的点\(x\)满足:\(p\)是\(x\)的第一个关键祖先,记为\(id_x=p\)

我们再记每个关键点\(p\)能操控的\(x\)中能被激活的点的总数为\(sum_p\),即\(p\)的子树内可选节点减去其他关键点的可选节点。(怎么越说越复杂……)

那么:

一、源点向每个红树的关键点\(p\)连一条流量为\(sum_p\),费用为0的边。

二、每个蓝树的关键点\(p\)向汇点连一条流量为\(sum_p\),费用为0的边。

三、\(1\)至\(n\)的每个点\(x\)红树和蓝树分别有一个关键祖先\(p\),\(p‘\)。由\(p\)向\(p‘\)连一条流量为1,费用为\(value_x\)的边。

第三点的边走了就代表选了这个点,否则就是不选这个点。

最后跑一边最大费用最大流即可。

判无解的方法:一棵树内有关键点的可选节点为负数(即自相矛盾,许多人被出题人坑死在这个点上),最大流不能跑满或两棵树总共选的节点数不一样。

代码:

#include<bits/stdc++.h>
namespace my_std{
    using namespace std;
    #define mod 998244353
    #define pii pair<int,int>
    #define fir first
    #define sec second
    #define MP make_pair
    #define rep(i,x,y) for (int i=(x);i<=(y);i++)
    #define drep(i,x,y) for (int i=(x);i>=(y);i--)
    #define go(x) for (int i=head[x];i;i=edge[i].nxt)
    #define sz 500500
    typedef long long ll;
    template<typename T>
    inline void read(T& t)
    {
        t=0;char f=0,ch=getchar();
        double d=0.1;
        while(ch>‘9‘||ch<‘0‘) f|=(ch==‘-‘),ch=getchar();
        while(ch<=‘9‘&&ch>=‘0‘) t=t*10+ch-48,ch=getchar();
        if(ch==‘.‘)
        {
            ch=getchar();
            while(ch<=‘9‘&&ch>=‘0‘) t+=d*(ch^48),d*=0.1,ch=getchar();
        }
        t=(f?-t:t);
    }
    template<typename T,typename... Args>
    inline void read(T& t,Args&... args){read(t); read(args...);}
    void file()
    {
        #ifndef ONLINE_JUDGE
        freopen("a.txt","r",stdin);
        #endif
    }
    inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std;

int n;

namespace tree
{

    int rt0,rt1;
    struct hh{int t,nxt;}edge[sz];
    int head[sz],ecnt;
    void make_edge(int f,int t)
    {
        edge[++ecnt]=(hh){t,head[f]};
        head[f]=ecnt;
        edge[++ecnt]=(hh){f,head[t]};
        head[t]=ecnt;
    }

    int w[sz],a[sz],id[sz],sum[sz],T;
    void dfs(int x,int fa)
    {
        if (a[x])
        {
            sum[id[x]=++T]=a[x];
            if (fa) sum[id[fa]]-=a[x];
            if (fa&&sum[id[fa]]<0) puts("-1"),exit(0);
        }
        else id[x]=id[fa];
        #define v edge[i].t
        go(x) if (v!=fa) dfs(v,x);
        #undef v
    }

    void init()
    {
        read(rt0,rt1);rt1+=n;
        int m,x,y;
        rep(i,1,n) read(w[i]);
        rep(i,1,n-1) read(x,y),make_edge(x,y);
        rep(i,1,n-1) read(x,y),make_edge(x+n,y+n);
        read(m);
        rep(i,1,m) read(x,y),a[x]=y;
        read(m);
        rep(i,1,m) read(x,y),a[x+n]=y;
        dfs(rt0,0);dfs(rt1,0);
    }

}
using tree::a;using tree::w;using tree::id;using tree::sum;

struct hh{int t,w,dis,nxt;}edge[sz];
int head[sz],ecnt=1;
void make_edge(int f,int t,int w,int dis)
{
    edge[++ecnt]=(hh){t,w,dis,head[f]};
    head[f]=ecnt;
    edge[++ecnt]=(hh){f,0,-dis,head[t]};
    head[t]=ecnt;
}

int pre[sz],dis[sz],flow[sz];
bool in[sz];
int S,T;
bool SPFA()
{
    queue<int>q;
    memset(dis,~0x3f,sizeof(dis));
    q.push(S);
    dis[S]=0;in[S]=1;
    flow[S]=INT_MAX;
    while (!q.empty())
    {
        int x=q.front();q.pop();in[x]=0;
        #define v edge[i].t
        go(x) if (dis[v]<dis[x]+edge[i].dis&&edge[i].w>0)
        {
            dis[v]=dis[x]+edge[i].dis;
            pre[v]=i;
            flow[v]=min(flow[x],edge[i].w);
            if (!in[v]) q.push(v);
            in[v]=1;
        }
        #undef v
    }
    return dis[T]!=dis[0];
}
int L,R;
void build()
{
    S=n*2+1;T=S+1;
    rep(i,1,n) if (a[i]) {make_edge(S,id[i],sum[id[i]],0);L+=sum[id[i]];}
    rep(i,n+1,n<<1) if (a[i]) {make_edge(id[i],T,sum[id[i]],0);R+=sum[id[i]];}
    rep(i,1,n) make_edge(id[i],id[i+n],1,w[i]);
}
int ans,mxflow;
void update(){mxflow+=flow[T];ans+=flow[T]*dis[T];for (int x=T,y;(y=pre[x],x!=S);x=edge[y^1].t) edge[y].w-=flow[T],edge[y^1].w+=flow[T];}
void MCF(){build();while (SPFA()) update();}
int main()
{
    file();
    read(n);
    tree::init();
    MCF();
    printf("%d",L==R&&L==mxflow?ans:-1);
}

原文地址:https://www.cnblogs.com/p-b-p-b/p/10246386.html

时间: 2024-11-08 19:58:09

2019雅礼集训 D4T1 w [费用流]的相关文章

2019雅礼集训 D7T2 subsequence [DP,平衡树]

题目描述: 样例: input1: 5 -2 -8 0 5 -3 output1: 5 10 13 2 -13 input2: 6 -10 20 -30 40 -50 60 output2: 60 160 280 390 400 210 数据范围与约定: 考虑DP:令\(dp(i,j)\)表示前\(i\)个点选\(j\)个,能得到的最大价值. 得到转移方程:\(dp(i,j)=\max\{dp(i-1,j),dp(i-1,j-1)+a_i\cdot j\}\) 这个方程很明显是\(n^2\)的.

2019雅礼集训 D10T1 数字重排 [DP]

题目描述: 样例: input: 5 5 5 10 17 23 output: 3 数据范围与约定: 简单DP,不做解释,直接搬题解. 标程: #include<bits/stdc++.h> using namespace std; const int N=1e5+5; int n,a[N],m,s; bitset<N>f,g; int main(){ freopen("sort.in","r",stdin); freopen("s

【2019雅礼集训】【第一类斯特林数】【NTT&amp;多项式】permutation

目录 题意 输入格式 输出格式 思路: 代码 题意 找有多少个长度为n的排列,使得从左往右数,有a个元素比之前的所有数字都大,从右往左数,有b个元素比之后的所有数字都大. n<=2*10^5,a,b<=n 输入格式 输入三个整数n,a,b. 输出格式 输出一个整数,表示答案. 思路: 这道题是真的神啊... 首先,根据官方题解的思路,首先有一个n^2的DP: 定义dp[i][j]表示一个长度为i的排列,从前往后数一共有j个数字大于所有排在它前面的数字. 首先有转移式: \[dp[i][j]=d

2019雅礼集训 D7T1 inverse [概率/期望,DP]

题目描述: 样例: input1: 3 1 1 2 3 output1: 833333340 input2: 5 10 2 4 1 3 5 output2: 62258360 数据范围与约定: 概率/期望的常用套路:将许许多多个元素单独考虑,以达到解决问题的目的. 这里发现不可能整个序列一起考虑,于是枚举任意两个位置,计算出k次翻转之后左边大于右边的概率,再加起来就好了. 于是我们有了一个非常暴力的DP: 令\(dp(i,j,k)?\) 表示k次翻转之后i位置大于j位置的概率.为了方便我们强行令

2019雅礼集训 D10T2 硬币翻转 [交互题]

题目描述: coin.h: #include<string> void guess(); int ask(std::string coin); grader.cpp: #include "coin.h" #include <iostream> #include <assert.h> using namespace std; namespace U { using u64 = unsigned long long; using i64 = long l

#6030. 【雅礼集训 2017 Day1】矩阵

#6030. 「雅礼集训 2017 Day1」矩阵 题目描述 有一个 n×n  的矩阵,每个位置 (i,j) 如果是 . 表示为白色,如果是 # 表示为黑色. 初始时,每个位置可以是黑色或白色的,(i,j)  位置的值会作为 ai,j 给你. 现在有一种操作,选择两个整数 i,j∈[1,n],记 (i,1),(i,2),…,(i,n) (i, 1), (i, 2)的颜色为 C1,C2,…Cn ??,将 (1,j),(2,j),…,(n,j)  的颜色赋为 C1,C2,…,Cn ??. 你的任务是

2017雅礼集训 Day2

今日得分:60+100+25 = 185,修改后60+100+100 今日题解: T1:有nlogn对不合法的数对,这些数对在DFS序上的支配范围画在平面上是一个或两个矩形,求矩形面积并即可 T2:递推,考虑新增加的一行一列的状态 1.与前面的一行共同完全占据了两行两列,为避免重复我们规定必须选第i列,那么行有C(i,2)种选法,列有i-1种:f[i-2]*C(i,2)*(i-1) 2.没有与前面一行共同占据两行两列,那么相当于把冲突的其中一个位置换到最后一列去:f[i-1]*C(i,2)*2

hdu6611 2019 多校3K 原始对偶费用流(正权化Dijkstra找增广路)

http://acm.hdu.edu.cn/showproblem.php?pid=6611 题很简单,一眼拆点费用流 就是点边拉满之后复杂度有点恐怖,比赛的时候没敢莽费用流 但是最后居然真的是费用流,不过必须上原始对偶且用Dijkstra增广 具体细节很多,大概就是指,原本的Dijktra无法处理负权图,我们就去想办法对所有的费用进行统一扩大,变成正权最短路. #include<bits/stdc++.h> #define fi first #define se second #define

「6月雅礼集训 2017 Day10」quote

[题目大意] 一个合法的引号序列是空串:如果引号序列合法,那么在两边加上同一个引号也合法:或是把两个合法的引号序列拼起来也是合法的. 求长度为$n$,字符集大小为$k$的合法引号序列的个数.多组数据. $1 \leq T \leq 10^5, 1 \leq n \leq 10^7, 1\leq K \leq 10^9$ [题解] 显然引号序列可以看做括号序列,于是我们有了一个$O(n^2)$的dp了. 设$f_{i,j}$表示到第$i$个位置,前面有$j$个左引号没有匹配,的方案数 每次,要么有