The 2019 ICPC Asia Shanghai Regional Contest

The 2019 ICPC Asia Shanghai Regional Contest

B.Prefix Code

题意: 判断一个前缀码数组是否合法

题解: 对于每一个字符串,只需要判断当前字符串是否为其他字符串的前缀或者其他字符串是否为它的前缀即可。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 1e5 + 10;
int n, m, T;
unordered_map<string, int> mp, mp2;

int main() {
    cin >> T;
    int kase = 1;
    while(T--) {
        mp.clear();
        mp2.clear();
        scanf("%d", &n);
        int flg = 1;
        for (int i = 0; i < n; ++i) {
            string s;
            cin >> s;
            string pre = "";
            for (int j = 0; j < s.size(); ++j) {
                pre += s[j];
                if (mp2[pre] ==  1) flg = 0;  // 如果其他字符串是他的前缀不可以
                mp[pre]++;
            }
            mp2[s] = 1;
            if (mp[s] >= 2) flg = 0;  // 如果这个字符串是其他字符串的前缀也不可以
        }
        printf("Case #%d: ", kase++);
        if (flg) puts("Yes");
        else puts("No");
    }
    return 0;
}

D.Spanning Tree Removal

题意: 给定一张完全图,每次删除掉一颗生成树,问最多能够删除多少颗生成树,大于删除生成树的边。

题解: 构造题,每次删边的时候就z字形删除,这样就能够删除n/2棵树。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 2e5 + 10;
int n, m, T, kase = 1;

void solve(int cur) {
   int step = 1;
   int last = cur;
   for (int i = 1; i <= n - 1; ++i) {
       int tmp;
       if (i & 1) tmp = (cur + step + n) % n;
       else tmp = (cur - step + n) % n, step ++;
       if (!tmp) tmp += n;
       cout << last << " " << tmp << endl;
       last = tmp;
   }
}

int main() {
   cin >> T;
   while(T--) {
       cin >> n;
       printf("Case #%d: %d\n", kase++, n / 2);
       int cur = 1;
       for (int i = 1; i <= n / 2; ++i) {
           solve(cur);
           cur --;
           if (!cur) cur = n;
       }
   }
   return 0;
}

E.Cave Escape

题意: 给定一个网格图,每次可以向4个方向走,当一个点第一次被走到时,当前这个点和走到这个点的权值的乘积可以累加到答案中。一个点可以被多次走到。问最多的答案值是多少。

题解: 从终点的角度思考,那么一个人走到终点必然是从终点的4个方向来的,那么为了答案最大,必然选择乘积最大的那个,当拥有了终点和终点相邻的某个点,那么必然从与这两个点相邻的权值最大的点来…不断重复这个过程,可以看出,这就是最大生成树的产生过程。那么就是求最大生成树即可。一开始看到这个题目,看到公式,以为是在暗示什么性质,想了半天,没搞懂,最后发现原来它就只是一个简单的递推公式。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 1e3 + 10;
int n, m, T;
int v[N][N], kase = 1;
int sx, sy, ex, ey, x[N * N], a, b, c, P, p[N * N];

struct Edge {
    int a, b, w;
    bool operator< (const Edge &W)const {
        return w > W.w;
    }
}edge[N * N * 4];

int mapping(int x, int y) {
    return (x - 1) * m + (y - 1);
}

int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

int main() {
    cin >> T;
    while(T--) {
        scanf("%d%d%d%d%d%d", &n, &m, &sx, &sy, &ex, &ey);
        scanf("%d%d%d%d%d%d", &x[1], &x[2], &a, &b, &c, &P);
        for (int i = 3; i <= n * m + 10; ++i) 
            x[i] = (a * x[i - 1] + b * x[i - 2] + c) % P;

        for (int i = 1; i <= n; ++i) 
            for (int j = 1; j <= m; ++j) 
                v[i][j] = x[(i - 1) * m + j];
            
        int tot = 0;
        for (int i = 1; i <= n; ++i) 
            for (int j = 1; j <= m; ++j) {
                if (j + 1 <= m) edge[tot++] = {mapping(i, j), mapping(i, j + 1), v[i][j] * v[i][j + 1]};
                if (i + 1 <= n) edge[tot++] = {mapping(i, j), mapping(i + 1, j), v[i][j] * v[i + 1][j]};
            }

        sort(edge, edge + tot);
        LL res = 0;
        for (int i = 0; i <= n * m + 10; ++i) p[i] = i;  // 并查集初始化
        for (int i = 0; i < tot ; ++i) {
            int a = edge[i].a, b = edge[i].b, w = edge[i].w;  // 边a, b, 权值为w
            a = find(a), b = find(b);  // 得到a的父节点和b的父节点
            if (a != b)  {
                res += (LL)w;  // 在的话放入集合内
                p[a] = b;
            }
        }
        printf("Case #%d: %lld\n", kase++, res);
    }
    return 0;
}

F.A Simple Problem On A Tree

题意: 给定一棵树,维护4个操作:

1 u v w:将u->v路径上的点全部赋值为w

2 u v w:将u->v路径上的点全部加上w

3 u v w:将u->v路径上的点全部乘上w

4 u v:询问u->v路径上的点的立方和

题解: 本题考的就是维护4个操作:区间赋值,区间加,区间乘,区间立方和。难点在于维护立方和,加乘操作就是先乘后加。先树剖,然后dfs序建线段树,然后处理即可。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5+5;
typedef long long LL;
const int p = 1e9 + 7;
int tot, num;
int n, m, r,t,cases=0;

int w[N], a[N], sum1[N * 4], sum2[N * 4], sum3[N * 4], lazy_mul[N * 4], lazy_add[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
//int h[N], e[N * 2], ne[N * 2], idx;   // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 size子树大小 top链头  fa父节点
vector<int> mp[N];

// 得到sz, fa, dep, wson数组
void dfs1(int u) {
    dep[u] = dep[fa[u]]+1; 
    sz[u] = 1; 
    for(int i = 0; i<mp[u].size(); i++) {
        int j=mp[u][i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
    }
}

// 得到dfn, top数组 
void dfs2(int u, int nowtop) {
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜索序重排权值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜索重儿子
    for(int i = 0; i<mp[u].size(); i++) {// 然后搜索轻儿子
        int y=mp[u][i]; 
        if(y ==fa[u]||y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void solve(int rt,int len,int a,int b){   //a为add b为mul
    // 修改乘法和加法标记
    lazy_mul[rt] = 1ll*lazy_mul[rt] * b % p;
    lazy_add[rt] = 1ll*lazy_add[rt] * b % p;
    lazy_add[rt] = ((lazy_add[rt] + a) % p + p) % p;

    // 先维护乘法标记:x^3=>(bx)^3, x^2=>(bx)^2
    if(b!=1){   //先乘后加
        sum1[rt] = 1ll*sum1[rt] * b % p;
        sum2[rt] = (1ll*sum2[rt] * b % p) * b % p;
        sum3[rt] = ((1ll*sum3[rt] * b % p) * b % p) * b % p;
    }

    // 再维护加法标记:(x+a)^3=x^3+3x^2*a+3xa^2+a^3, (x+a)^2=x^2+2xa+a^2
    if(a!=0){
        int a2 = 1ll*a * a % p, a3 = 1ll*a2 * a % p;
        sum3[rt] = ((sum3[rt] + (LL)len * a3 % p) + p) % p;
        sum3[rt] = ((sum3[rt] + 3ll * (LL)sum2[rt] % p * a % p) + p) % p;
        sum3[rt] = ((sum3[rt] + 3ll * (LL)sum1[rt] % p * a2 % p) + p) % p;
        sum2[rt] = ((sum2[rt] + 2ll * (LL)sum1[rt] % p * a % p) + p) % p;
        sum2[rt] = ((sum2[rt] + (LL)len * a2 % p) + p) % p;
        sum1[rt] = ((sum1[rt] + (LL)len * a % p) + p) % p;
    }
}

void pushup(int rt) {
    sum1[rt] = (sum1[rt << 1] + sum1[rt << 1 | 1]) % p;
    sum2[rt] = (sum2[rt << 1] + sum2[rt << 1 | 1]) % p;
    sum3[rt] = (sum3[rt << 1] + sum3[rt << 1 | 1]) % p;
}   

// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r) {
    lazy_add[rt] = 0;
    lazy_mul[rt] = 1;
    if(l==r) {
        int temp = w[l];
        sum1[rt] = temp;
        sum2[rt] = (1ll*sum1[rt] * sum1[rt]) % p;
        sum3[rt] = (1ll*sum1[rt] * sum2[rt]) % p;
        return ; 
    }
    int mid=(l + r)>>1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

// 下传
void pushdown(int rt, int l, int r) {
    int mid = (l + r) >> 1;
    solve(rt << 1, mid - l + 1, lazy_add[rt], lazy_mul[rt]);
    solve(rt << 1 | 1, r - mid, lazy_add[rt], lazy_mul[rt]);
    lazy_add[rt] = 0;
    lazy_mul[rt] = 1;
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要修改的左区间,R为需要修改的右区间
void modify(int rt, int l, int r, int L, int R, int a,int b) {
    if(L <= l && r <= R) {
        solve(rt, r - l + 1, a, b);
        return ; 
    } 
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    if(L <= mid) modify(rt << 1, l, mid, L, R, a,b); 
    if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, a,b); 
    pushup(rt);
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要查询的左区间,R为查询的右区间
int query(int rt, int l, int r, int L, int R) {
    if(L <= l && r <= R) {
        return sum3[rt]; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query(rt << 1, l, mid, L, R), ans %= p; 
    if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R), ans %= p;
    pushup(rt);
    return ans; 
}

// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree(int x, int y) {   
    //两点间的修改
    int ans = 0; 
    while(top[x] != top[y]) {// 把x点和y点整到一条重链上
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        ans %= p; 
        x = fa[top[x]];  // x每次跳一条链
    }
    if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans % p; 
}

// 树上修改 a为add ,b为mul
void path_modify_Tree(int x, int y, int a,int b) {
    //树上两点距离 
    while(top[x] != top[y]) { // 把x点和y点整到一条重链上
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x成为对应的头部深度更大的那个点
        modify(1, 1, n, dfn[top[x]], dfn[x], a,b); // 累加x的所有子树和
        x = fa[top[x]]; // x跳到原来x的头部的父节点
    }
    if(dep[x] > dep[y]) swap(x, y);  // 让x成为深度更浅的那个点
    modify(1, 1, n, dfn[x], dfn[y], a,b); 
}

int main() {
    scanf("%d", &t);
    while(t--){
        scanf("%d", &n);
        cases++;
        // 读入边,建树
        for(int i=1; i<=n; ++i) mp[i].clear(),wson[i]=0;
        //memset(h,  -1,  sizeof h);
        num = 0;
        for(int i=1, x, y; i<n; i++) {
            scanf("%d%d", &x, &y);
            mp[x].push_back(y);
            mp[y].push_back(x); 
        }

        for(int i=1; i<=n; i++) scanf("%d", &a[i]); // 读入每个点的权值
        // 两次dfs把树按照重链剖分
        dfs1(1);   // 得到sz, fa, dep, wson数组
        dfs2(1, 1);   // 得到dfn, top数组
        build(1, 1, n); 
        scanf("%d", &m);  
        printf("Case #%d:\n", cases);
        // m次询问
        for(int i=1, op, x, y, z; i<=m; i++) {
            scanf("%d", &op); 
            if(op == 1) {
                scanf("%d%d%d", &x, &y, &z); 
                path_modify_Tree(x, y, z,0); // 将树从 x到 y 结点最短路径上所有节点的值赋值为z
            }
            else if(op == 2) {
                scanf("%d%d%d", &x, &y, &z); 
                path_modify_Tree(x, y, z,1); // 将树从 x到 y 结点最短路径上所有节点的值加上z
            }
            else if(op == 3) {
                scanf("%d%d%d", &x, &y, &z); 
                path_modify_Tree(x, y, 0,z); // 将树从 x到 y 结点最短路径上所有节点的值乘上z
            }
            else {
                scanf("%d%d", &x,&y); 
                printf("%d\n", path_query_Tree(x,y)); 
            }
        }
    } 

    return 0;
}

G.Play the game SET

题意: 给定n张卡片,每张卡片有4个维度,每个维度有3个属性。卡片数目较少,只有38张。如果3张卡片的每个维度都相同或者都不同,那么这3张卡片就可以构成一个set集合。问所有的卡片可以构成多少个没有卡片重复的set集合。

题解: 对于每张卡片,4个维度,3个属性,那么可以使用一个长度为4的3进制数字表示。然后暴力枚举2张卡片作为set集合的一部分,计算出剩下一张卡片应该是什么样子,之后去set里面查询是否存在。这样就可以构造出所有的set集合。把所有的set集合作为一个点,然后暴力枚举任意2个set集合,如果2个set集合使用了同一张卡片,那么两个点连一条边,那么所有的不重复set集合数目就是求这张图的最大独立集,转而去求补图的最大团。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+5;
int card[40],n,m,cnt1;
unordered_map<string,int> mp;
int id[maxn],A[maxn][4];
int g[maxn][maxn];
int group[maxn], vis[maxn], cnt[maxn],t,ans;

void init() {
    mp["one"] = 10, mp["two"] = 11, mp["three"] = 12;
    mp["diamond"] = 20, mp["squiggle"] = 21, mp["oval"] = 22;
    mp["solid"] = 30, mp["striped"] = 31, mp["open"] = 32;
    mp["red"] = 40, mp["green"] = 41, mp["purple"] = 42;
}

int cal(int x,int y) {
    int ans=0;
    int k=1;
    for(int i=0;i<4;i++) {
        int a=x%3;
        int b=y%3;
        
        if(a==b) ans+=k*a;
        else ans += k * (3-a-b);
        k*=3;
        
        x/=3;
        y/=3;
    }
    return ans;
}

int check(int x,int y) {
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++) if(A[x][i]==A[y][j]) return 0;
    return 1;
}

void build() {
    for(int i=0;i<81;i++) id[i]=0;
    for(int i=1;i<=n;i++) id[card[i]]=i;
    cnt1=0;
    for(int i=1;i<=n;i++) 
        for(int j=i+1;j<=n;j++) {
            int k=id[cal(card[i],card[j])];
            if(k<=j) continue;
            //cout<<i<<j<<k<<endl;
            A[++cnt1][0]=i;
            A[cnt1][1]=j;
            A[cnt1][2]=k;
        }
    for(int i=1;i<=cnt1;i++) {
        for(int j=i+1;j<=cnt1;j++)
            g[i][j]=g[j][i]=check(i,j);
    }
    
    m=n/3;n=cnt1;
}
// u当前节点,pos当前最大团内的点数
bool dfs( int u, int pos ) {
    if (ans == m) return 1;
    int i, j;
    for( i = u + 1; i <= n; i++) { // 遍历其他的点
        if( cnt[i] + pos <= ans ) return 0;  // 可行性剪枝
        if( g[u][i] )  { // 必须有连边
            for( j = 0; j < pos; j++ ) if( !g[i][ vis[j] ] ) break; // 判断这个最大团内其他的点是否和i都有连边
            if( j == pos ) {      // 如果这个团内的点都和i点有连边
                vis[pos] = i;  // 把i点放入最大团
                if( dfs( i, pos+1 ) ) return 1;    // 判断以i为起点的最大团是否存在  
            }    
        }
    }    

    // 记录最大团内的所有点
    if( pos > ans ) { // 如果比原先的最大团大
        for( i = 0; i < pos; i++ )
            group[i] = vis[i]; 
        ans = pos;
        return 1;    
    }    
    return 0;
} 

// 求最大团
int maxclique() {
    ans = 0;  // 记录这张图的最大团
    for (int i = n; i > 0; --i) {  // 从n开始是为了保证先得到以点数大的点开始的最大团,使得后续好继承它的状态,利于剪枝
        vis[0] = i;  // 一开始最大团内点为i
        dfs(i, 1);  // 从i点开始dfs
        cnt[i] = ans;  // 更新以i为起点的最大团内的点数
    }
    return ans;
}

int main() {
    init();
    int t;
    scanf("%d",&t);
    for(int T=1;T<=t;T++) {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) {
            string s;
            cin>>s;
            int temp[5];
            for(int j=0;j<4;j++) {
                int p = s.find(']');
                temp[j] = mp[s.substr(1, p - 1)];
                if(p + 1 < s.size()) s = s.substr(p + 1, s.size() - p - 1);
            }
                
            sort(temp,temp+4);
            card[i]=0;
            for(int j=0;j<4;j++) card[i] = card[i]*3 + temp[j]%10;
        }
        
        build();
        
        printf("Case #%d: %d\n",T,maxclique());
        
        for (int i = 0; i < ans; ++i) {
            int p=group[i];
            printf("%d %d %d\n", A[p][0],A[p][1],A[p][2]);
        }
    }
    return 0;
}

H.Tree Partition

题意: 给定一棵树,要求删除k-1条边,使得树变为k个部分,然后最大的部分的权值最小。每个部分的权值为所有点的累加。

题解: 让最大的部分最小,那么就直接二分答案。二分的时候check函数就从子树往上累加,一旦子树和大于二分答案值,那么就把子树割掉,然后计算删除的边和k-1的关系即可

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 2e5 + 10, M = N * 2;
int n, m, T, w[N], k;
int e[M], ne[M], h[N], idx, kase = 1;
LL cnt;

void add(int a, int b ) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

LL dfs(int u, int fa, LL limit) {
    LL sum = w[u];
    vector<LL> v;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        LL tmp = dfs(j, u, limit);
        sum += tmp;
        v.push_back(tmp);
    }
    sort(v.begin(), v.end());
    reverse(v.begin(), v.end());
    for (int i = 0; i < v.size(); ++i) {
        if (sum > limit) {
            sum -= v[i];
            cnt++;
        }
        else break;
    }
    if (sum > limit) cnt = 1e9;
    return sum;
}

bool check(LL limit) {
    cnt = 0;
    dfs(1, -1, limit);
    // cout << limit << " " << cnt << endl;
    return cnt <= k - 1;
}

int main() {
    cin >> T;
    while(T--) {
        scanf("%d%d", &n, &k);
        idx = 0;
        for (int i = 1; i <= n; ++i) h[i] = -1;
        for (int i = 1; i <= n - 1; ++i) {
            int a, b;
            scanf("%d%d", &a, &b);
            add(a, b), add(b, a);
        }

        LL r = 0;
        for (int i = 1; i <= n; ++i) scanf("%d", &w[i]), r += (LL)w[i];
        LL l = 0;
        while(l < r) {
            LL mid = (l + r) >> 1;
            if (check(mid)) r = mid;
            else l = mid + 1;
        }
        printf("Case #%d: ", kase++);
        printf("%lld\n", l);
    }
    return 0;
}

K.Color Graph

题意: 给定一张n个点m条边的无向图,每条边的颜色为白色,问是否能够将边染色,使得红边的颜色最多,且不能出现红色的奇环。

题解: 没有奇数环说明是一个二分图,那么每条红边的端点在二分图的两边。本题的点数很少,那么直接二进制枚举点集,1表示在做部图,2表示在右部图。然后枚举所有边,判断是否在二部图中间,每次更新最大值即可。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 2e5 + 10;
int n, m, T, kase = 1;
int belong[N];
PII edge[N];

int main() {
    cin >> T;
    while(T--){
        cin >> n >> m;
        for (int i = 1; i <= m; ++i) cin >> edge[i].first >> edge[i].second;
        int res = 0;
        for (int i = 0; i < (1 << n); ++i) {
            int tmp = 0;
            for (int j = 0; j < n; ++j) 
                if (i >> j & 1) belong[j] = 0;
                else belong[j] = 1;
            for (int j = 1; j <= m; ++j) {
                int a = edge[j].first, b = edge[j].second;
                if (belong[a] ^ belong[b]) tmp++;
            }
            res = max(res, tmp);
        }
        printf("Case #%d: ", kase++);
        cout << res << endl;
    }
    return 0;
}

M.Blood Pressure Game

题意: 给定一个长度为n的数列a,数列的贡献为Σ(|a[i + 1] - a[i]|), 现在给定一个长度为m的数列b,打算插入a数列形成的n+1个位置中的m个,问如果插入b数列是的数列的贡献最大。

给出坐过山车过程的 n n n 个转折点高度 a i a_i ai,则坐过山车血压值增加量为 ∑ i = 1 n − 1 ∣ a i − a i + 1 ∣ \sum_{i=1}^{n-1}|a_i - a_{i + 1}| i=1n1aiai+1。给出 m m m 个可新增的转折点高度 b i b_i bi,每个 a i a_i ai 间至多插入一个 b i b_i bi,特殊的, a i a_i ai 前与 a n a_n an 后也可作为插入点,血压值增加量仍按相邻转折点高度差之和计算。问插入 1 , 2 , ⋯   , m 个转折点时能获得的最大血压增量为多少。

题解: a数组形成n+1个位置,而b数组有m个,那么就是二分匹配问题,左部图为b数组,m个点;右部图为a数组的间隙,n+1个点。然后问如何匹配使得费用最大。为什么是费用流呢?因为必须保证把m个点都用完,在这个基础上保证贡献最大,因此是费用流。

现在考虑建图问题:

S->左部图每个点,流量1,费用0

左部图每个点->右部图每个点,流量1,费用 ∣ b i − a j ∣ + ∣ b i − a j − 1 ∣ − ∣ a j − a j − 1 ∣ ∣b_i −a_j∣+∣b_i−a_{j−1}∣−∣a_j−a{j−1}| biaj+biaj1ajaj1;特殊的,对于边 $( b_i , c_1 ) $权重为 ∣ b i − a 1 ∣ ∣ b i − a 1 ∣ bia1 ,边$ ( b_i , c_{n + 1} ) $权重为 ∣ b i − a n ∣ ∣ b_i − a_n ∣ bian

右部图每个点->T,流量1,费用0

然后跑费用流即可。

这里有个问题,题目要求每次加入一个点的时候就打印一下当前的费用,然后在插入m个点的时候打印路径。这个怎么处理呢?

我们考虑EK算法的过程,每次找到一条增广路,那么其实就是每次插入一条增广路,因此每次spfa完就可以打印以下当前的费用了,所以在spfa外面套一层for(1->m)的循环即可

代码:

// 以下代码最后一个样例T了
#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

int const N = 2e3, M = 2e6, INF = 1e9;
int e[M], h[M], idx, ne[M], incf[N], f[M], pre[N], st[N];
LL w[M], d[N];  // d[i]到达i点的最小花费和,incf[i]到达i点的最大流
int n, m, S, T;
int a[N], b[N], pos[N];

void add(int a, int b, int c, LL d) {
    e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx++;
}

inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}

int spfa() {
    for (int i = 1; i <= n + m + 10; ++i) d[i] = -0x3f3f3f3f3f3f3f3f, incf[i] = 0;
    queue<int> q;
    q.push(S);
    st[S] = 1;
    d[S] = 0;
    incf[S] = INF;
    while(q.size()) {
        int t = q.front();
        q.pop();
        st[t] = 0;
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (!f[i] || d[j] >= d[t] + w[i]) continue;  // 如果流为0或者花费变大,不走
            d[j] = d[t] + w[i];  // 更新花费
            pre[j] = i;  // 记录当前点的前一条边
            incf[j] = min(f[i], incf[t]);  // 更新最大流
            if (!st[j]) {
                q.push(j);
                st[j] = 1;
            }
        }
    }
    
    return incf[T] > 0;
}

int main() {
    int kase = 1, TT;
    cin >> TT;
    while(TT--) {
        printf("Case #%d:\n", kase++);
        n = read(), m = read();
        for (int i = 1; i <= n + m + 10; ++i) {
            h[i] = -1;
            pos[i] = st[i] = 0;
        }
        idx = 0;
        S = n + m + 2, T = S + 1;
        LL offset = 0;
        for (int i = 1; i <= n; ++i) {
            a[i] = read();
            if (i >= 2) offset += llabs(1ll * a[i] - a[i - 1]);
        }
        for (int i = 1; i <= m; ++i) b[i] = read();
        for (int i = 1; i <= m; ++i) 
            for (int j = 1; j <= n + 1; ++j) {
                LL cost = 0;
                if (j == 1) cost = llabs((LL)b[i] - a[1]);
                else if (j == n + 1) cost = llabs((LL)b[i] - a[n]);
                else cost = llabs(1ll * b[i] - a[j - 1]) + llabs(1ll * b[i] - a[j]) - llabs(1ll * a[j - 1] - a[j]);
                add(i, j + m, 1, cost);
            }
        
        for (int i = 1; i <= m; ++i) add(S, i, 1, 0);
        for (int i = 1; i <= n + 1; ++i) add(i + m, T, 1, 0);
        int flow = 0;
        LL cost = 0;
        int countt = 1;
        for (int i = 1; i <= m; ++i) {
            if (!spfa()) break;
            countt ++;
            int t = incf[T];
            flow += t, cost += t * d[T];  // 更新最大流和最小花费
            for (int i = T; i != S; i = e[pre[i] ^ 1]) {
                f[pre[i]] -= t, f[pre[i] ^ 1] += t;  // 更新点
            }
            printf("%lld ", offset + cost);
        }
        for (int i = countt; i <= m; ++i) printf("%lld ", offset + cost);
        puts("");
        int cnt = 0;
        for (int i = 0; i < idx; i += 2) {
            if (e[i] > m && e[i] <= n + m + 1 && !f[i]) 
                pos[e[i] - m] = e[i ^ 1], cnt++;
            if (cnt >= n + 1) break;
        }
        for (int i = 1; i <= n; ++i) {
            if (pos[i]) printf("%d ", b[pos[i]]);
            printf("%d ", a[i]);
        }
        if (pos[n + 1]) printf("%d", b[pos[n + 1]]);
        puts("");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值