Educational Codeforces Round 67 DEG 题解

D. Subarray Sorting

题意:给你两个数组a,b,你可以在第一个数组里面任意取一些区间进行排序,问是否能够把数组a变成数组b。
思路:我们用一颗最小值线段树存 a 数组,枚举 b 数组,假设当前枚举到了 bi,我们先找到 a 数组中第一个等于 bi 的值的下标 k,然后在线段树中查询[1, k]最小值,如果最小值 < bi,就说明,k 前面有比 ak 小的数导致 ak 不能顺利的移动到和 bi 匹配的位置,无解,如果有解,就继续枚举 bi+1,并且把线段树中 k 点权值清空
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 3e5 + 10;
int a[maxn], b[maxn];
int mn[maxn * 4];
void up(int o, int l, int r, int k, int v) {
    if (l == r) {
        mn[o] = v;
        return;
    }
    int m = (l + r) / 2, ls = o * 2, rs = o * 2 + 1;
    if (k <= m)
        up(ls, l, m, k, v);
    else
        up(rs, m + 1, r, k, v);
    mn[o] = min(mn[ls], mn[rs]);
}
int qu(int o, int l, int r, int ql, int qr) {
    if (l >= ql && r <= qr)
        return mn[o];
    int m = (l + r) / 2, ls = o * 2, rs = o * 2 + 1, res = 1e9;
    if (ql <= m)
        res = min(res, qu(ls, l, m, ql, qr));
    if (qr > m)
        res = min(res, qu(rs, m + 1, r, ql, qr));
    return res;
}
queue<int> q[maxn];
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, x;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            q[a[i]].push(i);
            up(1, 1, n, i, a[i]);
        }
        int flag = 1;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &x);
            if (!q[x].empty()) {
                int k = q[x].front();
                q[x].pop();
                if (qu(1, 1, n, 1, k) < x)
                    flag = 0;
                else
                    up(1, 1, n, k, 1e9);
            }
            else
                flag = 0;
        }
        for (int i = 1; i <= n; i++)
            while (!q[a[i]].empty())
                q[a[i]].pop();
        if (flag)
            puts("YES");
        else
            puts("NO");
    }
}

E. Tree Painting

题意:给你一颗树,第一次你可以任意选一个点涂黑色,然后你获得的权值为这个点的联通快的大小(未被涂色的联通快),之后你每一次涂色都只能涂与黑点相邻的点,并随之获得权值,求最大总权值。
思路:我们发现如果我们定好了第一个,那么总权值是一个定值,那么问题转化为,枚举每一个点为涂色起点所能获得的总权值,答案是最大的那个,设d[u] 为 u 子树中,先涂 u 点所能获得的总权值,sz[u] 为 u 子树大小,那么转移方程:d[u] = d[son] + sz[u],我们先一遍dfs求出以 1 为根且为涂色起点的答案,接下来再用一个dfs进行换根dp,对于 u 的儿子 v,我们要把根转移到 v,首先断开u v 的连接:d[u] =d[u] - sz[v] -d[v],然后以 v 为根再连接:d[v] += d[u] + n - sz[v]。
#include<bits/stdc++.h>
#define pb push_back
#define ll long long
using namespace std;
const int maxn = 2e5 + 10;
vector<int> G[maxn];
ll d[maxn], ans;
int n, sz[maxn];
void dfs(int u, int fa) {
    sz[u] = 1;
    for (auto v : G[u]) {
        if (v == fa)
            continue;
        dfs(v, u);
        sz[u] += sz[v];
        d[u] += d[v];
    }
    d[u] += sz[u];
}
void dfs2(int u, int fa) {
    ans = max(ans, d[u]);
    for (auto v : G[u]) {
        if (v == fa)
            continue;
        ll tmp = d[u] - d[v] - sz[v];
        d[v] = d[v] + (n - sz[v]) + tmp;
        dfs2(v, u);
    }

}
int main() {
    int u, v;
    cin>>n;
    for (int i = 1; i < n; i++) {
        cin>>u>>v;
        G[u].pb(v);
        G[v].pb(u);
    }
    dfs(1, 0);
    dfs2(1, 0);
    cout<<ans;
}

G. Gang Up

题意:有 m 条双向路连接 n 个地点,这些地点有 k 个人,1号地点是老板开会的地方,现在这 k 个人要去 1 号地点开会,每个人每分钟只能走一条路或者原地不动,对于每条边,如果有 q 个人同时走,那么花费 += q * q * d,每个人到了 1 号节点时,花费 += 这个人花费的时间 * c,求让这 k 个人都到达 1 号节点的最小花费。
思路:我们把每个点根据时间拆点,n,m,k最大为50,那么我们把每个点拆为100个点,每个点的人都可以选择原地不动,那么我们枚举每个点根据时间连边:ui -->ui+1 ,流量为k,费用为 c,对于原图每条边 u v,枚举时间 i,连接ui --> vi+1 和 vi–>ui+1考虑到费用和流量的平方关系,我们加至多50条这样的边(实际上不用这么多),流量均为1,费用为 c + 2 * p - 1(p表示当前第几条边),然后连接1 号点和汇点,直接跑最小费用流即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 55 * 100, inf = 1e9;
struct Edge
{
	int from,to,cap,flow,cost;
	Edge(int a,int b,int c,int d,int e)
	{
		from=a,to=b,cap=c,flow=d,cost=e;
	}
};
struct MCMF{
	int n,m,s,t;
	vector<Edge>edges;
	vector<int>G[maxn];
	int inq[maxn];
	int d[maxn];
	int p[maxn];
	int a[maxn];
	void init(int n)
	{
		this->n=n;
		for(int i=0;i<=n;i++)G[i].clear();
		edges.clear();
	}
	void add(int from,int to,int cap,int cost)
	{
		edges.push_back(Edge(from,to,cap,0,cost));
		edges.push_back(Edge(to,from,0,0,-cost));
		m=edges.size();
		G[from].push_back(m-2);
		G[to].push_back(m-1);
	}
	bool bellman(int s,int t,int& flow,int& cost)
	{
		for(int i=0;i<=n;i++)d[i]=inf,inq[i]=0;
		d[s]=0;inq[s]=1;p[s]=0;a[s]=inf;
		queue<int>Q;
		Q.push(s);
		while(!Q.empty())
		{
			int u=Q.front();Q.pop();
			inq[u]=0;
			for(int i=0;i<G[u].size();i++)
			{
				Edge& e=edges[G[u][i]];
				if(e.cap>e.flow&&d[e.to]>d[u]+e.cost)
				{
					d[e.to]=d[u]+e.cost;
					p[e.to]=G[u][i];
					a[e.to]=min(a[u],e.cap-e.flow);
					if(!inq[e.to])
					{
						Q.push(e.to);
						inq[e.to]=1;
					}
				}
			}
		}
		if(d[t]==inf)return false;
		flow+=a[t];
		cost+=d[t]*a[t];
		int u=t;
		while(u!=s)
		{
			edges[p[u]].flow+=a[t];
			edges[p[u]^1].flow-=a[t];
			u=edges[p[u]].from;
		}
		return true;
	}
	int mincost(int s,int t)
	{
		int flow=0,cost=0;
		while(bellman(s,t,flow,cost));
		return cost;
	}
} solve;
int n, m;
int id(int u, int time) {
    return (u - 1) * 2 * 50 + time;
}
int main()
{
    int k, c, d, u, v;
    cin>>n>>m>>k>>c>>d;
    int S = 0, T = n * 50 * 2 + 1;
    solve.init(T + 1);
    for (int i = 1; i <= k; i++) {
        cin>>u;
        solve.add(S, id(u, 1) ,1, 0);
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j < 100; j++)
            solve.add(id(i, j), id(i, j + 1), k, c);
    for (int i = 1; i <= 100; i++)
        solve.add(id(1, i), T, k, 0);
    for (int i = 1; i <= m; i++) {
        cin>>u>>v;
        for (int j = 1; j < 100; j++)
        for (int p = 1; p <= k; p++) {
            solve.add(id(u, j), id(v, j + 1), 1, c + (2 * p - 1) * d);
            solve.add(id(v, j), id(u, j + 1), 1, c + (2 * p - 1) * d);
        }
    }
    cout<<solve.mincost(S, T);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值