2020.11.31 选拔赛题解

训练赛地址ios

A (线段树or暴力)

题目连接
⭐⭐⭐c++

题意:
有一个长为\(L\)的停车道,坐标从0开始,车辆停放时车头朝向正方向,且距离车头\(f\)范围内和距离车尾\(b\)范围内不能有其它车辆。
有两种操做:数组

  1. “1 \(a\)”,有1辆长度为\(a\)的车开进来找停车位。若是能找到输出最小的起始位置,不然输出-1.
  2. “2 \(a\)”,第\(i\)个操做中开进来的车开出去了(保证第\(i\)个操做是1操做)

解析:函数

  • 作法一(线段树)
    考虑创建线段树,维护区间内最大无车区域\(mx\),左端点向内最大无车区域\(ls\),右端点向内最大无车区域\(rs\),以及\(lazy\)标签
  1. 对于\(update\)函数,用\(lazy\)维护维护区间修改值,,进行基础的区间修改便可,当修改区间彻底被包含在线段区间内,同时更新\(mx,ls,rs\)
  2. 对于\(query\)函数,考虑左端点向内无车区域、左区间+右区间无车区域,右端点向内无车区域三种放置车辆状况
    • 对于左端点向内无车区域能够放置的状况,对左区间进行递归查询
    • 对于左区间+右区间能够放置的状况,返回【左区间右端点-对应的\(rs\)值】
    • 对于右端点向内无车区域能够放置的状况,对右区间进行递归查询
  3. 对于\(pushdown\)函数,\(lazy\)标签下移,更改维护区段值
  4. 对于\(pushup\)函数,父区间\(ls\)承接左区间\(ls\),父区间\(rs\)承接右区间\(rs\),且若是左区间均可用,则能够延伸到右区间的\(ls\),同理,若是右区间均可用,则能够延伸到左区间的\(rs\),而父区间对应的\(mx\),则要知足\(mx=\max(mx_l,mx_r,rs_l+ls_r)\)
  5. 因为整个\(L\)两端的汽车放置不受\(b,f\)的影响,所以能够将\(L\)拓展\(L+b+f\),这样处理还避免了\(query\)查询中,若是初始整个区间都要被使用的状况。
    注意:
    1. \(query\)函数中,必定要按所给顺序进行查询,保证是在最左端插入可放置车辆
    2. 对于返回值而言,因为总体\((b+a+f)\)插入位置比线段树位置少了\(b\)\(L\)改变所致使),而车尾位置又比总体插入位置多了b,相抵,即可以获得线段树位置即为车辆车尾对应的位置
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/

struct Tree
{
	int l, r, lazy, ls, rs, mx;
};

const int maxn = 1e6 + 205;

struct Segment_Tree
{
	Tree tree[maxn << 2];

	void pushdown(int root)
	{
		if (~tree[root].lazy)
		{
			tree[root << 1].mx = tree[root << 1].ls = tree[root << 1].rs = (tree[root << 1].r - tree[root << 1].l) * tree[root].lazy;
			tree[root << 1 | 1].mx = tree[root << 1 | 1].ls = tree[root << 1 | 1].rs = (tree[root << 1 | 1].r - tree[root << 1 | 1].l) * tree[root].lazy;
			tree[root << 1].lazy = tree[root << 1 | 1].lazy = tree[root].lazy;
			tree[root].lazy = -1;
		}
	}
	void pushup(int root)
	{
		tree[root].ls = tree[root << 1].ls, tree[root].rs = tree[root << 1 | 1].rs;
		tree[root].mx = max(tree[root << 1].mx, tree[root << 1 | 1].mx);
		tree[root].mx = max(tree[root].mx, tree[root << 1].rs + tree[root << 1 | 1].ls);
		if (tree[root << 1].mx == tree[root << 1].r - tree[root << 1].l)
			tree[root].ls += tree[root << 1 | 1].ls;
		if (tree[root << 1 | 1].mx == tree[root << 1 | 1].r - tree[root << 1 | 1].l)
			tree[root].rs += tree[root << 1].rs;
	}

	void build(int k, int l, int r)
	{
		tree[k].l = l, tree[k].r = r;
		tree[k].ls = tree[k].rs = tree[k].mx = r - l;
		tree[k].lazy = -1;
		if (r - l != 1)
			build(k << 1, l, l + (r - l) / 2), build(k << 1 | 1, l + (r - l) / 2, r);
	}

	void update(int k, int ql, int qr, int val)
	{
		int& l = tree[k].l, & r = tree[k].r, & mx = tree[k].mx, & ls = tree[k].ls, & rs = tree[k].rs;
		if (ql <= l && r <= qr)
		{
			ls = rs = mx = (r - l) * val;
			tree[k].lazy = val;
		}
		else if (ql<r && qr>l)
		{
			pushdown(k);
			update(k << 1, ql, qr, val);
			update(k << 1 | 1, ql, qr, val);
			pushup(k);
		}
	}

	int query(int k, int val)
	{
		int& l = tree[k].l, & r = tree[k].r, & mx = tree[k].mx, & ls = tree[k].ls, & rs = tree[k].rs;
		if (tree[k << 1].mx >= val)
		{
			pushdown(k);
			return query(k << 1, val);
		}
		else if (tree[k << 1].rs + tree[k << 1 | 1].ls >= val)
			return tree[k << 1].r - tree[k << 1].rs;
		else if (tree[k << 1 | 1].mx >= val)
		{
			pushdown(k);
			return query(k << 1 | 1, val);
		}
		return -1;
	}

}sol;

pair<int, int> qu[105];

int main()
{
	//freopen("abc.in", "r", stdin);
	int L, b, f;
	int n, opt, a;
	while (~scanf("%d%d%d", &L, &b, &f))
	{
		L += b + f;
		sol.build(1, 0, L);
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i)
		{
			scanf("%d%d", &opt, &a);
			if (opt == 1)
			{
				int ans = sol.query(1, a + b + f);
				printf("%d\n", ans);
				if (ans == -1) continue;
				qu[i].first = ans + b;
				qu[i].second = qu[i].first + a;
				sol.update(1, qu[i].first, qu[i].second, 0);
			}
			else
				sol.update(1, qu[a].first, qu[a].second, 1);
		}
	}
}
  • 作法二(暴力排序枚举)
    查看题目能够发现,询问次数居然只有最多100次?What?! 也就是说停车场中的车辆也最多只有100辆,因而这道题能够直接暴力
  1. 因为端点车辆放置不受\(b,f\)的影响,因此提早在数组中放置\([-b,-b]\)\([L+f,L+f]\)的两辆汽车(这样不用特判)
  2. 遇到1操做,\(O(n)\)查询能够最早能够插入的位置,而后进行插入,插入位置\(i\)须要知足

\[car[i].l-car[i-1].r>=a+b+f \]

同时须要保存对应的操做数,操做结束后进行排序
3. 遇到2操做,\(O(n)\)查询\(a\)所对应的车辆,进行删除便可ui

#include<bits/stdc++.h>
using namespace std;
/*===========================================*/
struct Car
{
	int id;
	int l, r;
	bool operator<(const Car& a)const
	{
		return l < a.l;
	}
};

vector<Car> car;

void solve()
{
	int L, b, f, n;
	while (~scanf("%d%d%d", &L, &b, &f))
	{
		car.clear();
		car.push_back(Car{ -1, -b, -b });
		car.push_back(Car{ -1,L + f,L + f });
		int opt, a;
		scanf("%d", &n);
		for (int j = 1; j <= n; ++j)
		{
			scanf("%d%d", &opt, &a);
			if (opt == 1)
			{
				bool ok = false;
				for (int i = 1; i < car.size(); ++i)
				{
					if (car[i].l - car[i - 1].r >= a + b + f)
					{
						car.push_back(Car{ j,car[i - 1].r + b,car[i - 1].r + b + a });
						printf("%d\n", car[car.size() - 1].l);
						ok = true;
						sort(car.begin(), car.end());
						break;
					}
				}
				if (!ok)
					printf("-1\n");
			}
			else
				for (int i = 1; i < car.size(); ++i)
					if (car[i].id == a)
					{
						car.erase(car.begin() + i);
						break;
					}
		}
	}
}

int main()
{
	solve();
}

B(dfs构造)

题目连接
⭐⭐编码

题意:
给你三个整数,\(n,d,k\)。你的任务是从新构建出一棵树或者判断出不能构造出这样的树。\(n\)表明这棵树一共有\(n\)个点,\(d\)表明的是这棵树的直径为\(d\)\(k\)是每一个最大节点度数为\(k\)。若是可以构造出这样的树,输出‘YES’,和全部的边。若是不能,那么输出‘NO’。spa

解析:.net

  1. 首先构造出这条最长的直径,接下来就是向这条直径上的点上添加其余的点同时保证直径不会超过\(d\)便可
  2. 很明显能够发现,添加点的层数\(i\)要保证\(i\le \min(i-1,d+1-i)\)
  3. 这样的话即可以用dfs进行指数级别的递归构造

注意:code

  1. 第一次进行构造时,因为是从直径上的点开始,因此可用度为\(k-2\),而日后的构造可用度为\(k-1\)
  2. \(k=1\wedge n>2\text{(only two points exist is allowed)}\qquad and \qquad n\le d\text{(construct the diam)}\)两种状况进行特判
  3. 递归里写法必定要注意前后顺序,保证在达到目标数之后,再也不向数组中添加新答案,也不要继续递归性查询,不然会爆MLE
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

int now;
typedef pair<int, int> P;
vector<P> v;
int n, d, k;

void dfs(int fa, int up, int lim)
{
	if (now == n || !up || !lim) return;
	while (up--)
	{
		v.push_back(P(fa, ++now));
		if (now == n) return;
		else dfs(now, k - 1, lim - 1);
		if (now == n) return;
	}
}

int main()
{
	scanf("%d%d%d", &n, &d, &k);
	if (k == 1 && n > 2 || n <= d)
	{
		printf("NO");
		return 0;
	}
	bool ok = false;
	now = d + 1;
	for (int i = 1; i <= d; ++i)
		v.push_back(P(i, i + 1));
	for (int i = 2; i <= d; ++i)
		dfs(i, k - 2, min(i - 1, d + 1 - i));
	if (now == n)
	{
		printf("YES\n");
		for (auto& i : v)
			printf("%d %d\n", i.first, i.second);
	}
	else
		printf("NO");

}

C(二分+贪心)

题目连接
⭐⭐排序

题目:
给出\(n\)个数,从中选出长度为\(k\)的子序列造成环,若是相邻两个元素之和不超过\(M\),求解\(M_{min}\)

解析:

  1. 对于两个数,若是他们的和不超过\(M\),则两者至少有一个小于等于\(\lfloor\frac{M}{2}\rfloor\)
  2. 那么即可以记录这些\(x\)的位置\(i\ ,\{i\mid p[i]\le\lfloor\frac{M}{2}\rfloor\}\)贪心地在\((p[i],p[i-1])\)之间插入可行点,这样就能够得到对于某个\(M\)所能获得的环内元素数量\(\Delta k\)的最大值
  3. 能够获得每一个\(M\)所对应的\(\Delta k_{max}\),即可以进行二分

注意:

  1. 因为单个数字上限达到了\(10^9\),因此\(M\)会大于\(2\times10^9>INT_{max}\),所以必定要开long long
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn=2e5+5;
int w[maxn];
int p[maxn];
int n,k;

int ok(long long m)
{
    int res=0;
    int cnt=0;
    for(int i=0;i<n;++i)
        if(w[i]<=m/2)
            p[cnt++]=i;
    if(cnt<=1)
        return cnt;
    for(int i=1;i<cnt;++i)
        for(int j=p[i-1]+1;j<p[i];++j)
            if(1ll*w[j]+max(w[p[i-1]],w[p[i]])<=m)
            {
                ++res;
                break;
            }
    for(int j=p[cnt-1]+1;j<n;++j)
        if(1ll*w[j]+max(w[p[cnt-1]],w[p[0]])<=m)
            return res+cnt+1;
    for(int j=0;j<p[0];++j)
        if(1ll*w[j]+max(w[p[cnt-1]],w[p[0]])<=m)
            return res+cnt+1;
    return res+cnt;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;++i)
        scanf("%d",&w[i]);
    long long l=0,r=2e9+10,mid;
    while(l<r)
    {
        mid=l+(r-l)/2;
        if(ok(mid)>=k) r=mid;
        else l=mid+1;
    }
    printf("%lld",r);
}

D(打表找规律)

题目连接

题意:
给出¥1,¥5,¥10,¥50,四种钞票,问\(n\)张钞票能够组成多少种不一样的面额

解析:
初看题目很像一道dp,但发现\(n\)高达\(10^9\),果断放弃
打表找规律不失为一种好方法

注意:不开long long成神仙

总结:
经过打表发如今\(n>11\)时,答案是一个等差数列,公差为49,这样的话对\(n\le11\)部分进行特判,就解决了问题

打表代码:

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

set<int> s;

void dfs(int x, int cur, int num)
{
	switch (x)
	{
	case 0:
		for (int i = 0; i <= cur; ++i)
			dfs(x + 1, cur - i, num + i);
		break;
	case 1:
		for (int i = 0; i <= cur; ++i)
			dfs(x + 1, cur - i, num + 5 * i);
		break;
	case 2:
		for (int i = 0; i <= cur; ++i)
			dfs(x + 1, cur - i, num + 10 * i);
		break;
	case 3:
		s.insert(num + cur * 50);
	}
}
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/

LL ans[] = { 0,4,10,20,35,56,83,116,155,198,244,292 };

int main()
{
	LL n;
	scanf("%lld", &n);
	if (n <= 11)
		printf("%lld", ans[n]);
	else
		printf("%lld", ans[11] + 49 * (n - 11));
}

E(暴力+KMP)

题目连接
⭐⭐⭐

题意:
给出\(n\)个独立的单词。在任意两个相邻的单词之间有且仅有一个空格。须要注意的是,在第一个单词以前没有空格,在最后一个单词以后也没有空格。
如今能够进行一次操做,将重复出现过的单词以及他们之间的空格进行组合,造成一个单词组,每一个单词用一个字母表示,例如"\(ab\ cd\ ab\ cd\ ee\rightarrow A A\ ee\)",这个单词组在原所给单词序列中出现须要多于一次
问进行这样的操做后,求单词序列长度的最小值

解析:

  1. \(map\)存储每一个单词所对应的编码,这样就将单词组,压缩成了一个只包含数字的数组。同时记录每一个编码所对应单词的长度\(sz\)
  2. 暴力枚举区间\([l,r]\),对区间内的编码进行KMP预处理,查询这段编码在总体单词序列中出现的次数\(cnt\)
  3. 那么若是出现次数大于1的状况下,写出对应的单词组替代状况

\[\underbrace{\overbrace{word_1}^{sz_1}\ word_2\ \cdots\ word_n}_{\sum_{i=1}^nsz_i(words)+n-1(space)} \]

能够发现减小了\((\sum_{i=1}^nsz_i)+n-1\),而增长了单词数量所对应的表明字符\(n\),因此总的来讲减小了\((\sum_{i=1}^nsz_i)-1\),对结果统计最小值便可

#include<bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(0);cin.tie(0);
using namespace std;
/*===========================================*/

map<string, int> m;
const int maxn = 305;
int sz[maxn];
int a[maxn];
int nxt[maxn];
int cnt;


int main()
{
	IOS
	int n, res = 0;
	string s;
	cin >> n;
	for (int i = 0; i < n; ++i)
	{
		cin >> s;
		if (!m[s]) m[s] = ++cnt, sz[cnt] = s.size();
		a[i] = m[s];
		res += s.size();
	}
	res += n - 1;
	int total = res;
	for (int lf = 0; lf < n; ++lf)
	{
		int len = 0;
		for (int ri = lf; ri < n; ++ri)
		{
			len += sz[a[ri]];
			nxt[0] = -1;
			int cnt = 0;
			int i = 0, k = -1;
			while (i < ri - lf)
			{
				if (k == -1 || a[lf + i] == a[lf + k])
				{
					if (a[lf + ++i] == a[lf + ++k]) // 当两个字符相等时要跳过
						nxt[i] = nxt[k];
					else
						nxt[i] = k;
				}
				else
					k = nxt[k];
			}
			nxt[ri - lf + 1] = 0;
			i = 0; // 主串的位置
			int j = 0; // 模式串的位置
			while (i < n)
			{
				if (j == -1 || a[i] == a[lf + j])
				{
					++i, ++j;
					if (j == ri - lf + 1)
					{
						++cnt;
						j = nxt[j];
						//操做
					}
				}
				else
					j = nxt[j];
			}
			if (cnt != 1)
				res = min(res, total - cnt * (len - 1));
		}
	}
	cout << res;
}
相关文章
相关标签/搜索