2020 Multi-University Training Contest 4 补题

总结

这场的G题,提醒要初入网络流了。

C - Contest of Rope Pulling

题意:

从两个集合中各自选出若干元素,每个元素两个属性w[i]和v[i],组成集合A和集合B,要求集合A的w[i]之和==集合B的w[i]之和且两个集合的v[i]之和最大。
∑ n + m < = 1 e 4 , 单 组 n , m < = 1 e 3 , w [ i ] < = 1 e 3 , − 1 e 9 < = v [ i ] < = 1 e 9 \sum n + m <= 1e4, 单组 n , m <= 1e3, w[i] <= 1e3, -1e9 <= v[i] <= 1e9 n+m<=1e4,n,m<=1e3,w[i]<=1e3,1e9<=v[i]<=1e9

思路:

一眼经典01背包。
假设按照正规写法。
f[i][j]:对于单个组,从前i各种选,w[i]值之和为j的集合。
属性:max
可以发现单组 ∑ w [ i ] < = 1 e 6 \sum w[i] <= 1e6 w[i]<=1e6, 按照正规写法O(n * 1e6)铁T。
第一种写法。O(sum[1] + sum[2] + … sum[n - 1]) 由于数据水,可以飘过去
先从小到大排序,尽量缩小每次的枚举次数。

	int sum = 0; //因为是恰好装满的背包
	for(int i = 1 ; i <= n ; i ++) //所以每次转移的max为当前sum[i]。每次有效状态最小为v[i]最大为前缀和sum[i]
	{
		sum += v[i];
		for(int j = sum ; j >= v[i]; j --)
		{
//			f[i][j] = f[i - 1][j];
//			if(j - v[i] >= 0)
			f[j] = max(f[j], f[j - v[i]] + w[i]);
		}
	}

第二种写法。比较神奇。
随机化+背包。
由于 ∑ w [ i ] < = 1 e 6 \sum w[i] <= 1e6 w[i]<=1e6,但是ans在f[n + m][0]。
造成了许多的空间浪费。
可以把其中一个组的w[i]重新赋值为-w[i]。
再以随机化的方式重排数组。
一定量的可以缩小背包的体积。
然后采取当前时间复杂度内允许的极限体积maxn。
由于存在负值。
[-maxn, 0, maxn]
可以整体右移动maxn.
[0, maxn, 2 * maxn]
那么对于每次所选物品
i f ( a [ i ] . f i r s t > 0 ) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − a [ i ] . f i r s t ] + a [ i ] . s e c o n d )    ( j − a [ i ] . f i r s t > = 0 ) if(a[i].first > 0) f[i][j] = max(f[i][j], f[i - 1][j - a[i].first] + a[i].second) \;(j - a[i].first >= 0) if(a[i].first>0)f[i][j]=max(f[i][j],f[i1][ja[i].first]+a[i].second)(ja[i].first>=0)
e l s e f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − a [ i ] . f i r s t ] + a [ i ] . s e c o n d    ( j − a [ i ] . f i r s t < = 2 ∗ m a x n ) else f[i][j] = max(f[i][j], f[i - 1][j - a[i].first] + a[i].second \; (j - a[i].first <= 2 * maxn) elsef[i][j]=max(f[i][j],f[i1][ja[i].first]+a[i].second(ja[i].first<=2maxn)
两个转移方程看似一样,实则表示含义不同。注意约束条件。

PII a[maxn];
ll f[maxn];
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		int n, m;
		scanf("%d %d", &n, &m);
		for(int i = 1 ; i <= n + m; i ++)
		{
			scanf("%d %lld", &a[i].first, &a[i].second); 
			if(i > n)
			a[i].first = -a[i].first;
		}
		random_shuffle(a + 1, a + 1 + n + m);
		for(int j = 0; j <= maxn ; j ++)
		f[j] = -1e18;
		f[100000 / 2] = 0;
		for(int i = 1 ; i <= n + m ; i ++)
		{
			if(a[i].first > 0)
			{
				for(int j = 100000 ; j >= a[i].first ; j --)
				{
					f[j] = f[j];
					if(f[j - a[i].first] != -1e18) // 大  <---- 小 
					f[j] = max(f[j], f[j - a[i].first] + a[i].second);
				}
			}
			else
			{ 
				for(int j = 0 ; j <= 100000 + a[i].first ; j ++)
				{
					f[j] = f[j];
					if(f[j - a[i].first] != -1e18)
					f[j] = max(f[j], f[j - a[i].first] + a[i].second);
				}
			}
		}
		printf("%lld\n", f[100000 / 2]);
	}
	return 0;
}

D - Deliver the Cake

题意:

起点为s,终点为t, 且保证s-- > t连通。
给定n个点及每个点的状态(L, R, M), L:在该点只能左手拿东西, R:在该点只能右手拿,M:在该点左手右手都可以。
求从s–>t的最少时间。每次换手可以任意位置花费x个时间。
1 < = n < = 1 e 5 , 1 < = m < = 2 e 5 , 1 < = x < = 1 e 9 1 <= n <= 1e5, 1 <= m <= 2e5, 1 <= x <= 1e9 1<=n<=1e5,1<=m<=2e5,1<=x<=1e9
∑ n < = 2 e 5 , ∑ m < = 4 e 5 \sum n<= 2e5, \sum m <= 4e5 n<=2e5,m<=4e5

思路:

最开始思路假了,认为是最简单的最短路问题。
wa了一发之后,想明白了。
之前写法,能不换手就不换手,每次到达一个新点,如若要换手,就改成当前点的状态,并且加上x,否则不换手,也改成当前点的状态。
题中顶点e,若属性为M,那么在当前点可以选择换手,也可以选择不换手。 会造成两种情况。

  1. 从L属性的点走到当前点。且当前不换手,那么下次遇到R属性,必须要换手(代码写法,直接把到达M点,且手的属性改成了M,造成下次走R属性的点, 没有换手,造成答案错误)。
  2. 从R属性的点走到当前点。同理。

赛时虽然想到了DP,但是认为无向图不存在拓扑结构。
竟然想到了把M属性的点拆成L属性和R属性两个点,然后重新跑最短路!!

code:

#include <iostream>
#include <map>
#include <algorithm>
#include <queue>

using namespace std;
typedef long long ll;
typedef pair <ll, int> PII;
const int maxn = 3e5 + 10;		
int n, m, s, t;
ll x;
string str[maxn]; 
int h[maxn], ne[maxn * 6], e[maxn * 6], idx;
ll w[maxn * 6], dis[maxn * 6];
bool book[maxn];
struct note{
	int L;
	int R;
}A[maxn];
void add(int u, int v, ll ww)
{
	w[idx] = ww;
	e[idx] = v;
	ne[idx] = h[u];
	h[u] = idx ++;
}
void djs(int sta)
{
	for(int i = 1 ; i <= 3 * n ; i ++)
	dis[i] = 1e18, book[i] = false;
	dis[sta] = 0;
	priority_queue <PII, vector<PII>, greater<PII>> heap;
	heap.push({0, sta});
	while(heap.size())
	{
		PII t = heap.top();
		heap.pop();
		ll a = t.first;
		int b = t.second;
		if(book[b]) continue;
		book[b] = true;
		for(int i = h[b] ;i != -1 ; i = ne[i])
		{
			int j = e[i];
			if(str[j] == str[b])
			{
				if(dis[j] > dis[b] + w[i])
				{
					dis[j] = dis[b] + w[i];
					heap.push({dis[j], j});
				}
			}
			else
			{
				if(dis[j] > dis[b] + w[i] + x)
				{
					dis[j] = dis[b] + w[i] + x;
					heap.push({dis[j], j});
				}
			}
		}
	}
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		scanf("%d %d %d %d %lld", &n, &m, &s, &t, &x);
		string c;
		cin >> c;
		int len = c.length();
		for(int i = 0 ; i < len ; i ++)
		str[i + 1] = c[i];
		for(int i = 1 ; i <= 3 * n ; i ++) h[i] = -1, A[i].L = A[i].R = 0;
		idx = 0;
		int num = n;
		for(int i = 1 ; i <= m ; i ++)
		{
			int u, v;
			ll w;
			scanf("%d %d %lld", &u, &v, &w);
			if(str[u] == "M" && !A[u].L)
			{
				num ++;
				A[u].L = num;
				str[num] = "L";
				num ++;
				A[u].R = num;
				str[num] = "R";
			}
			if(str[v] == "M" && !A[v].L)
			{
				num ++;
				A[v].L = num;// n + 3
				str[num] = "L";
				num ++;
				A[v].R = num; // n + 4
				str[num] = "R";
			}
			if(str[u] != "M" && str[v] != "M") //都不等于 
			add(u, v, w), add(v, u, w);
			else if(str[u] == str[v] && str[u] == "M")
			{
				add(A[u].L, A[v].L, w), add(A[v].L, A[u].L, w);
				add(A[u].L, A[v].R, w), add(A[v].R, A[u].L, w);
				add(A[u].R, A[v].L, w), add(A[v].L, A[u].R, w);
				add(A[u].R, A[v].R, w), add(A[v].R, A[u].R, w);
			}
			else if(str[u] == "M" && str[v] != "M")
			{
				add(A[u].L, v, w), add(v, A[u].L, w);
				add(A[u].R, v, w), add(v, A[u].R, w);
			}
			else if(str[u] != "M" && str[v] == "M")
			{
				add(u, A[v].L, w), add(A[v].L, u, w);
				add(u, A[v].R, w), add(A[v].R, u, w);
			}
		}
		ll res = 2e18;
		if(str[s] == "M")
		{
			if(str[t] == "M")
			{
				djs(A[s].L);
				res = min({res, dis[A[t].L], dis[A[t].R]});
				djs(A[s].R);
				res = min({res, dis[A[t].L], dis[A[t].R]});
			}
			else
			{
				djs(A[s].L);
				res = min({res, dis[t]});
				djs(A[s].R);
				res = min({res, dis[t]});
			}
		}
		else
		{
			djs(s);
			if(str[t] == "M")
			{
				res = min({res, dis[A[t].L], dis[A[t].R]});
			}
			else
			{
				res = min(res, dis[t]);
			}
		}
		printf("%lld\n",res);
	}
	return 0;	
} 
/*
每次换手xs 呆在原地
7 6 1 7 100
LMMLMMR
1 2 10
2 3 10
3 4 10
4 5 10
5 6 10
6 7 10

*/

DP写法
在最短路算法中,djs()堆优化算法,本身在求最短路的过程中就满足了拓扑结构,SPFA()由于一个点进队多次,必须在跑完最短路后再建一颗最短路径树,才能跑DP。
感觉这种类似,最短路 + dp状态机。
对于堆中弹出的点,假设一个状态已经更新过所连的边了,那么直接continue就好了。(类似最短路, 只不过改成了状态而已)
这种写法,代码贼短。!!!

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
int n, m, s, t;
int h[maxn], ne[maxn * 2], e[maxn * 2], idx;
ll x, dis[maxn][3], w[maxn * 2];
char str[maxn];
bool book[maxn][3];
void add(int u, int v, ll ww)
{
	w[idx] = ww;
	e[idx] = v;
	ne[idx] = h[u];
	h[u] = idx ++;
}
struct note{
	int op;
	int id;
	ll w;
	bool operator < (const note &a) const
	{
		return w > a.w;
	}
};
void djs()
{
	for(int i = 1 ; i <= n ; i ++) dis[i][0] = dis[i][1] = 1e18, book[i][0] = book[i][1] = false;
	priority_queue <note> heap;
	if(str[s] == 'M')
	{
		dis[s][0] = dis[s][1] = 0;
		heap.push({0, s, 0ll});
		heap.push({1, s, 0ll});
	}
	else if(str[s] == 'L')
	dis[s][0] = 0, heap.push({0, s, 0ll});
	else dis[s][1] = 0, heap.push({1, s, 0ll});
	while(heap.size())
	{
		auto t = heap.top();
		heap.pop();
		int b = t.id;
		if(book[b][t.op]) continue;
		book[b][t.op] = true;
		for(int i = h[b] ; i != -1 ; i = ne[i])
		{
			int j = e[i];
			if(str[j] == 'L' || str[j] == 'M')
			{
				ll temp = dis[j][0];
				dis[j][0] = min({dis[j][0], dis[b][0] + w[i], dis[b][1] + w[i] + x});
				if(dis[j][0] < temp)
				{
					heap.push({0, j, dis[j][0]});
				}
			}
			if(str[j] == 'R' || str[j] == 'M')
			{
				ll temp = dis[j][1];
				dis[j][1] = min({dis[j][1], dis[b][1] + w[i], dis[b][0] + w[i] + x});
				if(dis[j][1] < temp)
				heap.push({1, j, dis[j][1]});
			}
		}
	}	
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		scanf("%d %d %d %d %lld", &n, &m, &s, &t, &x);
		scanf("%s", str + 1);
		for(int i = 1 ; i <= n ; i ++)
		h[i] = -1;
		idx = 0;
		for(int i = 1 ; i <= m ; i ++)
		{
			int u, v;
			ll w;
			scanf("%d %d %lld", &u, &v, &w);
			add(u, v, w), add(v, u, w);
		}
		djs();
		if(str[t] == 'M')
		{
			printf("%lld\n", min(dis[t][0], dis[t][1]));
		}
		else if(str[t] == 'L')
		{
			printf("%lld\n", dis[t][0]);
		}
		else printf("%lld\n", dis[t][1]);
	}
	return 0;
}

E - Equal Sentences

题意:

给定n个单词,求"almost-equal"的单词的数量。
“almost-equal”:原句中第i个单词和新句中第i个单词的下标不超过1。
∑ n < = 2 e 5 , n < = 1 e 5 \sum n <= 2e5, n <= 1e5 n<=2e5,n<=1e5

思路:

题意说明一个单词可以和前后相距1的单词交换。(且要求相互交换的单词不相同)
885d7.png)
交换关系图示,假设2能和1换,那么3就不能和2换,但是4可以继续和3换…。
只要满足相邻不发生连续两次交换即可。
f[i][0/1]:前i个中,且第i个状态为0\1的所有合法方案的集合
属性:cnt
f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ]    i f ( s t r [ i ] ! = s t r [ i − 1 ] ) f[i][1] = f[i - 1][0] \; if(str[i] != str[i - 1]) f[i][1]=f[i1][0]if(str[i]!=str[i1])
f [ i ] [ 0 ] = f [ i − 1 ] [ 1 ] + f [ i − 1 ] [ 0 ] f[i][0] = f[i - 1][1] + f[i - 1][0] f[i][0]=f[i1][1]+f[i1][0]
初始化 f[0][0] = 1

code:

#include <iostream>
#include <map>
#include <algorithm>

using namespace std;
const int maxn = 2e5 + 10;
typedef long long ll;
string str[maxn];
ll f[maxn][4];
const ll mod = 1e9 + 7;
ll get_mod1(ll a, ll b)
{
	return (a % mod + (b % mod)) % mod;
}
int main() //O(n) 0(nlogn)
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		int n;
		scanf("%d", &n);
		for(int i = 1 ; i <= n ; i ++)
		{
			cin >> str[i];
			f[i][0] = f[i][1] = 0;
		}
		f[1][0] = 1;
		for(int i = 2 ; i <= n ; i ++)
		{
			if(str[i] != str[i - 1]) f[i][1] = get_mod1(f[i][1], f[i - 1][0]);
			f[i][0] = get_mod1(f[i - 1][0] , f[i - 1][1]);
		}
		printf("%lld\n", get_mod1(f[n][0], f[n][1]));
	} 
	return 0;	
}

G - Go Running

题意:

给定一个无限长的x轴,n个信息。求满足n个信息的最少学生的数量。学生可随意选择跑步开始时间,结束时间 ,地点,方向,且速度 = 1m/s。
信息表示为:t[i] x[i]:至少一个学生在t[i]s在x[i]处跑步。
n < = 1 e 5 , 1 < = t [ i ] , x [ i ] < = 1 e 9 n <= 1e5, 1 <= t[i], x[i] <= 1e9 n<=1e5,1<=t[i],x[i]<=1e9
∑ n < = 5 e 5 \sum n <= 5e5 n<=5e5

思路:

可知学生跑步距离x = 1 * t + ▲。
观察样例。
在这里插入图片描述

假设画一条x–t的坐标轴。那么对于每个点(x,y)经过该点的直线有两条,且斜率分别为1和-1。
贪心的想,要使得每条线经过的点尽量多,才能使得数量最少。
那么对于图中(1,1)点,属于两条直线。交于x轴a点和b点。
不就相当于a—b,a和b之间有一条直线,且要完成覆盖的点为(1,1),既可以选a号点进行覆盖,也可以选择b号点进行覆盖。
不就相当于二分图中的最小点覆盖问题嘛?
观察题目范围,匈牙利跑最大匹配。 时间复杂度O(n * m)水不过去。
只能最大流跑 O(n sqrt(m))。
但是。 网络流。。还属于盲区。

PII a[maxn];
int n;
map <PII, int> mp;
int h[maxn], ne[maxn * 2], e[maxn * 2], w[maxn * 2], idx;
int S, T, cur[maxn], d[maxn]; 
void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
// 找增广路 同时将图变成分层图,方便处理环
int bfs()
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    queue <int> alls;
    d[S] = 0, alls.push(S), cur[S] = h[S];
    while(!alls.empty())
    {
        int u = alls.front();
        alls.pop();
        for(int i = h[u]; i != -1; i = ne[i])
        {
            if(w[i] && d[e[i]] == -1)
            {
                d[e[i]] = d[u] + 1;
                cur[e[i]] = h[e[i]];
                if(e[i] == T)   return 1;
                alls.push(e[i]);
            }
        }
    }
    return 0;
}
// 更新残留网络
int dfs(int u, int flow)
{
    if(u == T)  return flow;
    int rest = 0, k;
    for(int i = cur[u]; i != -1 ; i = ne[i])
    {
        cur[u] = i; // 当前弧优化
        if(w[i] && d[e[i]] == d[u] + 1)
        {
            k = dfs(e[i], min(flow - rest, w[i]));
            if(!k)  d[e[i]] = -1; // 3号优化,将分层图中的当前点变成 -1 相当于打上标记
            w[i] -= k, w[i ^ 1] += k, rest += k;
            if(rest == flow)    return flow; // 2号优化
        }
    }
    return rest;
}

int dinic()
{
    int res = 0, flow;
    while(bfs())    while(flow = dfs(S, INF))   res += flow;
    return res;
}
int main() // O(n) O(nlogn)
{
    int t;
    scanf("%d", &t);
    while(t --)
    {
        scanf("%d", &n);
        vector <ll> Alls;
        Alls.push_back(-1e9);
        mp.clear();
        for(int i = 1 ; i <= n ; i ++) 
        {
            ll t, x;
            scanf("%lld %lld", &t, &x);
            a[i] = {x + t + 1e9, x - t};
            Alls.push_back(x + t + 1e9);
            Alls.push_back(x - t);
        }
        sort(Alls.begin(), Alls.end());
        Alls.erase(unique(Alls.begin(), Alls.end()), Alls.end()); // n * 2
        S = int(Alls.size()), T = S + 1;
        memset(h, -1, sizeof(h));
        idx = 0;
        for(int i = 1 ; i <= n ; i ++)
        {
            int temp1 = lower_bound(Alls.begin(), Alls.end(), a[i].first) - Alls.begin();
            int temp2 = lower_bound(Alls.begin(), Alls.end(), a[i].second) - Alls.begin();
            if(!mp.count({temp1, temp2}))
            add(temp1, temp2, 1), add(temp2, temp1, 0), mp[{temp1, temp2}] = 1;
            if(!mp.count({S, temp1}))
            add(S, temp1, 1), add(temp1, S, 0), mp[{S, temp1}] = 1;
            if(!mp.count({temp2, T}))
            add(temp2, T, 1), add(T, temp2, 0), mp[{temp2, T}] = 1;
        }
        printf("%d\n", dinic());
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值