2021牛客多校#10 F-Train Wreck(数学,优先队列)

本文探讨了一种算法问题,如何为一个只有一条死胡同轨道的栈状火车站分配彩色火车,确保每次入栈时栈内颜色序列独特。通过转化为树形结构,利用优先队列策略,博主展示了如何确定颜色分配,最终提供了C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

传送门

题目大意

有一个旧火车站,其只有一个死胡同轨道,可以视为一个栈,可以将火车入栈或者出栈。有 n ( 1 ≤ n ≤ 1 0 6 ) n(1\leq n\leq 10^6) n(1n106)彩色辆火车进站,第 i i i 辆火车的颜色是 k i ( 1 ≤ k i ≤ n ) k_i(1\leq k_i\leq n) ki(1kin),进出栈顺序已定,现在你需要为每次入栈操作分配一辆火车,使得入栈时栈中其他火车颜色序列唯一。

题解

题目的要求就是在每次输入左括号的序列不能与之前的序列相同。
如果用数字表示输入左括号时的长度大小,当前的我们可以将样例 ( ) ( ( ) ) ( ) ()(())() ()(())()变为 1121 1121 1121,可得我们需要保证三个1长度的颜色不相同。
再如 ( ( ) ( ) ( ) ) (()()()) (()()()),为 1222 1222 1222,我们需要保证三个2代表的其中第二个数字不相同,再观察一下图形,我们可以得出括号内相同层次的所代表的颜色不能相同,而之前的颜色是相同的。
你想到了什么?树的序列
在这里插入图片描述

通过举例,我们发现可以将其变为一棵树,要求是某个节点的所有子节点颜色不能相同。
只要颜色数满足所有非叶节点的子节点数,那么它就是可行的。
对于每个子节点,我们只需要优先选择颜色多的染色即可。
总结:
①我们需要建一棵树,表示其括号从属情况;
这里提供一种方法,对于每一个左括号,继续往右,若遇到长度小于等于它的左括号,
就起码遇到了一个右括号,跳出,不然,则是其子节点。
②对于每一个子节点,我们用优先队列选取较大的进行染色;
一起取出 − 1 -1 1再一起放入,
如果染色的数量不够了,则输出“NO“。

参考代码

#include<bits/stdc++.h>
#define FOR(i,n,m) for(int i=n;i<=m;i++)
using namespace std;
void read(int &x)
{
    int ret=0;
    char c=getchar();
    while(!isdigit(c))
        c=getchar();
    while(isdigit(c))
        ret=ret*10+c-'0',c=getchar();
    x=ret;
}
const int N=1e6+5;
struct node{
    int id,num;
}co[N];
priority_queue<pair<int,int>> q;
vector<int> v[N];
int n,t,t1;
int siz1[N],siz2[N],f[N],c[N];
int st[N],tt;
int s1=0;
void dfs(int x)
{
    if(x==n)
        return;
    if(s1==n)
        return;
    FOR(i,s1+1,n)
    {
        if(siz1[i]<=siz1[x])      //不为其子节点
            return;
        s1=i;
        v[x].push_back(i);       //构树
        dfs(i);
        i=s1;                //下一个
    }
}
vector<pair<int,int>> va;
int check(int x)
{
    if(v[x].size()==0)
        return 1;
    int s=0;
    while(s<v[x].size())        //染色(一起放出)
    {
        if(q.empty())
            return 0;
        va.push_back(q.top());       //优先选择栈顶颜色多的
        q.pop();
        va[s].first--;
        if(va[s].first<0)
            return 0;
        c[v[x][s]]=va[s].second;
        s++;
    }
    s=0;
    while(s<v[x].size())          //重新放入
        q.push(va[s++]);
    va.clear();
    FOR(i,0,v[x].size()-1)
        if(check(v[x][i])==0)
            return 0;
    return 1;
}
int main()
{
    read(n);
    FOR(i,1,2*n)
    {
        char c;
        cin>>c;
        if(c=='(')
            siz1[++t]=++t1;
        else
            t1--;
    }
    FOR(i,1,n)
    {
    	co[i].id=i;
    	co[i].num=0;
    }
    FOR(i,1,n)
    {
        if(siz1[i]==1)
        {
            s1=i;
            dfs(i);
            v[0].push_back(i);      //对于长度为1的时候,我们设其父节点为0
            i=s1;                  //下一个
        }
    }
    FOR(i,1,n)
    {
        int x;
        read(x);
        co[x].num++;              //统计颜色数量
    }
//     FOR(i,0,n)
//         for(int j=0;j<v[i].size();j++)
//             cout<<i<<" "<<v[i][j]<<endl;
    FOR(i,1,n)
    	q.push(make_pair(co[i].num,co[i].id));
    if(check(0)==0)
        puts("NO");
    else
    {
        puts("YES");
        FOR(i,1,n)
            printf("%d ",c[i]);
    }
}
### 联合活动 Chocolate 问题解题思路 #### 背景描述 在网的联合活动中,“Chocolate”问题是关于如何合理分配巧克力给朋友。具体来说,目标是通过K次切割将一块由个连续部分组成的巧克力分成K+1份,每一份都包含一些连续的部分。 #### 解决方案概述 为了有效地解决这个问题,可以采用动态规划的方法来寻找最优解法。该方法的核心在于定义状态转移方程以及初始化边界条件[^1]。 #### 动态规划实现细节 - **状态表示**:设`dp[i][j]`代表前i个块中做最j刀所能获得的最大价值。 - **初始条件**:当没有切分时(`j=0`),最大值即为整个区间的总和;对于其他情况,则需遍历所有可能的位置进行尝试。 - **状态转移**:对于每一个新的位置k,在其之前已经完成了一定数量的分割操作(j),此时需要计算从当前位置到起点之间的最小成本,并更新全局最优解。 ```python def max_chocolate_value(chunks, K): n = len(chunks) # 计算区间内的累积和用于快速求子数组之和 prefix_sum = [0] * (n + 1) for i in range(1, n + 1): prefix_sum[i] = prefix_sum[i - 1] + chunks[i - 1] dp = [[float(&#39;-inf&#39;)] * (K + 1) for _ in range(n)] # 初始化第一列 for i in range(n): dp[i][0] = prefix_sum[i + 1] for j in range(K + 1): # 遍历每一刀数目的可能性 for i in range(n): # 当前考虑到第几个chunk为止 if j == 0 or i < j: continue for p in range(i): # 尝试不同的最后一刀位置p cost = abs(prefix_sum[p + 1] - prefix_sum[i + 1]) dp[i][j] = max(dp[i][j], min(dp[p][j - 1], cost)) return dp[-1][-1] ``` 此算法的时间复杂度主要取决于三重循环结构O(N^3*K),其中N为chunks的数量,K为允许的最大切割次数。虽然看起来效率不高但对于题目规模而言是可以接受的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值