AtCoder Beginner Contest 214题解

D - Sum of Maximum Weights

题意:

给出一个树,包含n个节点,n-1条边,每条边有一个权重wi。记f(u,v)为点u到点v的最短路径上出现的最大的权重,求n个点两两之间的f(u,v)之和。

题解:

假设有两个树,两个树之间通过一个权重为w的边连接,且两个树中的边的权重都小于w,那么此时满足f(u,v)==w的点对个数就有size(tree1)*size(tree2) .
根据上述假设,我们可以按照边权重大小重新加边来哦构造这颗树,在构造过程中就能够得知有多少个点对是满足f(u,v)==wi的。过程中使用并查集进行合并操作。

代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pii pair<int,int>
#define debug(X) std::cerr<<#X<<" = "<<X<<endl
#define io_init  ios::sync_with_stdio(false);    \
            cin.tie(0);                     \
            cout.tie(0);
const int maxn = 2e5+5;
const int mod = 1e9+7;
int t;
int n;
int f[maxn],sz[maxn];
struct edge
{
    int u,v,w;
}e[maxn];
 
bool cmp(edge a,edge b)
{
    return a.w<b.w;
}
 
int gf(int x)
{
    return x==f[x]?x:f[x]=gf(f[x]);
}
 
int main()
{
    io_init;
    cin>>n;
    for(int i=1;i<n;++i)cin>>e[i].u>>e[i].v>>e[i].w;
    for(int i=1;i<=n;++i)f[i]=i,sz[i]=1;
    sort(e+1,e+n,cmp);
    LL ans = 0;
    for(int i=0;i<n;++i)
    {
        int x = gf(e[i].u),y=gf(e[i].v);
        ans +=1ll*e[i].w*sz[x]*sz[y];	
        //merge
        f[x]=y,sz[y]+=sz[x];
    }
    cout<<ans;
    return 0;
}

E - Packing Under Range Regulations

题意:

有n个球,每个球要求必须要放到各自的一个区间【Li,Ri】内,并且保证每个点上最多放一个球,问有没有可行的放置方法?

题解:

个人觉得是个贪心问题。

首先,假设有两个球它们对应的区间是【La,Ra】和【Lb,Rb】,其中Ra==Rb,La<Lb;那么如果想要尽可能的满足题意,应该先安排那个球呢?肯定是b球,因为即使b球安排完后【Lb,Rb】没有空余点了,那a球依然可以安排在【La,Lb-1】上。

第二,假设我们为了尽可能的满足题意,我们每次都把球放在可以放置的最小位置,这样后面的球就会有更多的选择去放,那么能够满足题意的机会也就更大一些。所以我们如果要从小王大去放置的话,就需要优先选择右端点小的球。

所以,最终我们可以将区间按照右端点递增,右端点相同时按左端点递减的顺序排列。这样能够更大机会满足题意。

代码如何记录某个点已经被占用了呢?可以使用map,因为左右端点的范围比较大,而球的个数较小。
如何知道某个点被占用后,下一个可放点在哪里呢?可以在放置球时同时记录每个点的下一个可用点。这个使用并查集能够简单有效地维护。

代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pii pair<int,int>
const int maxn = 2e5+5;
const int mod = 1e9+7;
int t;
int n;
map<int,int> mp;
struct edge
{
    int l,r;
}e[maxn];
 
bool cmp(edge a,edge b)
{
    if(a.r==b.r)
        return a.l>b.l;
    return a.r<b.r;
}
 
int gf(int x)
{
    return (mp.count(x)==0||mp[x]==x)?x:mp[x]=gf(mp[x]);
}
 
int main()
{
    io_init;
    cin>>t;
    while(t--)
    {
        cin>>n;
        for(int i=1;i<=n;++i)cin>>e[i].l>>e[i].r;
        sort(e+1,e+n+1,cmp);
        mp.clear();
        bool ok = true;
        for(int i=1;i<=n;++i)
        {
            int now = gf(e[i].l);
            if(now>e[i].r)
            {
                ok=false;
                break;
            }
            mp[now]=now+1;
        }
        if(ok)
            cout<<"Yes\n";
        else
            cout<<"No\n";
    }
    return 0;
}

F - Substrings

题意:

给出一个字符串S。现在可以从中删除一些非连续的字符(删除的每两个字符不相邻),剩下的字符构成一个子串。问最多会产生多少种不同的子串?

题解:

首先,先不去考虑删除时每两个字符不相邻的限制,想想该怎么去做?

先来计算一下,进行删除操作后会产生多少个子串(可能相同)?
设dp[i]为前i个字符能产生的子串且以第i个字符作为子串结尾。
转移方程:dp[i] = sum(dp[k]) 0<=k<=i-1
复杂度 O(n^2) / 维护sum则是O(n)

那么该怎么转移才能保证产生子串不同呢?
假设dp[i] == dp[j] 且 j < i ,并且j是离i最近的那一个(小于i的最大的那个)。为了不重复,那么dp[i]没有必要从S[J]之前的状态进行转移,因为那样做会产生重复,dp[j]在进行转移时已经产生了那些可能的子串。那么很容易想到转移方程: dp[i] = sum(dp[k]) j<=k<=i-1 ,j满足上述条件,若是不存在满足条件的j那么j=0。
复杂度 O(n^2)

虽然第一眼看的时间复杂度为O(n^2),但是实际复杂度远远低于O(n^2),实际上应该是O(m*n),其中m为字符串中不同字符的种类数(26)。因为每次在寻找满足dp[i]=dp[j]时,每一个dp[k]只会被二次使用至多26次。

好了,最后再来看一下 删除的字符不能相邻该怎么处理?
其实很简单,状态转移的时候 从i-2开始转移即可。
dp[i] = sum(dp[k]) j<=k<=i-2,j同上

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

int main(){
    string S; cin >> S;
    int N = S.size();
    vector<long long> dp(N+2); dp[0] = 1;
    for(int i = 0; i < N; i++){
        for(int j = i-1; ; j--){
            dp[i+2] = (dp[i+2] + dp[j+1]) % 1000000007;
            if(j == -1 || S[j] == S[i]) break;
        }
    }
    long long ans=0;
    for(int i = 2; i < N+2; i++) ans += dp[i];
    cout << ans%1000000007 << endl;
}

后续若是继续补题会继续添加题解。
upd 2021/8/25:
差不多就补到F题了。

欢迎评论和指正!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值