蓝桥杯15届省赛题目反思

五子棋对弈

题意:5*5棋盘下五子棋,平局的情况数
解法:直接搜索然后计数

训练士兵

题意:n名士兵,编号1-n,每名士兵需要训练ci次,士兵训练的费用为pi,除了单独训练士兵还可以用费用S训练全体士兵,求最小训练花费
思路:士兵按照训练次数的降序排序,然后计算前缀和,找到第一个前缀和小于S的位置k,那么就有
t o t a l C o s t = S ⋅ c k + 1 + ∑ i = 1 k p i ( c i − c k + 1 ) totalCost = S\cdot c_{k + 1}+\sum_{i=1}^k p_i(c_i - c_{k + 1}) totalCost=Sck+1+i=1kpi(cick+1)
代码:

#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 1e5 + 11;
struct Soder
{
    int price, cycle;
    bool operator<(const Soder &a) const
    {
        return cycle > a.cycle;
    }
} soder[N];
ll presum[N]; // 前缀和
ll ans;
int main()
{
    int n;
    ll S;
    int cycle[N];
    scanf("%d%ld", &n, &S);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &soder[i].price, &soder[i].cycle);
    sort(soder + 1, soder + n + 1);
    for (int i = 1; i <= n; i++)
        presum[i] = presum[i - 1] + soder[i].price;
    int idx = lower_bound(presum + 1, presum + n + 1, S) - presum;
    ans = S*soder[idx].cycle;
    for (int i = 1; i < idx; i++)
        ans += ((ll)soder[i].price)*(soder[i].cycle - soder[idx].cycle);
    printf("%lld\n", ans);
    return 0;
}

团建

题意:给定两棵树,大小分别为n和m,每个树上的节点都有一个权值(第一棵树ai,第二棵树bi),然后从两棵树的1号节点出发求最长的公共前缀。(特殊约束:每个节点的叶子节点的权重各不相同)
思路:用两个有限队列存储树的节点,优先队列排序第一关键字是节点深度,第二关键字是节点权值。每次比对两个优先队列的队头,若相等(权重、深度都相同),则更新答案,并拓展两个节点的儿子入队;若不相等弹出深度较小的节点,若深度一样则弹出权重较小的节点。
代码:

#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 2e5 + 11;
int n, m;
int pVal[N][2];
int edgeCnt, head[N][2],fa[N][2];
int ans;
struct Edge
{
    int pre, to;
    Edge(int a = 0, int b = 0) : pre(a), to(b) {}
} edge[N << 2];
struct Node
{
    int depth, val, idx;
    Node(int a = 0, int b = 0, int c = 0) : depth(a), val(b), idx(c) {}
    bool operator<(const Node &a) const
    {
        if (depth != a.depth)
            return depth > a.depth;
        return val > a.val;
    }
    bool operator!=(const Node &a) const
    {
        return depth != a.depth || val != a.val;
    }
};
void addedge(int a, int b, int idx)
{
    edge[++edgeCnt] = (Edge){head[a][idx], b};
    head[a][idx] = edgeCnt;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &pVal[i][0]);
    for (int i = 1; i <= m; i++)
        scanf("%d", &pVal[i][1]);
    for (int i = 1; i < n; ++i)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        addedge(a, b, 0);
        addedge(b, a, 0);
    }
    for (int i = 1; i < m; ++i)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        addedge(a, b, 1);
        addedge(b, a, 1);
    }
    priority_queue<Node> q[2];
    for (int i = 0; i < 2; i++)
    {
        while (!q[i].empty())
            q[i].pop(); // 初始化
        q[i].push(Node(1, pVal[1][i],1));
    }
    while (!q[0].empty() && !q[1].empty())
    {
        Node a = q[0].top(), b = q[1].top();
        if (a != b)
        {
            if (a < b)
                q[1].pop();
            else
                q[0].pop();
            continue;
        }
        q[1].pop();
        q[0].pop();
        ans = a.depth;
        for(int i = head[a.idx][0]; i; i = edge[i].pre)
        {
            int v = edge[i].to;
            if(v != fa[a.idx][0]){
                q[0].push(Node(a.depth + 1, pVal[v][0], v));
                fa[v][0] = a.idx;
            }
        }
        for(int i = head[b.idx][1]; i; i = edge[i].pre)
        {
            int v = edge[i].to;
            if(v != fa[b.idx][1]){
                q[1].push(Node(b.depth + 1, pVal[v][1], v));
                fa[v][1] = b.idx;
            }
        }
    }
    printf("%d\n", ans);
}

成绩统计

题意:给定n个正整数,在给定一个正整数k和T,前x个数中能否挑出k个数使得这k个数方差小于T,若能则输出最小的x。
思路:二分答案,可行的x是单调的,故而二分枚举x。判断x是否可解的方法:将1~x个数排序,然后以1为起点,窗口大小为k,滑动统计窗口内的方差。解的判断的复杂度是O(nlogn),二分的复杂度是(logn)故而最终复杂度是 O ( n l o g 2 ( n ) ) O(nlog^2(n)) O(nlog2(n))
注意数据范围(爆int了)

#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 1e5 + 11;
ll n, k, T;
ll val[N];
ll nowVal[N];
ll ans;
bool isLegal(int s)
{
    bool flag = false;
    if (s < k)
        return flag;
    memcpy(nowVal, val, sizeof(ll) * (s + 1));
    sort(nowVal + 1, nowVal + s + 1);
    ll sum = 0, prodsum = 0;
    for (int i = 1; i <= s; ++i)
    {
        sum += nowVal[i];
        prodsum += nowVal[i] * nowVal[i];
        if (i > k)
        {
            sum -= nowVal[i - k];
            prodsum -= nowVal[i - k] * nowVal[i - k];
        }
        if (i >= k)
        {
            double sigmaProd = (double)prodsum / k - (double)sum * sum / k / k;
            if (sigmaProd <= T)
            {
                flag = true;
                break;
            }
        }
    }
    return flag;
}
int solve(int l, int r)
{
    if (l == r)
        return isLegal(l) ? l : -1;
    int mid = (l + r) >> 1;
    if (isLegal(mid))
        return solve(l, mid);
    else
        return solve(mid + 1, r);
}
int main()
{    
//    freopen("data.in","r",stdin);
    scanf("%ld%ld%ld", &n, &k, &T);
    for (int i = 1; i <= n; i++)
        scanf("%ld", &val[i]);
    ans = solve(k, n);
    printf("%ld\n", ans);
    return 0;
}

因数统计

题意:给定n个正整数的序列 { a i } n \{a_i\}_n {ai}n,给出合法的四元组 ( i , j , k , l ) (i,j,k,l) (i,j,k,l),其中 i , j , k , l i,j,k,l i,j,k,l各不相同。四元组在 a k = 0 ( m o d   a i ) , a l = 0 ( m o d   a j ) a_k =0(mod\ a_i),a_l=0 (mod\ a_j) ak=0(mod ai),al=0(mod aj)时为合法。
思路:
先考虑统计合法的因子对数即 ( i , k ) (i,k) (i,k)对数,即满足 a k = 0 ( m o d   a i ) a_k =0(mod\ a_i) ak=0(mod ai)的对数。注意到数据范围时1e5,故而采用遍历每个数倍数的方法可以统计得到合法 ( i , k ) (i,k) (i,k)对数。
我们记合法对数的集合为 A = { ( i , k ) ∣ a k = 0 ( m o d   a i ) } A=\{(i,k)|a_k =0(mod\ a_i)\} A={(i,k)ak=0(mod ai)},记 A ′ = { ( j , l ) ∣ a l = 0 ( m o d   a j ) } A'=\{(j,l)|a_l =0(mod\ a_j)\} A={(j,l)al=0(mod aj)}。显然 A = A ′ A=A' A=A,两者做直积的 B = { ( i , j , k , l ) ∣ a k = 0 ( m o d   a i ) , a l = 0 ( m o d   a j ) } B=\{(i,j,k,l)|a_k =0(mod\ a_i),a_l =0(mod\ a_j)\} B={(i,j,k,l)ak=0(mod ai),al=0(mod aj)}。显然集合B中的元素都满足因数的条件,但是不满足 i , j , k , l i,j,k,l i,j,k,l各不相同的条件,故而可以在集合B的基础上利用容斥原理计算满足条件的四元组数量。记 B ( i , k ) = { ( i , j , k , l ) ∈ B ∣ i = k } B_{(i,k)}=\{(i,j,k,l)\in B|i=k\} B(i,k)={(i,j,k,l)Bi=k},同理有 B ( i , l ) B_{(i,l)} B(i,l), B ( j , k ) B_{(j,k)} B(j,k), B ( j , l ) B_{(j,l)} B(j,l),进一步记 B ( i , k ) , ( j , l ) = { ( i , j , k , l ) ∈ B ∣ i = k , j = l } B_{(i,k),(j,l)}=\{(i,j,k,l)\in B|i=k,j=l\} B(i,k),(j,l)={(i,j,k,l)Bi=k,j=l},同理有 B ( i , l ) , ( j , k ) B_{(i,l),(j,k)} B(i,l),(j,k) B ( i , k ) , ( j , k ) B_{(i,k),(j,k)} B(i,k),(j,k), B ( i , l ) , ( j , l ) B_{(i,l),(j,l)} B(i,l),(j,l)为空集)
故而最终的集合为 c a r d ( B ′ ) = c a r d ( B ) − c a r d ( B ( i , k ) ) − c a r d ( B ( i , l ) ) − c a r d ( B ( j , k ) ) − c a r d ( B ( j , l ) ) + c a r d ( B ( i , k ) , ( j , l ) ) + c a r d ( B ( i , l ) , ( j , k ) ) card(B')=card(B)-card(B_{(i,k)})-card(B_{(i,l)})-card(B_{(j,k)})-card(B_{(j,l)})+card(B_{(i,k),(j,l)})+card(B_{(i,l),(j,k)}) card(B)=card(B)card(B(i,k))card(B(i,l))card(B(j,k))card(B(j,l))+card(B(i,k),(j,l))+card(B(i,l),(j,k))
所以解题的关键在于 B ( i , k ) B_{(i,k)} B(i,k) B ( i , l ) B_{(i,l)} B(i,l), B ( j , k ) B_{(j,k)} B(j,k), B ( j , l ) B_{(j,l)} B(j,l), B ( i , k ) , ( j , l ) B_{(i,k),(j,l)} B(i,k),(j,l)以及 B ( i , l ) , ( j , k ) B_{(i,l),(j,k)} B(i,l),(j,k)集合元素数量的统计。
下面介绍 B ( i , k ) B_{(i,k)} B(i,k) B ( i , l ) B_{(i,l)} B(i,l)元素个数的统计, B ( j , k ) B_{(j,k)} B(j,k), B ( j , l ) B_{(j,l)} B(j,l)的统计类似。
我们考虑一下计数的顺序, B ( i , k ) B_{(i,k)} B(i,k)所涉及到的四元组可以表示为 ( i , j , i , l ) (i,j,i,l) (i,j,i,l),故而我们通过枚举 i i i,计算每一个 i i i对应的 ( j , l ) (j,l) (j,l)的个数即可,显然就是 a i a_i ai倍数个数的平方,记为 n i , ( i , k ) n_{i,(i,k)} ni,(i,k)
我们做一些记号: m l t i mlt_i mlti表示序列中 a i a_i ai倍数的个数, f a c t o r i factor_i factori表示 a i a_i ai因数的个数。(这两个量在统计 B B B的时候可以一起统计得到)
故而 n i , ( i , k ) = m l t i ∗ m l t i n_{i,(i,k)}=mlt_i*mlt_i ni,(i,k)=mltimlti c a r d ( B ( i , k ) ) = ∑ j = 1 n n j , ( i , k ) card(B_{(i,k)})=\sum\limits_{j=1}^{n}n_{j,(i,k)} card(B(i,k))=j=1nnj,(i,k)
B ( i , l ) B_{(i,l)} B(i,l)涉及到的四元组可以表示为 ( i , j , k , i ) (i,j,k,i) (i,j,k,i),同样的枚举 i i i,计算对应的 ( j , k ) (j,k) (j,k)数量即可,即有 n i , ( i , l ) = m l t i ∗ f a c t o r i n_{i,(i,l)}=mlt_i*factor_i ni,(i,l)=mltifactori c a r d ( B ( i , l ) ) = ∑ j = 1 n n j , ( i , l ) card(B_{(i,l)})=\sum\limits_{j=1}^{n}n_{j,(i,l)} card(B(i,l))=j=1nnj,(i,l)
接下来是 B ( i , k ) , ( j , l ) B_{(i,k),(j,l)} B(i,k),(j,l),显然有 c a r d ( B ( i , k ) , ( j , l ) ) = c a r d ( A ) card(B_{(i,k),(j,l)})=card(A) card(B(i,k),(j,l))=card(A),这个在统计 B B B时就可以得出。
最后时 B ( i , l ) , ( j , k ) B_{(i,l),(j,k)} B(i,l),(j,k),此时显然有 a i = a j a_i=a_j ai=aj,故而就是在相同的值的元素中选两个的排列,统计给定序列中每个不同的值出现的次数记为 b i b_i bi,总共有N个不同的值,则 c a r d ( B ( i , l ) , ( j , k ) ) = ∑ i = 1 N ( b i − 1 ) ∗ b i card(B_{(i,l),(j,k)})=\sum\limits_{i=1}^{N}(b_i-1)*b_i card(B(i,l),(j,k))=i=1N(bi1)bi
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<set>
#define ll long long
using namespace std;
const int N=1e5+11;
int n;
int val[N];
ll valcnt[N],fctcnt[N],multcnt[N];
ll twocnt,ans;
int read(){
    int num=0,flag=1;char s=getchar();
    for(;(s>'9'||s<'0')&&s!='-';s=getchar());
    if(s=='-') flag=-1,s=getchar();
    for(;s>='0'&&s<='9';s=getchar()) num=num*10+s-'0';
    return num*flag;
}
int main(){
  // freopen("19706.in","r",stdin);
//  freopen(".out","w",stdout);
    n = read();
    int maxval = 1;
    int minval = N;
    for(int i = 1; i <= n; ++i){
        val[i] = read();
        maxval = max(maxval,val[i]);
        minval = min(minval,val[i]);
        valcnt[val[i]]++;
    }
    for(int i = minval; i <= maxval; ++i){
        if(valcnt[i]==0)
            continue;
        fctcnt[i] += valcnt[i] - 1;
        multcnt[i] += valcnt[i] - 1;
        for(int j = 2*i; j <= maxval; j+=i){
            fctcnt[j] += valcnt[i];
            multcnt[i] += valcnt[j];
        }
    }
    for(int i = minval; i <= maxval; ++i){
        twocnt += fctcnt[i]*valcnt[i];
        ans = ans - valcnt[i]*multcnt[i]*multcnt[i];
        ans = ans - valcnt[i]*multcnt[i]*fctcnt[i]*2;
        ans = ans - valcnt[i]*fctcnt[i]*fctcnt[i];
        ans = ans + valcnt[i]*(valcnt[i]-1);
    }
    ans += twocnt * twocnt ;
    ans += twocnt;
    printf("%lld",ans);
}

封印宝石

题意:将n个宝石放入n个箱子中,每个宝石有一个权重,将宝石放入箱子里需要消耗体力,例如将第i个宝石放入第j个箱子中需要消耗体力 i − j , i ≥ j i-j,i\ge j ij,ij,初始时有体力k。同时需要约束相邻的箱子不能放入相同权重的宝石,求最后箱子中宝石的最大字典序。
思路:对于第i个箱子而言就是查询[i,i+k_left]区间中可用宝石的最大值,如果前一个箱子中的值和查询到的最大值相等,则需要考虑查询次大值(权重与最大值不同),同时需要注意当两个宝石权重相同时,位置靠前的更优。
代码如下:

#include<bits/stdc++.h>
#define ls s<<1
#define rs s<<1|1
using namespace std; 
const int N = 1e5 + 11;
int n,k;
int val[N];
int ans[N];
struct Node{
	int id,val;
	Node():id(0),val(-1){} 
	Node(int _id, int _val):id(_id),val(_val){}
	bool operator <(const Node& a)const{
		return val == a.val ? id < a.id : val > a.val;
	}
};
struct Tree{
	int l,r;
	Node mx1,mx2;
}tree[N<<2];
int read(){
	char s = getchar(); int flag = 1, now = 0;
	while(s != '-' && (s > '9' || s < '0')) s = getchar();
	if(s == '-') {flag = -1; s = getchar();}
	while(s >= '0' && s <= '9'){
		now = now * 10 + s - '0';
		s = getchar();
	}
	return now * flag;
}
void update(int s){
	Node tmp[4];
	tmp[0] = tree[ls].mx1;
	tmp[1] = tree[ls].mx2;
	tmp[2] = tree[rs].mx1;
	tmp[3] = tree[rs].mx2;
	sort(tmp, tmp + 4);
	tree[s].mx1 = tmp[0];
	for(int i = 1; i < 4; ++i)
		if(tmp[i].val != tmp[0].val){
			tree[s].mx2 = tmp[i];
			break;
		}
}
void build(int s, int l, int r){
	tree[s].l = l; tree[s].r = r;
	if(l == r){
		tree[s].mx1 = Node(l,val[l]);
		tree[s].mx2 = Node();
		return;
	}
	int mid = l + r >> 1;
	build(ls, l, mid);
	build(rs, mid+1, r);
	update(s);
}
Tree findmax(int s, int l, int r){
	if(l <= tree[s].l && tree[s].r <= r)
		return tree[s];
	Tree lmax, rmax, now;
	int mid = tree[s].l + tree[s].r >> 1;
	if(l <= mid)
		lmax = findmax(ls, l, r);
	if(r > mid)
		rmax = findmax(rs, l, r);
	Node tmp[4];
	tmp[0] = lmax.mx1;
	tmp[1] = lmax.mx2;
	tmp[2] = rmax.mx1;
	tmp[3] = rmax.mx2;
	sort(tmp, tmp + 4);
	now.mx1 = tmp[0];
	for(int i = 1; i < 4; ++i)
		if(tmp[i].val != tmp[0].val){
			now.mx2 = tmp[i];
			break;
		}
	return now;
}
void changeVal(int s, int id, int val){
	if(tree[s].l == tree[s].r && tree[s].l == id){
		tree[s].mx1 = Node(id, -1);
		return;
	}
	int mid = tree[s].l + tree[s].r >> 1;
	if(id <= mid)
		changeVal(ls, id, val);
	else
		changeVal(rs, id, val);
	update(s);
}
int main(){
	n = read(); k = read();
	for(int i = 1; i <= n; ++i){
		val[i] = read();
	}
	build(1,1,n);
	for(int i = 1; i <= n; ++i){
		int l = i, r = min(n, i + k);
		Tree node = findmax(1,l,r);
		Node now;
		if(node.mx1.val != ans[i-1])
			now = node.mx1;
		else
			now = node.mx2;
		ans[i] = now.val;
		if(ans[i] != -1) {
			k -= now.id - i;
			changeVal(1,now.id,-1);
		}
		printf("%d ",ans[i]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值