CCPC哈尔滨2019 Exchanging Gifts Gym - 102394E(数学)

outputstandard output
After the dress rehearsal of CCPC Harbin Site 2019, 𝑚 contestants are still in the contest arena. They are taking photos, discussing the problems, and exchanging gifts.

Initially, everyone has exactly one gift in their hand. Note that some contestants may have the same type of gifts. Specifically, the type of the gift in the 𝑖-th contestant’s hand can be represented as a positive integer 𝑔𝑖. Two contestants 𝑖 and 𝑗 (1≤𝑖,𝑗≤𝑚) share the same type of gifts if and only if 𝑔𝑖=𝑔𝑗 holds.

There can be many rounds of gift exchanging between these 𝑚 contestants. In each round, two contestants may exchange their gifts with each other. Note that a pair of contestants can exchange gifts multiple times if they like. In the end, there will still be exactly one gift in each contestant’s hand.

Let’s denote ℎ𝑖 as the type of gift in the 𝑖-th contestant’s hand in the end. If 𝑔𝑖≠ℎ𝑖 holds, the 𝑖-th contestant will be happy, because they have a different type of gift, otherwise they will be unhappy. Your task is to write a program to help them exchange gifts such that the number of happy contestants is maximized. For example, if 𝑔=[3,3,2,1,3] and ℎ=[1,2,3,3,3], there will be 4 happy contestants.

Since 𝑚 can be extremely large, you will be given 𝑛 sequences 𝑠1,𝑠2,…,𝑠𝑛, and the sequence 𝑔 is equal to 𝑠𝑛. The 𝑖-th (1≤𝑖≤𝑛) sequence will be given in one of the following two formats:

“1 k q[1…k]” (1≤𝑘≤106, 1≤𝑞𝑖≤109): It means 𝑠𝑖=[𝑞1,𝑞2,…,𝑞𝑘].
“2 x y” (1≤𝑥,𝑦≤𝑖−1): It means 𝑠𝑖=𝑠𝑥+𝑠𝑦. Here “+” denotes concatenation of sequences, for example [3,3,2]+[2,2,3,3]=[3,3,2,2,2,3,3].
Input
The input contains multiple cases. The first line of the input contains a single integer 𝑇 (1≤𝑇≤10000), the number of cases.

For each case, the first line of the input contains a single integer 𝑛 (1≤𝑛≤106), denoting the number of sequences. Each of the next 𝑛 lines describes a sequence in one of the two formats defined in the problem statement, where the 𝑖-th (1≤𝑖≤𝑛) line describes the sequence 𝑠𝑖.

It is guaranteed that the sum of all 𝑛 over all cases does not exceed 106, and the sum of 𝑘 over all cases does not exceed 106. It is also guaranteed that no sequence has a length that exceeds 1018.

Output
For each case, print a single line containing a single integer denoting the maximum number of happy contestants.

Example
inputCopy
2
1
1 5 3 3 2 1 3
3
1 3 3 3 2
1 4 2 2 3 3
2 1 2
outputCopy
4
6

思路参考自:
https://blog.csdn.net/qq_40695203/article/details/102568790?utm_source=distribute.pc_relevant.none-task

题意:
有很多个序列si。
两个操作给出序列
1 n xxx.代表给一个长度为n的序列
2 x y 代表合并x序列和y序列所成的序列。
最后一个序列sn,重新排列后和原来相同位置不同数字的数目最大是多少

思路:
有两个前置技能点:
1. BZOJ2456: mode(众数)

如何O(n)的且不开数组寻找出现次数大于一半的数

2. hdu1205 吃糖果(鸽巢定理)

n个数字各有一定数量,存在排列且相邻数字不同的条件

本题中也有两个关键点:

  1. 如何确定最后一个数列是什么
  2. 如何求出排列形成新数列不同数的数目
  • 先算第一个点。我们不可能直接用数组存下这个数列。我们可以通过算贡献的形式算出数列的组成:每个数列对最后一个数列的贡献,这个可以用dp的形式计算出来。

  • 第二个点则用到了两个前置技能点。直接说结论:假设总数字数位all,出现次数最多的数字数为ans,次数为sum。假设sum ≤ all / 2,那么一定存在一种方法使得全部位置数字改变,答案为all。否则多出来的ans,其他数字和ans在一起,答案为(all - sum) * 2。

  • 求ans可以O(n)的求出来,方法来自技能点1。

  • 这个问题的形式和放糖果很相似,结论和技能点2的结论也大致相同。关键就在于,如果最大的数字部分不能被填满,那就一定有重复,如果能被填满,那么填完最大部分,剩下部分递归进行下去肯定也行。

  • 下面的证明方法确实复杂了~~。直接按照鸽巢定理,先填数字最大的部分,要是填不满,填上的部分就是答案。要是填的满,对于后面的次大部分按照同样的方式填,要是填的满,递归下去,要是填不满,用最大的那个补上。

  • 证明方法:假设sum > ans / 2,结论是显然的。假设sum ≤ ans / 2。假设原数列分布为数目大的数字在前,小的在后,相同数字在一起。此时将数列分为几个部分 cnt1, cnt2 ,cnt3…cntN. cnt1 ~ cntN-1的长度为sum,cntN的长度 ≤ sum。

  • 引理:大于1个相邻且大小为sum的数字堆可以保证有种方法使得所有位置数字都和原来不同。证明:假设存在cntK,cntK+1,且两者数目均为sum。那么使得两者相同位置数字均改变的放法是, K+1直接顺序放在K上,K顺序放在K+1上,那么相同位置数字相距为sum+1,数字出现最大次数才为sum,所以不可能相同。假设存在cntK,cntK+1,cntK+2,长度均为sum,可行的放法为K+2顺序放在K,上,K顺序放在K+1上,K+1顺序放在K+2上,同上面证明。又所有数字均由2和3组成,证毕。

  • 假设cntN长度为len,那么取cnt1前len个数字和cntN交换即可。最后剩下的全是长度为sum且相邻的堆,由引理,证毕。

  • 因为最终是遍历给出序列的所有数,所以复杂度为 o ( k ) , k ≤ 1 e 6 o(k),k≤1e6 o(k),k1e6

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;

const int maxn = 1e6 + 7;
vector<int>G[maxn];
int n;
int op[maxn][3];
ll num[maxn],all,now,sum;

void IN()//输入
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&op[i][0]);
        num[i] = 0;G[i].clear();
        if(op[i][0] == 1)
        {
            int m;scanf("%d",&m);
            for(int j = 1;j <= m;j++)
            {
                int tmp;scanf("%d",&tmp);
                G[i].push_back(tmp);
            }
        }
        else
        {
            scanf("%d %d",&op[i][1],&op[i][2]);
        }
    }
}

void solve1()//计算每个数列出现了多少次
{
    num[n] = 1;
    for(int i = n;i >= 1;i--)
    {
        if(op[i][0] == 2)
        {
            num[op[i][1]] += num[i];
            num[op[i][2]] += num[i];
        }
    }
}

void solve2()//计算出现次数最多的数的出现次数
{
    sum = 0;now = 0;all = 0;
    for(int i = 1;i <= n;i++)
    {
        if(op[i][0] == 1 && num[i] != 0)
        {
            int len = (int)G[i].size();
            for(int j = 0;j < len;j++)
            {
                int v = G[i][j];
                if(v == now)
                {
                    sum += num[i];
                }
                else sum -= num[i];
                
                if(sum < 0)
                {
                    now = v;
                    sum = -sum;
                }
                all += num[i];
            }
        }
    }
}

void solve3()//计算出现次数最多的数是什么
{
    sum = 0;
    for(int i = 1;i <= n;i++)
    {
        if(op[i][0] == 1 && num[i] != 0)
        {
            int len = (int)G[i].size();
            for(int j = 0;j < len;j++)
            {
                int v = G[i][j];
                if(v == now)
                {
                    sum += num[i];
                }
            }
        }
    }
}

void Print()
{
//    printf("%lld %lld\n",sum,all);
    if(sum > all / 2)printf("%lld\n",(all - sum) * 2);
    else printf("%lld\n",all);
}

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        IN();
        solve1();
        solve2();
        solve3();
        Print();
    }
    return 0;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值