2017 Multi-University Training Contest

来源:互联网 发布:seo零基础 编辑:程序博客网 时间:2024/06/06 19:37

HDU - 6058 - Kanade's sum(双向链表)

题意:

  求解,定义f(l,r,k)为[l,r]区间中第k大的元素。n为50w。k为80。

思路:

  这题我们枚举每个元素i作为第k大出现的区间数量num,那么它对答案的贡献度就是i*num。

  至于如果求解每个元素作为第k大出现的区间呢。我们思考如果找到这个元素i所在的位置。往左浮现出k个比他大的元素的位置,往右同理,如果定义元素i的位置是pos[0],那么往左的比他大的元素位置依次为pos[-1,-2,-3]。往右为正。那么[pos[k] + 1,pos[k - 1]]  和 [pos[0],pos[1] - 1]这个两个区间是不是随意组合都能截断出元素i作为第k大的区间,所以它的贡献值是左边这个区间的大小left乘上右边这个区间的大小。可以把这个一直往右挪动直到左边界达到元素i本来到的位置并且右边界达到极致为止。算k个乘积的加和,就能算出元素i作为第k大出现的区间数量num。如果我们用链表来维护这个数组,那么就可以做到O(1)的移动,那么对每个元素的查询的复杂度就是O(k*1)。

  具体操作为:我们从小到大枚举元素,每次枚举出一个元素以后,找到它的区间们,删除这个元素。就能保证在链表中的每一个元素除了他本身,都大于我们要枚举的元素i。所以是O(1)的移动。这题就做完了。

        哦对了!!!有个点要思考!!!为什么代码中右边界rb是要从lb往右跳k次,而不是直接使用元素i的位置。

        答:是为了确保第一次枚举的时候,[lb,rb]中元素i的确为第k大。你问我为什么?emmm。万一你lb没跳够k次就到边界上了呢。

#include <bits/stdc++.h>using namespace std;typedef long long LL;const int mod = 1e9 + 7;const int maxn = 5e5 + 5;int pre[maxn], nxt[maxn];int pos[maxn], a[maxn];int main(){    int T;    scanf("%d", &T);    while(T--)    {        int n, k;        scanf("%d%d", &n, &k);        for(int i = 1; i <= n; i++)        {            pre[i] = i - 1;            nxt[i] = i + 1;        }        pre[n + 1] = n;        nxt[n + 1] = n + 1;        pre[0] = 0;        nxt[0] = 1;        for(int i = 1; i <= n; i++)        {            scanf("%d", &a[i]);            pos[a[i]] = i;        }        LL ans = 0;        for(int i = 1; i <= n; i++)        {            int idx = pos[i];            int lb = idx;            for(int j = 0; j < k; j++)  lb = pre[lb];            int rb = lb;            for(int j = 0; j < k; j++)  rb = nxt[rb];            LL temp = 0;            while(lb != idx && rb != n + 1)            {                temp += 1LL * (nxt[rb] - rb) * (nxt[lb] - lb);                lb = nxt[lb], rb = nxt[rb];            }            temp = temp * i;            ans += temp;            pre[nxt[idx]] = pre[idx];            nxt[pre[idx]] = nxt[idx];        }        printf("%lld\n", ans);    }    return 0;}

HDU - 6060 - RXD and dividing(脑洞题)

题意:

  就是把一棵树上除了标号为1以外的n-1个点,划分为k个点集合si(1<= i <= k),然后分别联通这k个点集合{}所需的路径的最小费用的加和。最大化这个和。问你要多少。

思路:

  还真没想到。。比赛的时候。它的这个操作就是把n-1个点,每个点标记上1-k的标记,然后每条边的贡献度,就是min{这颗子树的大小,k}。加和就完了。dfs一次O(n)。

#include <bits/stdc++.h>using namespace std;typedef long long LL;const int maxn = 1e6 + 6;int n, k;int siz[maxn];vector<pair<int, int>>G[maxn];LL ans;void dfs(int u, int fa){    siz[u] = 1;    for(auto o : G[u])  if(o.first != fa)    {        dfs(o.first, u);        siz[u] += siz[o.first];        ans += 1LL * o.second * min(siz[o.first], k);    }}int main(){    while(~scanf("%d%d", &n, &k))    {        for(int i = 1; i <= n; i++) G[i].clear();        for(int i = 0; i < n - 1; i++)        {            int u, v, c;            scanf("%d%d%d", &u, &v, &c);            G[u].push_back({v, c});            G[v].push_back({u, c});        }        ans = 0;        dfs(1, -1);        printf("%lld\n", ans);    }    return 0;}

HDU - 6061 - RXD and functions(NTT)

纯公式推导。然后就是套模板了。




#include <bits/stdc++.h>using namespace std;typedef long long LL;const int mod = 998244353;const int maxn = 4e5+10;const int g = 3;LL qp[30];int getLen(int a){    a <<= 1;    int res = 1;    while(res < a)    res <<= 1;    return res;}//求余版本LL quickpow (LL a, LL b, LL Mod){a %= Mod;//至关重要!    LL ret = 1;    while(b)    {        if(b & 1)   ret = ret * a % Mod;        b >>= 1;        a = (a * a) % Mod;    }    return ret;}void brc(LL *a, int l){    for(int i=1, j=l/2; i<l-1; i++)    {        if(i<j) swap(a[i],a[j]);        int k=l/2;        while(j>=k)        {            j-=k;            k>>=1;        }        if(j<k) j+=k;    }}void ntt(LL *y, int l, int on){    brc(y,l);    int id = 0;    long long u,t,tmp;    for(int h=2;h<=l;h<<=1)    {        id++;        for(int j=0;j<l;j+=h)        {            long long w = 1;            for(int k=j;k<j+h/2;k++)            {                u = y[k];                t = w*y[k+h/2]%mod;                y[k] = u+t;                if(y[k]>=mod)y[k]-=mod;                y[tmp=k+h/2]=u-t;                if(y[tmp]<0)y[tmp]+=mod;                w = w*qp[id]%mod;            }        }    }    if(on<0)    {        for(int i=1;i<l/2;i++)swap(y[i],y[l-i]);        long long ni = quickpow(l,mod-2, mod);        for(int i=0;i<l;i++)y[i] = (y[i]*ni)%mod;    }}int c[maxn];LL fac[maxn], invf[maxn];LL F1[maxn], F2[maxn];void init(){    for(int i=0;i<21;i++)    {            int t=1<<i;            qp[i]=quickpow(g,(mod-1)/t, mod);    }    fac[0] = 1;    for(int i = 1; i <= 100000; i ++)    {        fac[i] = fac[i - 1] * i % mod;    }    invf[100000] = quickpow(fac[100000], mod - 2, mod);    for(int i = 100000 - 1; i >= 0; i--)    {        invf[i] = invf[i + 1] * (i + 1) % mod;    }}int main(){    init();//    cout << invf[0] << endl;    int n;    while(~scanf("%d", &n))    {        for(int i = 0; i <= n; i++) scanf("%d", &c[i]);        int m;        scanf("%d", &m);        long long suma = 0;        for(int i = 0; i < m; i++)        {            int x;            scanf("%d", &x);            suma -= x;            suma = (suma + mod) % mod;        }        int len = getLen(n + 1);        LL si = 1;        for(int i = 0; i < len; i++)        {            if(i <= n)            {                F1[i] = 1LL * c[n - i] * fac[n - i] % mod;                F2[i] = si * invf[i] % mod;                si = si * suma % mod;            }            else F1[i] = F2[i] = 0;        }        ntt(F1, len, 1);        ntt(F2, len, 1);        for(int i = 0; i <len; i++)        {            F1[i] = F1[i] * F2[i] % mod;        }        ntt(F1, len, -1);        for(int i = 0; i <= n; i++)        {            F1[i] = (F1[i] + mod) % mod;            F1[i] = (F1[i] * invf[n - i] % mod);        }        for(int i = 0; i <= n; i++)        {            printf("%lld ", F1[n - i]);        }puts("");    }    return 0;}



HDU - 6063 - RXD and math(结论题 or 打表)

题意:

 

思路:

  icpc赛场上血的教训就是,遇事不决先打表。一打表就出结论了。n^k,快速幂一下就AC了啊,这水的一笔。

  emmm。什么情况,居然wa了。哦。n和k都是1e18,回顾一下快速幂quickpow(a, b, mod)的过程,a = a * a的操作直接溢出了。蠢比orz。

  所以正解是,进入快速幂之前%一下n。

#include <bits/stdc++.h>using namespace std;typedef long long LL;const int mod = 1e9 + 7;LL quickpow(LL a, LL b, LL Mod){    LL ret = 1;    a %= Mod;    while(b)    {        if(b & 1)   ret = ret * a % Mod;        b >>= 1;        a = a * a % Mod;    }    return ret;}int main(){    LL n, k;    for(int cas = 1; ~scanf("%lld%lld", &n, &k); cas++)    {        printf("Case #%d: %lld\n", cas, quickpow(n, k, mod));    }    return 0;}

HDU - 6065 - RXD, tree and sequence(LCA+DP)

题意:

        给你一棵树,编号1-n,根为1。给你一个序列a。问你,把这个序列a切成k段,每段的LCA加和最小为多少。

思路:

        dp[i][j]:前i个数切割成j段,LCA最小值为多少。

        思考一下转移,如果第i个数,自成一段那么应该是dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + depth[a[i]]).

        如果第i个数并入之前一段,那么会有两种情况,一:第i个数字不影响第j段的LCA,那么dp[i][j] = min(dp[i][j], dp[i - 1][j]),二:第i个数字更新第j段的LCA,那么dp[i][j] = min(dp[i][j], dp[i - 2][j - 1] + depth[LCA(a[i -1], a[i])])。

        解释一下第二种情况:设a[1] - a[n] 的LCA为L,那么易得,a[1] - a[n + 1]的LCA 就是   a[1] - a[n]中的任意一个和a[n + 1]的LCA。所以更新第j段的LCA的时候,我们采取

a[i - 1]和a[i]的LCA。然后还有是因为,处理出相邻点间lca深度之后,比如 7 4 5 6 3 9 10,若最优切分方式里 4 与 3 在不同区间里,4 与 3 之间的任何数,即 5 6,划分给 3 区间或者 4 区间,都不会影响最终答案。 所以可以这么做

#include <bits/stdc++.h>using namespace std;typedef long long LL;const int maxn = 3e5 + 10;const int inf = 0x3f3f3f3f;vector<int>G[maxn];const int root = 1;int pa[20][maxn];int depth[maxn];void dfs(int v, int fa, int d){    pa[0][v] = fa;    depth[v] = d;    for(auto o : G[v])  if(o != fa)        dfs(o, v, d + 1);}void init(int V){    dfs(root, -1, 1);    for(int k = 0; k + 1 < 20; k ++)    {        for(int v = 1; v <= V; v++)        {            if(pa[k][v] < 0)    pa[k + 1][v] = -1;            else pa[k + 1][v] = pa[k][pa[k][v]];        }    }}int LCA(int u, int v){    if(depth[u] > depth[v]) swap(u, v);    for(int k = 0; k < 20; k++)    {        if(depth[v] - depth[u] >> k & 1)            v = pa[k][v];    }    if(u == v)  return u;    for(int k = 20 - 1; k >= 0; k--)    {        if(pa[k][u] != pa[k][v])        {            u = pa[k][u];            v = pa[k][v];        }    }    return pa[0][u];}int n, m;int a[maxn];vector<vector<int>>dp;int main(){    while(~scanf("%d%d", &n, &m))    {        for(int i = 1; i <= n; i++) G[i].clear();        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);        for(int i = 1; i < n; i++)        {            int u, v;            scanf("%d%d", &u, &v);            G[u].push_back(v);            G[v].push_back(u);        }        init(n);        dp.resize(n + 1);        for(int i = 0; i <= n; i++)        {            dp[i].resize(m + 1);            for(int j = 1; j <= m; j++)                dp[i][j] = inf;        }        dp[0][0] = 0;        for(int i = 1; i <= n; i++)        {            for(int j = 1; j <= m; j++)            {                dp[i][j] = min(dp[i][j], dp[i - 1][j]);                dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + depth[a[i]]);                if(i > 1) dp[i][j] = min(dp[i][j], dp[i - 2][j - 1] + depth[LCA(a[i], a[i - 1])]);            }        }        printf("%d\n", dp[n][m]);    }    return 0;}


HDU - 6066 - RXD's date(water)

思路:

  说明我手速还可以啊。读了一会别的题,看榜上ac了,转过来读了直接秒的速度还挺快的 嘻嘻。

#include <bits/stdc++.h>using namespace std;typedef long long LL;const int mod = 1e9 + 7;int main(){    int n;    scanf("%d", &n);    int cnt = 0;    for(int i = 0; i < n; i++)    {        int x;        scanf("%d", &x);        if(x <= 35) cnt++;    }    printf("%d\n", cnt);    return 0;}