Codeforces 每日一练 1213G+961E+1282B2

(都是多解题啊QAQ,我就只写一种了,其实是不会其他的

1213G Path Queries

传送门
题意:一棵节点数为n的边权树,m次询问,每次回答最大权值小于q的简单路径数目。
考虑到符合询问的路径一定不包含权值大于q的边,可以考虑用先把询问排序,然后边排序,依次加边的离线做法。
对于已经加的边,形成的是一个个连通块,假设某连通块的大小为n,那么它对答案的贡献就是 C n 2 C_{n}^{2} ,合并过程中先减去两个连通块的贡献,合并后加上整体的贡献。
带权并查集+离线即可AC。

#include <bits/stdc++.h>
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define maxn 200005
#define ll long long
#define int long long
int q[maxn],tmp[maxn],sz[maxn],f[maxn];
struct edge{
    int u,v,w;
}e[maxn];
ll ans=0;
int findfa(int x){
    if(f[x]==x)return x;
    else f[x]=findfa(f[x]);
    return f[x];
}
void mergefa(int a,int b){
    int fa=findfa(a),fb=findfa(b);
    if(fa!=fb){
        if(sz[fa]<sz[fb])swap(fa,fb);
        f[fb]=fa;
        ans-=sz[fa]*(sz[fa]-1)/2;
        ans-=sz[fb]*(sz[fb]-1)/2;
        sz[fa]+=sz[fb];
        ans+=sz[fa]*(sz[fa]-1)/2;
    }
}
bool cmp(edge a,edge b){
    return a.w<b.w;
}
map<int,int>mp;
signed  main(){
    IOS
    int n,m;
    cin>>n>>m;
    for (int j = 1; j <=n ; ++j) {
        f[j]=j;
        sz[j]=1;
    }
    for (int i = 0; i <n-1 ; ++i) {
        int u,v,wt;
        cin>>u>>v>>wt;
        e[i].u=u;
        e[i].v=v;
        e[i].w=wt;
    }
    for (int i = 0; i <m ; ++i) {
        cin>>q[i];
        tmp[i]=q[i];
    }
    sort(e,e+n-1,cmp);
    sort(q,q+m);
    for (int i = 0,j=0; i <m ; ++i) {
        while(j<n-1&&e[j].w<=q[i]){
            mergefa(e[j].u,e[j].v);
            j++;
        }
        mp[q[i]]=ans;
    }
    for (int i = 0; i <m ; ++i) {
        cout<<mp[tmp[i]]<<" ";
    }
    return 0;
}

关于并查集+离线的另一道题:洛谷P1197 [JSOI2008]星球大战
其实这道题还能点分治去写,或者二分+并查集的在线做法。
分治走这里
二分+并查集

961E Tufurama

传送门
题意:一个电视连续剧有n季(2e5季真的折磨哦 ),每季有a[i]集,询问点对<x,y>满足第y季第x集与第x季第y集同时存在的数目。
首先尝试把题意简化,若要满足要求一定有a[x]>=y和a[y]>=x,为了避免重复计算,加上y>x,那么就相当于在x+1~a[x]之间找满足a[y]>=x的季数,同时由于季数一定小于等于n,那么如果a[x]>=n,可以把a[x]直接换成n,把数据范围缩小到2e5。
明白题意后就是做法了,可以树状数组也可以权值线段树,但正好没学过主席树,就去学了学,然后用主席树水过了这道题。
(如果你像我一样没学过主席树,可以这边走:传送门
那么我们建好树之后只需要每次去找i+1~a[i]里在区间[i,n]的个数就好。

#include <bits/stdc++.h>
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define maxn 200005
#define ll long long
int a[maxn];
ll b[maxn];
struct persistent_seg_tree
{
    struct data
    {
        int sum;
        data() :sum(0) {}
    };
    struct node
    {
        int l, r;
        data d;
        node() :l(0), r(0) {}
    };
    vector<node> nodes;
    vector<int> roots;
    int sz;

    void up(int id)
    {
        nodes[id].d.sum = nodes[nodes[id].l].d.sum + nodes[nodes[id].r].d.sum;
    }
    int newnode(int cpy)
    {
        int id = (int)nodes.size();
        node tmp = nodes[cpy];
        nodes.push_back(tmp);
        return id;
    }
    int add(int tp, int tl, int tr, int i, int v)
    {
        int id = newnode(tp);
        if (tl + 1 >= tr)
        {
            nodes[id].d.sum += v;
            return id;
        }
        int tmid = (tl + tr) / 2;
        if (i < tmid)
        {
            int nid = add(nodes[id].l, tl, tmid, i, v);
            nodes[id].l = nid;
        }
        else
        {
            int nid = add(nodes[id].r, tmid, tr, i, v);
            nodes[id].r = nid;
        }
        up(id);
        return id;
    }
    int getsum(int tp, int tl, int tr, int l, int r)
    {
        if (l <= tl && tr <= r)
        {
            return nodes[tp].d.sum;
        }
        int tmid = (tl + tr) / 2;
        int sum = 0;
        if (l < tmid)
        {
            sum += getsum(nodes[tp].l, tl, tmid, l, r);
        }
        if (r > tmid)
        {
            sum += getsum(nodes[tp].r, tmid, tr, l, r);
        }
        return sum;
    }
    // interface
    void init(int range, int root_size) // 数组大小[0, range),操作次数
    {
        sz = range;
        nodes.clear();
        roots.clear();
        nodes.reserve(root_size * (int)(log(sz * 2.0) / log(2.0) + 1.01));
        nodes.push_back(node());
        roots.reserve(root_size + 1);
        roots.push_back(0);
    }
    void add(int pos, int v)
    {
        int last = roots.back();
        roots.push_back(add(last, 0, sz, pos, v));
    }
    int getsum(int t, int l, int r)
    {
        if (t <= 0) return 0;
        if (r < l) return 0;
        if (t >= (int)roots.size()) t = (int)roots.size() - 1;
        return getsum(roots[t], 0, sz, l, r + 1);
    }
}tree;
signed  main(){
    IOS
    int n;
    cin>>n;
    ll tmp=n;
    int ma=-1;
    for (int i = 1; i <=n ; ++i) {
        cin>>b[i];
        a[i]=min(b[i],tmp);
        ma=max(ma,a[i]);
    }
    tree.init(ma+1,4*n);
    for (int i = 1; i <=n ; ++i) {
        tree.add(a[i],1);
    }
    ll ans=0;
   for (int i = 1; i <=n ; ++i) {
        int r=min(n,a[i]);
        int l=i+1;
        if(r>=l)ans+=tree.getsum(r,i,n+1)-tree.getsum(l-1,i,n+1);
    }
    cout<<ans;
    return 0;
}

如果你不想写主席树,简单的权值线段树也是可以的,贴大佬博客了:权值线段树传送门

1282B2 K for the Price of One (Hard Version)

传送门
题意:n个商品,每个加个a[i],你携带了金额为p的money,正好碰上商店的活动,购买一个商品可以选择k-1个加个小于等于它的商品免费拿走(0或k-1二选一,不能选其他数目),最后你能买多少物品。
首先把商品按加个排序,明显买连续的前m个是最优的,假设购买了前k个,而后面的从k+2开始购买,那我们把后面的整体前移,数目不变,总价减少了,说明购买连续的前m个是最优的。
那么就可以写dp转移了,记录买到第i个要多少钱,对于前k-1个,由于不能白嫖,只能连续买,dp[i]=dp[i-1]+a[i],而对于之后的,肯定怎么省钱怎么来,dp[i]=dp[i-k]+a[i]。转移过程中记录价格合法的最大物品数目即可。

#include <bits/stdc++.h>
using namespace std;
#define IOS ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define maxn 200005
#define ll long long
ll a[maxn],p;
int n,k;
ll dp[maxn];
signed  main(){
    IOS
    int t;
    cin>>t;
    while(t--) {
        cin >> n >> p >> k;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
        }
        memset(dp,0, sizeof(dp));
        sort(a+1,a+1+n);
        for (int i = 1; i <=n ; ++i) {
            if(i<k)dp[i]=dp[i-1]+a[i];
            else dp[i]=dp[i-k]+a[i];
        }
        int ans=0;
        for (int j = 1; j <=n ; ++j) {
            if(dp[j]<=p){
                ans=j;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

发布了19 篇原创文章 · 获赞 16 · 访问量 6417

猜你喜欢

转载自blog.csdn.net/qq_43682148/article/details/105372955
今日推荐