SDUT 2023 summer team contest(for 22) - 9补题和总结

发布时间 2023-08-01 21:56:31作者: liuwansi

C - Association for Control Over Minds

题面:
“我今天只能制作其中一些药剂,其余的以后再做。” 你决定道。你按照编号从1到N的顺序逐个考虑你所有的配方。对于每个配方,如果你无法调制这个药剂(下一段解释),你跳过这个配方,考虑下一个,如果有的话。否则,即使这可能导致一些接下来的药剂无法调制,你也会调制这个配方。因此,是否调制一个药剂并不是一个选择,它只取决于你考虑药剂时是否可能进行调制。

为了调制一个药剂,首先你准备一个新的空锅(你从杂货店里弄到了无限多的锅)。然后,在这个锅中,你只把所需的所有成分放进去,不能有任何不在配方中列出的成分(也就是说,锅里不能含有任何未在配方中列出的成分)。对于那些之前你决定调制的药剂中没有使用过的成分,你可以简单地将其放入这个锅中。你还可以将之前用过的锅中的所有内容倒入这个锅中,以便混合其中的所有成分(由于你倒入了锅中的全部内容,这个旧的调制物将无法再用于下一次调制)。最后,你用扫帚搅拌这个锅,并取出一瓶混合物稍后用于测试你的手下。剩下的混合物仍然留在这个锅中,可以稍后倒入另一个锅中。

“今天你要调制多少个配方?” 你问自己。

输入
第一行包含一个非负整数2≤N≤200,000,表示你拥有的总配方数。接下来是N行,第i行描述第i个配方。每行是一个以空格分隔的整数列表。每行以一个整数1≤M开头,表示制作该配方所需的原料数量。然后,接下来是M个整数,描述所需的原料。原料通过0到500,000之间的整数进行标识,不同的整数代表不同的原料。对于每个配方,所有的原料都是不同的。所有配方中所需原料数量的总和不会超过500,000。

输出
打印出你将要调制的配方的数量。

样例数据说明
在第一个示例中,第一个药剂可以调制,因为两种原料都没有被使用过。因此,你将调制这个药剂。由于同样的原因,第二个药剂也可以调制。第三个药剂无法调制,因为原料1已经不存在,并且与另一种不在配方中的成分混合在一起。通过倒入用于第一次和第二次调制的锅中的内容,并添加到目前未使用过的原料5,第四个药剂可以调制。最后一个药剂无法调制,因为第一次调制的锅中的内容已经全部倒入了第四个药剂中,现在混合了其他不在配方中的成分。

对于第二个示例,由于第一个药剂可以调制,它必须被调制。因此,第二个和第三个药剂无法再被调制。

题意:利用并查集把每次完成的材料存到这次用到的编号最小的材料那里,并记录好以这个材料开头的并查集的集合元素个数。转移的时候把个数转移过去就行.

对于每一个东西,先利用并查集把他们变成他们所处集合的集合首,去掉重复元素,然后再加和各个集合元素个数,如果结果恰好是材料个数,就能够完成这个东西

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

#define N 500002

int fa[N],num[N];

int findfa(int x) {
    return fa[x]==x?x:findfa(fa[x]);
    
}

int main()
{
    int n,m,ans;
    while(cin>>n)
    {
        ans=0;
        for(int i=0;i<=N;i++) num[i]=1,fa[i]=i;
        while(n--)
        {
            scanf("%d",&m);
            vector<int> a(m,0);
            int sum=0;
            for(int i=0;i<m;i++)
            {
                scanf("%d",&a[i]);
                a[i]=findfa(a[i]+1);
            }
            sort(a.begin(),a.end());
            a.erase(unique(a.begin(),a.end()),a.end());
            for(int i=0;i<a.size();i++)
                sum+=num[a[i]];
            if (sum==m)
            {
                ans++;
                int x=a[0],y;
                for(int i=1;i<a.size();i++)
                {
                    y=a[i];
                    fa[y]=x;
                    num[x]+=num[y];
                    num[y]=0;
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

E - Association for Computing Machinery

E题这个情景很符合现实,我的队友在看完这个题面之后才第一次理解了icpc赛制的排名方式,首先给出n和p,分别代表题目数量和从第几道题开始做,接下来一行n个数据是n道题目所用时间最后输出做出来的题目总数和总罚时

#include<bits/stdc++.h>
using namespace std;
#define int long long 
void liuwansi()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
}
signed main()
{
    liuwansi;
    int n,p;
    while(cin>>n>>p)
    {
        int a[n];
        int tm=0,cnt=0;
        for(int i=0;i<n;i++)cin>>a[i];
        if(a[p]<=300)
        {
        swap(a[0],a[p]);
        sort(a+1,a+n);
        for(int i=1;i<n;i++)a[i]+=a[i-1];
        for(int i=0;i<n;i++)
        {
            if(a[i]<=300)
            {
                tm+=a[i];
                cnt++;
            }else if(a[i]>300)break;
        }
        }
        cout<<cnt<<' '<<tm<<endl;
    }
}

G - Association for the Country of Mububa

一道有些困难的dp题,一开始伪装成了贪心题的模样,具体的题目题意是有n个数,要按顺序将其分成m组,后一组数据之和要大于等于前一组数据之和,m要尽可能的大;
一开始我会考虑使用贪心来进行计算比如这样的代码:

#include<bits/stdc++.h>
#include<stdio.h>
#include<string.h>
#include<cstdlib>
#include<iostream>
using namespace std;
int x,y,z,i,j,sum;
long long int a[5000],m,n;
int main()
{
    scanf("%d",&x);
    for(z=1; z<=x; z++)
        scanf("%lld",&a[z]);
    m=a[1];
    n=0;
    sum=1;
    for(z=2; z<=x; z++)
    {
        n+=a[z];
        if(m<=n)
        {
            sum++;
            m=n;
            n=0;
        }
    }
    cout<<sum<<endl;
    return 0;
}

但是这时候贪心法的弊端就显现出来了,比如

5
3 3 1 4 4

会输出3,即:3 3 1+4 4(没用舍去)
但实际上应该是 3 3+1 4 4 (共4个)
所以应该写一个dp:

#include<bits/stdc++.h>
using namespace std;
#define int long long

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int T=1;
    
    while(T--)
    {
        int n;
        cin>>n;
        vector<int> a(n+1),s(n+1),dp(n+1),b(n+1);
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            s[i]=s[i-1]+a[i];
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=i-1;j>=0;j--)
            {
                if(s[i]-s[j]>=b[j])//b表示i结尾的段的最小情况
                {
                    //对于每个i从大到小找到第一个j,使得s[j+1 ~ i]
                    //恰好大于b[j]
                    //此时b[i]就是s(j+1~i)dp[i]=dp[j]+1
                    b[i]=s[i]-s[j];
                    dp[i]=dp[j]+1;
                    break;
                }
            }
        }
        cout<<dp[n]<<endl;
    }
    return 0;
}