zoukankan      html  css  js  c++  java
  • HGOI 20191101am 题解

      Problem A awesome

      给出一个序列$A_i$,任取序列中三个数组成三元组$(a_i , a_j , a_k)$。

      输出本质不同的且$abc equiv 1 (mod  P)$且满足$a leq b leq c$的三元组$(a,b,c)$的组数。

      对于$100\%$的数据满足$n leq 2333 , P in Prime$

    Soltuion : 

      本题显然会卡常数,并且出了非常暧昧的数据范围。

      设$n$不去重前的数据规模,而$m$是去重前的数据规模。

      我们可以使用$O(n)$的暴力处理三元组中三个数都相同的情况。

      我们可以使用$O(n^2)$暴力处理三元组中两个数的情况。

      我们可以使用$O(n^2 log_2 n)$暴力处理三元组中所有数都不同的情况。

      我们将数组中每个数以其模$P$的余数为键值插入到hash表中。

      然后对于去重后的数组$O(m^2)$枚举两个不同的数,然后通过逆元从hash表中取出对应的数集即可。

      可以通过一个$lower_bound$来计数。

      时间复杂度为$O(n^2 log_2 n)$。

    # pragma GCC optimize(3)
    # include <bits/stdc++.h>
    # define int long long
    # define hash Hash
    using namespace std;
    const int N=3e3+10;
    int n,p,a[N],s[N],inv[N];
    vector<int>tmp;
    int Pow(int x,int n,int mo) {
        int ans = 1;
        while (n) {
            if (n&1) ans =ans * x %mo;
            x =x *x % mo;
            n>>=1;
        }
        return ans % mo;
    }
    struct Node {int key; vector<int>v;};
    vector<Node> hash[1927];
    void insert(int key,int val) {
        int to = key % 1926;
        for (int i=0;i<hash[to].size();i++) {
            if (key == hash[to][i].key) {
                hash[to][i].v.push_back(val);
                return ;
            }
        }
        Node tmp; tmp.key=key; tmp.v.push_back(val);
        hash[to].push_back(tmp);
    }
    vector<int>ttt;
    bool find(int key) {
        int to = key % 1926;
        for (int i=0;i<hash[to].size();i++) {
            if (key == hash[to][i].key) {
                ttt = hash[to][i].v;
                return 1;
            }
        }
        return 0;
    }
    signed main() {
        scanf("%lld%lld",&n,&p);
        for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
        sort(a+1,a+1+n);
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=n;j++) 
                if (a[i] == a[j]) s[i]++;
        }
        for (int i=1;i<=n;i++) inv[i] = Pow(a[i],p-2,p);
        for (int i=1;i<=n;i++) tmp.push_back(a[i]);
        tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
        for (int i=0;i<tmp.size();i++) insert(tmp[i]%p,tmp[i]);
        int ans = 0;
        for (int i=1;i<=n;i++) {
            if (a[i] == a[i-1] && i!=1) continue;
            if (s[i]<3) continue;
            if (a[i] * a[i] % p * a[i] % p == 1) ans++;
        }
        for (int i=1;i<=n;i++) {
            if (a[i] == a[i-1] && i!=1) continue;
            if (s[i] < 2) continue;
            for (int j=0;j<tmp.size();j++) if (tmp[j] != a[i] && a[i] * a[i] % p * tmp[j] % p == 1) ans++;
        }
        for (int i=1;i<=n;i++) {
            if (a[i] == a[i-1] && i!=1) continue;
            for (int j=i+1;j<=n;j++) {
                if (a[j] == a[j-1] && j!=i+1) continue;
                if (a[i] >= a[j]) continue;
                if (a[i] % p == 0 || a[j] % p == 0) continue;
                int key = 1 * inv[i] * inv[j] % p;
                if (!find(key)) continue;
                ans += ttt.end()-upper_bound(ttt.begin(),ttt.end(),a[j]);
            }
        }
        printf("%lld
    ",ans);
        return 0;
    }
    awesome.cpp

      Problem B bag

      有$n$个包和$m$个物品,每个包有容量$a_i$,每个物品有体积$w_i$价值$v_i$。

      选出$k$个物品,满足每个物品可以放置在$1$个包中,且可以通过排列所有包使得包内物品$w_i,v_i$都单调不增。

      输出最大的$k$。

      对于$100\%$的数据满足$1 leq n,m leq 10^5$

      Solution :

        首先将$w_i$按照降序排序,当$w_i$相同的时候按照$v_i$降序以最大化$k$。

        首先考虑到$k$个物品放入$k$个包,显然会选择$k$个最大的包放入。

        问题就转化为对价值$v_i$做一个最长不上升子序列。

        设$f[i]$表示考虑到第$i$的位置最长不上升子序列的值,且强制第$i$个人必须选。

        朴素的$dp$方程为$f[i] = max(f[i] , f[j] + 1)$,其中$j$能转移到$i$的条件是$v[j] geq v[i]$且$t[f[j] + 1] geq w[i]$

        这样的时间复杂度为$O(n^2)$

        可以通过改变状态为$f[i]$表示长度为$i$的最长不上升子序列最后一个数的最大值是多少。

        可以看出$f[i+1] leq f[i]$ 所以$f$是单调的。

        所以可以二分出最后一个$j$使得$f[j] geq v[i]$ ,用$v[i]$更新$f[j+1]$的值。

        所以,此时复杂度为$O(nlog_2n)$

    # include<bits/stdc++.h>
    # define int long long
    using namespace std;
    const int N=1e5+10;
    struct rec{int w,v;}a[N<<1];
    bool cmp(rec a,rec b) { 
        if (a.w == b.w) return a.v>b.v;
        else return a.w > b.w;
    }
    int n,m,t[N],f[N];
    signed main() {
        int tt; scanf("%lld",&tt);
        while (tt--) {
            scanf("%lld",&n);
            for (int i=1;i<=n;i++) {
                scanf("%lld%lld",&a[i].w,&a[i].v);
            }
            sort(a+1,a+1+n,cmp);
            scanf("%lld",&m);
            for (int i=1;i<=m;i++) {
                scanf("%lld",&t[i]);
            }
            memset(f,0,sizeof(f));
            sort(t+1,t+1+m); reverse(t+1,t+1+m);
            for (int i=1;i<=n;i++) {
                int l=1,r=n,p=0;
                while (l<=r) {
                    int mid=(l+r)>>1;
                    if (f[mid]>=a[i].v) p=mid,l=mid+1;
                    else r=mid-1;
                }
                if (a[i].w<=t[p+1] && p+1<=m) f[p+1] = max(f[p+1],a[i].v);
            }
            for (int i=n;i>=0;i--) if (f[i] != 0) {
                printf("%lld
    ",i);
                break;
            }   
        }
        return 0;
    }
    bag.cpp

      Problem C subtree

     给出$n,k$,和限制$a_1 , a_2 , ... , a_k$

      输出有根树的个数,使得不存在任意一个节点的size,在集合$a_1 , a_2 , ... , a_k$中。

      设该树的深度为$i$,给出$[l,r]$,输出$i in [l,r]$的答案。

      对于$100\%$的数据$1 leq n leq 500$.

       Solution:

         设$f[i][j]$表示有$n$个节点的有根树,设其根为$1$,深度为$j$的方案数。

        考虑将一棵含有$j$个节点的有根子树的编号强制为$2$ , 拼接到根节点$1$上。

        那么就划分为$f[j][d-1]$($2$为根节点的子树)和$f[i-j][d]$(另外一部分树)两个子问题。

        同时考虑到除去编号为$1$和编号为$2$两个节点,其他的节点可以任意划分,所以有一个组合数$inom{i-2}{j-1}$

        所以动态规划方程为$f[i][d] = sumlimits_{j = 1} ^{i-1} f[j][d-1] imes f[i-j][d] imes inom{i-2}{j-1} $   

        时间复杂度为$O(n^3)$

    # include<bits/stdc++.h>
    # define int long long
    using namespace std;
    const int N=505;
    const int mo=998244353;
    int n,k;
    int f[N][N],c[N][N];
    bool flag[N];
    signed main()
    {
    //  freopen("data.in","r",stdin);
        scanf("%lld%lld",&n,&k);
        for (int i=0;i<=n;i++) {
            c[i][0] = c[i][i] = 1;
            for (int j=1;j<i;j++)
                c[i][j] = (c[i-1][j-1] + c[i-1][j])%mo;
        }
        for (int i=1;i<=k;i++) {
            int t; scanf("%lld",&t); flag[t] = 1;
        }
        if (flag[1]) f[1][1]=0; else f[1][1]=1;
        for (int d=2;d<=n;d++) {
            f[1][d] = 1;
            for (int i=2;i<=n;i++) {
                for (int j=1;j<=i-1;j++) 
                (f[i][d]+=f[j][d-1]*f[i-j][d]%mo*c[i-2][j-1]%mo)%=mo;
            }
            for (int i=1;i<=n;i++) if (flag[i]) f[i][d] = 0;
        } 
        int l,r; scanf("%lld%lld",&l,&r);
        for (int i=l;i<=r;i++) printf("%lld ",((f[n][i]-f[n][i-1])%mo+mo)%mo);
        return 0;
    }
    subtree.cpp
  • 相关阅读:
    2017.5.11下午学习内容
    windows消息和消息队列
    探索Win32系统之窗口类(转载)
    WinMain函数详解(转载)
    Ajax爬取实战头条街拍美图
    Ajax实战微博
    Ajax请求分析实战
    ubuntu 安装rails
    ubuntu Thunderbird 接收邮件显示乱码的问题排除
    ubuntu 开机挂载windows分区
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/11776841.html
Copyright © 2011-2022 走看看