A - Subscribers
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,a,b;
cin>>n>>a>>b;
cout<<min(a,b)<<' '<<max(a+b-n,0)<<endl;
}
B Touitsu
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,ans=0;
string a,b,c;
cin>>n>>a>>b>>c;
for(int i=0;i<n;i++)
{
if(a[i]!=b[i]&&b[i]!=c[i]&&a[i]!=c[i]) ans+=2;
else if(a[i]==b[i]&&b[i]==c[i]) ans+=0;
else ans+=1;
}
cout<<ans<<endl;
}
C - Different Strokes
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
ll x,y;
int id;
}a[100005],b[100005];
bool cmp1(node p1,node p2)
{
return p1.x-p2.y>p2.x-p1.y;
}
bool cmp2(node p1,node p2)
{
return p1.y-p2.x>p2.y-p1.x;
}
bool vis[100005];
int main()
{
int n;
ll ans1=0,ans2=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y),a[i].id=i,b[i]=a[i];
sort(a+1,a+1+n,cmp1);
sort(b+1,b+1+n,cmp2);
int i=1,j=1;
bool flag=true;
while(n--)
{
if(flag)
{
while(vis[a[i].id]) i++;
ans1+=a[i].x;
vis[a[i].id]=true;
i++;
}
else
{
while(vis[b[j].id]) j++;
ans2+=b[j].y;
vis[b[j].id]=true;
j++;
}
flag=!flag;
}
printf("%lld\n",ans1-ans2);
}
D Restore the Tree
典型的拓扑排序
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,p[100005];
vector<int>v[100005];
int vv[100005];
void bfs(int x)
{
queue<int>q;
q.push(x);
while(!q.empty())
{
int now=q.front();q.pop();
for(int i=0;i<v[now].size();i++)
{
vv[v[now][i]]--;
p[v[now][i]]=now;
if(vv[v[now][i]]==0) q.push(v[now][i]);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n-1+m;i++)
{
scanf("%d%d",&x,&y);
v[x].push_back(y);
vv[y]++;
}
for(int i=1;i<=n;i++)
if(vv[i]==0) {bfs(i);break;}
for(int i=1;i<=n;i++) printf("%d\n",p[i]);
}
E Weights on Vertices and Edges
可以这样考虑,给出一个图,这个图一开始是一个连通块
我们重复执行以下操作:把每个连通块中边权大于连通块中点权的和边删除
直到不能再删为止,就得到了最优答案
但是直接删边比较困难,考虑把边按权值排序,用带撤销的并查集生成一颗最小生成树
那么,我们只有删除最小生成树中的边就行了,因为最小生成树中的边不满足,其它边权大的就更不可能满足
因为我们的并查集是带撤销的,所以可以每次删边很快计算出每个连通块的权值
注意,并查集不能路径压缩,否则无法撤销,在连接的时候使用启发式合并,可以保证时间复杂度
还有一种方法就是并查集反着做,正着做的时候每个连通块中记录不符合条件的边数
如果并查集合并的时候连通块满足条件了,把不符合的边数置0即可。
//带撤销的并查集
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
struct edge
{
int u,v,w;
bool operator<(const edge&o)const
{
return w<o.w;
}
}e[N];
int n,m,a[N];
stack<int>f[N];
int vis[N],dep[N];
ll sum[N];
int getf(int x){return f[x].top()==x?x:getf(f[x].top());}
void del(int x,ll s)
{
sum[x]-=s;
if(f[x].top()!=x) del(f[x].top(),s);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) f[i].push(i),sum[i]=a[i];
for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
sort(e+1,e+1+m);
for(int i=1;i<=m;i++)
{
int fu=getf(e[i].u),fv=getf(e[i].v);
if(fu==fv) continue;
if(dep[fu]<dep[fv])
{
f[fu].push(fv);
sum[fv]+=sum[fu];
vis[i]=fu;
}
else
{
f[fv].push(fu);
sum[fu]+=sum[fv];
dep[fu]=max(dep[fv]+1,dep[fu]);
vis[i]=fv;
}
}
for(int i=m;i>=1;i--)
if(vis[i])
{
int fu=getf(e[i].u);
if(e[i].w>sum[fu])
{
del(f[vis[i]].top(),sum[vis[i]]);
f[vis[i]].pop();
}
}
int ans=0;
for(int i=1;i<=m;i++)
{
int fu=getf(e[i].u),fv=getf(e[i].v);
if(fu!=fv) continue;
if(e[i].w<=sum[fu]) ans++;
}
printf("%d\n",m-ans);
}
//并查集反着做
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
const int N=2e5+5;
struct edge
{
int u,v,w;
bool operator<(const edge&o)const
{
return w<o.w;
}
}e[N];
int n,m,a[N],f[N];
ll sum[N],p[N];
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) f[i]=i,sum[i]=a[i];
for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
sort(e+1,e+1+m);
for(int i=1;i<=m;i++)
{
int fu=getf(e[i].u),fv=getf(e[i].v);
if(fu!=fv)
{
f[fu]=fv;
sum[fv]+=sum[fu];
p[fv]+=p[fu];
}
p[fv]++;
if(e[i].w<=sum[fv]) p[fv]=0;
}
int ans=0;
for(int i=1;i<=n;i++)
if(getf(i)==i) ans+=p[i];
printf("%d\n",ans);
}
F Jewels
很容易想到,如果某种颜色的珍珠要放入选中的集合,那么先把它最大的两个放入是最优的
考虑每种颜色权值最大的两个珍珠要同时放入才有效,我们可以把这两个珍珠的权值都变成它们的平均值
它们两个的平均值仍然满足是同种颜色中最大的,这不影响我们求解
接下来我们将所有按权值从大到小珍珠进行排序,注意排序后每种颜色权值最大的两个是相邻的
也就是说,无论何时我们考虑一个为止i(1<=i<=n),那么前i个中仅可能有一种颜色的珍珠只存在一个
并且如果前i个中只有一种颜色只存在一个,那么这个珍珠一定是第i个,而前i-1个一定符合条件
那么所有i符合每种颜色出现两个的颜色的位置i,前i个的和就是取i个珍珠的答案
而对于不符合的,我们已经放置了i-1个符合答案了,可以考虑
1 . 放入一个前i-1个已经出现两次以上的颜色并且没在前i-1个位置的珍珠
2 . 在前i-1个珍珠中取出一个权值较小并且取出后不会导致某种颜色出现次数少于2的珍珠,
然后放入第i和第i+1个珍珠(因为第i和第i+1个珍珠是未放入的珍珠中两个权值最大的,而
其权值和可能大于第一种情况中的权值和,即比从前i-1中取一个珍珠,和i+2位置后面取一
个珍珠的权值和要大)
3 . 在前i-1个珍珠中取出两个颜色相同且权值和最小的珍珠,放入三个颜色相同切权值和最大
颜色在前i-1个珍珠中未出现的珍珠,这种情况有点难想,设想一些,如果i-1是个偶数
而前面i-1个球中每种颜色的球恰好只有两个,那么次数我不能取出一个球放入两个球,也不
能直接放入一个球,因为没有球可取,也没有球可放(怎么珍珠变成球了),也就是第一种
情况和第二种情况都无法满足,那么只能考第三种情况来弥补。同时,还有可能出现后面三
个球的权值和比前面两个球的权值和大的情况。蛤?那为什么不考虑取出三个球放四个球,
取出四个球放五个球的情况了?设想一个,假设从前i-1取出了三个球,现在要放四个球,
那么第i和第i+1个球可以直接放进去,因为这两个球权值最大,这就变成了,而i-1前面空了
三个球,现在我还需要取两个球,那么在i+2以后的位置取肯定是不优的,于是又回到前面
的球取两个球,此时转变成了第二种情况。同理,更多的情况,也不需要考虑了(注意从取
两个以上的球剩下的球就可以任取这种性质上来考虑)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,k,vis[N];
vector<ll>v[N];
multiset<ll>one,two,three;
vector<pair<ll,int> >jewels;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
int x,y;scanf("%d%d",&x,&y);
v[x].emplace_back(y*2);
}
for(int i=1;i<=k;i++)
{
sort(v[i].begin(),v[i].end(),greater<int>());
two.insert(v[i][0]+v[i][1]);
if(v[i].size()>2) three.insert(v[i][0]+v[i][1]+v[i][2]);
ll avg=v[i][0]+v[i][1]>>1;
jewels.push_back({avg,i});
jewels.push_back({avg,i});
for(int j=2;j<v[i].size();j++)
jewels.push_back({v[i][j],i});
}
sort(jewels.begin(),jewels.end(),greater<pair<ll,int>>());
ll sum=0,mnone=1ll<<60,mntwo=1ll<<60;
for(int i=0;i<n;i++)
{
ll ans=-2,value=jewels[i].first,c=jewels[i].second;
if(vis[c]==0)
{
if(!one.empty())
ans=max(ans,sum+*one.rbegin());
if(!two.empty())
ans=max(ans,sum-mnone+*two.rbegin());
if(!three.empty())
ans=max(ans,sum-mntwo+*three.rbegin());
two.erase(two.find(v[c][0]+v[c][1]));
if(v[c].size()>2)
three.erase(three.find(v[c][0]+v[c][1]+v[c][2]));
}
else
{
ans=sum+value;
if(vis[c]==1)
{
for(int j=2;j<v[c].size();j++)
one.insert(v[c][j]);
mntwo=min(mntwo,v[c][0]+v[c][1]);
}
else
{
one.erase(one.find(value));
mnone=min(mnone,value);
}
}
vis[c]++;
sum+=value;
printf("%lld\n",ans/2);
}
}