智算之道初赛第三场

智算之道初赛第三场解析
在这里插入图片描述

解析:

对于 100% 的数据,直接按照题意模拟即可。

代码:

int main()
{
	int n,L,A,B;
	int opt,l=0,a=0,i;
	scanf("%d %d %d %d",&n,&L,&A,&B);
	for(i=0;i<n;i++)
	{
		scanf("%d",&opt);
		if(opt==1)
		{
			scanf("%d",&a);
		}
		else if(opt==2)
		{
			scanf("%d",&l);
		}
		else if(opt==3)
		{
			if(l>=L&&a>=A&&a<=B)
				printf("%d\n",a);
			else
				printf("GG\n");
		}
	}
}

在这里插入图片描述

解析:

对于 50% 的数据,直接开数组储存所有手牌,每次暴力遍历整个数组找到第一个相同的删 除,模拟即可。

对于 100% 将手牌放进队列,开桶记录每种颜色的第一个位置,每插入一个就找第一个位置 删除,在那个位置打上标记,每次从队头弹出打了标记的元素,模拟即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+7;
int A[maxn],B[maxn];
int qA[maxn<<5],qB[maxn<<5];
int lA,lB,rA,rB;
int cntA,cntB,cnt;
int ans,n;
pair<int,int> Tmp[maxn];
int main()
{
    int i,x;
	scanf("%d",&n);
    for(i=0;i<4*n-2;i++)
	{
        scanf("%d",&x);
        if(i&1)
		{
            if(A[x])
			{
                cntA--;
                A[x] = 0;
            }
			else
			{
                A[x] = i+1;
                cntA++;
            }
        }
		else
		{
            if(B[x])
			{
                cntB--;
                B[x] = 0;
            }
			else
			{
                B[x] = i+1;
                cntB++;
            }
        }
    }
    int tot = 0;
    for(i=1;i<=n;i++)
        if(A[i])
            Tmp[++tot] = make_pair(A[i],i);
    sort(Tmp+1,Tmp+1+tot);
    for(i=0;i<tot;i++)
        qA[i] = Tmp[i-1].second;
    rA = tot-1;
    tot = 0;
    for(i=1;i<=n;i++)
        if(B[i])
            Tmp[++tot] = make_pair(B[i],i);

    sort(Tmp+1,Tmp+1+tot);
    for(i=0;i<tot;i++)
        qB[i] = Tmp[i].second;
    rB = tot-1;
    while(cntA&&cntB)
	{
        cnt++;
        if(cnt&1)
		{
            while(!A[qA[lA]]&&lA<=rA)
                lA++;
            --cntA;
            A[qA[lA]] = 0;
            if(B[qA[lA]])
			{
                --cntB;
                B[qA[lA]] = 0;
            }
			else
			{
                ++cntB;
                B[qA[lA]] = 1;
                qB[++rB] = qA[lA];
                lA++;
            }
        }
		else
		{
            while(!B[qB[lB]]&&lB<=rB)
                lB++;
            --cntB;
            B[qB[lB]] = 0;
            if(A[qB[lB]])
			{
                --cntA;
                A[qB[lB]] = 0;
            }
			else
			{
                ++cntA;
                A[qB[lB]] = 1;
                qA[++rA] = qB[lB];
                lB++;
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

在这里插入图片描述

解析:

对于 10% 的数据,爆搜顺序按照规则算代价即可。对于另外 40% 的数据,发现顺序就是链的顺序,直接按照规则算代价即可。先按照子树大小排序,然后跑一遍 DFS 序就是最优序列的一种,证明如下:

1.先尝试证明 DFS 序一定最优,对于以同一深度的节点为根的子树,在一棵中找一个叶 子节点 j ,在另一棵里找一个根节点 i (当前序列中 j 所在的子树的根在 i 之前出现)。
2.假设 j < i 的时候代价最小,尝试将 j 放在 i 的后面,此时 i < j。那么我们发现如果 j 放在 i 与 i 的孩子们之间,i 的孩子们和 i 的距离都会加 1,所以总代价会增加,而 j 和 j 的父亲的距离一定也会增加,所以总代价也会增加。
所以对于 j 所在子树的根比 i 在序列中出现的早的情况,j < i 的情况是最优的。
3.那么每个子树形成了一段连续的区间,对于每个节点进行调整,最后我们发现形成了一个DFS 序(有时别的序列也会和 DFS 序列一样优秀,但是此处证明的是dfs序列是最优解之一,不是唯一解。)
4.接下来再证明先遍历子树小的更优秀。
5.所有的 size 排序之后,第 i 个根节点的贡献是前面所有树的 size 之和,所以要排序。综上所述,当我们进行 DFS 序且按子树大小排序时,求出代价即为最优
代码:

#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 1e6 + 5;
int cnt[maxn];
vector<int> v[maxn], p;
// 求子树结点的个数
void dfs(int u)
{
    int i,t,e;
	cnt[u]=1;
    t=v[u].size();
    for (i=0;i<t; i++)
    {
        e=v[u][i];
        dfs(e);
        cnt[u]+=cnt[e];
    }
}
bool cmp(int a,int b)
{
	return cnt[a]<cnt[b];
}
int main()
{

    int n,m,x,i,j,t;
    long long temp,ans;
    scanf("%d %d",&n,&m);
    for (i=2;i<=n;i++)
    {
        scanf("%d",&x);
        v[x].push_back(i);
    }
    dfs(1);     //求出每棵子树结点的个数 cnt[]
	ans=1; //根节点1的贡献点是1
    // 分别对 每个结点作为根节点时 计算它的子节点的贡献值
    for (i=1;i<=n;i++)
    {
        p.clear();
        t=v[i].size();
        for (j=0;j<t;j++)
            p.push_back(v[i][j]);
        // 把结点i的所有儿子结点按照子树结点个数从小到大排序
        sort(p.begin(),p.end(),cmp);
        temp=1;
        // 计算结点i的子节点到i的贡献点
        t=p.size();
        for (j=0;j<t;j++)
        {
            x=p[j];
            ans+=temp;
            temp+=cnt[x];
        }
    }
    printf("%lld\n",ans*m);
    return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值