SDUT 2023 summer team contest(for 22) - 9补题和总结
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;
}