2017 ACM/ICPC Asia Regional Shenyang Online【solved:7 / 12】

来源:互联网 发布:c语言栈实现四则运算 编辑:程序博客网 时间:2024/05/20 04:13

hdu 6194 string string string(后缀数组)

题意:给一个字符串,和一个k,问你串中恰好出现k次的子串有多少个。

思路:后缀数组height数组的应用。理解了height数组就很容易啦。
然后K>1的时候,在height数组上,枚举每一个长度为k的区间,对答案的贡献度就是ans += max(0, minn - max(he

ight[lb - 1], height[rb + 1]))。怎么理解呢,就是从长度1-minn这里minn个字符串都出现了k次,从sa[lb-1]-sa[lb+k-2]。然后因为是恰好出现k次,所以对于这1-minn要减去,在区间前后一格位置也出现的,也即是max(height[i], height[i + 1])。
emmm,k==1的时候,判断方式同上,只不过要理解一个sa[i]的意义,sa[i]代表的是它是后缀i,所以长度为len-sa[i],那么剪掉重复的,就是答案了。

关于后缀数组中变量的意义:
参考自:http://blog.csdn.net/yxuanwkeith/article/details/50636898
Str :需要处理的字符串(长度为Len)
Suffix[i] :Str下标为i ~ Len的连续子串(即后缀)
Rank[i] : Suffix[i]在所有后缀中的排名
SA[i] : 满足Suffix[SA[1]] < Suffix[SA[2]] …… < Suffix[SA[Len]],即排名为i的后缀为Suffix[SA[i]] (与Rank是互逆运算)

#include <bits/stdc++.h>using namespace std;const int maxn = 100000 + 5;//SAchar s[maxn];int sa[maxn], t[maxn], t2[maxn], c[maxn], len;bool cmp(int *y,int a,int b,int k){    int a1=y[a];    int b1=y[b];    int a2=a+k>=len ? -1:y[a+k];    int b2=b+k>=len ? -1:y[b+k];    return a1==b1 && a2==b2;}void build_sa(int m)//构造字符串s的后缀数组,每个字符值必须为0-m-1{    int *x = t, *y = t2;    for(int i = 0; i < m; i++) c[i] = 0;    for(int i = 0; i < len; i++) ++c[x[i] = s[i] - 'a'];    for(int i = 1; i < m; i++) c[i] += c[i-1];    for(int i = len-1; i >= 0; i--) sa[--c[x[i]]] = i;    for(int k = 1; k <= len; k <<= 1)    {        int p = 0;        for(int i = len - k; i < len; i++) y[p++]=i;        for(int i = 0; i < len; i++)           if(sa[i] >= k) y[p++] = sa[i] - k;        for(int i = 0; i < m; i++) c[i] = 0;        for(int i = 0; i < len; i++) ++c[x[y[i]]];        for(int i = 1; i < m; i++) c[i] += c[i-1];        for(int i = len - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];        swap(x, y);        m = 1; x[sa[0]]=0;        for(int i = 1; i < len; i++)            x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1:m++;        if( m>=len ) break;    }}int rrank[maxn], height[maxn];void getHeight(){    for(int i=0; i<len; i++) rrank[sa[i]]=i;    height[0]=0;    int k=0;    for(int i=0; i<len; i++)    {        if(!rrank[i]) continue;        int j=sa[rrank[i]-1];        if(k) k--;        while(s[i+k]==s[j+k]) k++;        height[rrank[i]]=k;    }}//RMQint d[maxn][20];void initRMQ(int n, int a[]){    for(int i = 0; i < n; i++)  d[i][0] = a[i];    for(int j = 1; (1 << j) <= n; j++)    {        for(int i = 0; i + (1 << j) - 1 < n; i++)        {            d[i][j] = min(d[i][j - 1], d[i + (1 << (j - 1))][j - 1]);        }    }}int query(int l, int r){    int k = 0;    while((1 << (k + 1)) <= r - l + 1)    k++;    return min(d[l][k], d[r - (1 << k) + 1][k]);}int main(){    int T;    scanf("%d", &T);    while(T--)    {        int k;        scanf("%d%s", &k, s);        len = strlen(s);        build_sa(26);        getHeight();        long long ans = 0;        if(k == 1)        {            height[len] = 0;            for(int i = 0; i < len; i++)    ans += len - sa[i] - max(height[i], height[i + 1]);            printf("%lld\n", ans);            continue;        }        initRMQ(len, height);        for(int i = 1; i + k - 1 < len; i++)        {            int lb = i, rb = lb + k - 2;            int minn = query(lb, rb);            ans += max(0, minn - max(height[lb - 1], height[rb + 1]));        }        int lb = len - k +1, rb = len - 1;        int minn = query(lb, rb);        ans += max(0, minn - height[lb - 1]);        printf("%lld\n", ans);    }    return 0;}

hdu 6195 cable cable cable(water)

题意:给K个A物品,M个B物品(KM2311)。使得任意选择K个B物品。都能和A物品产生一一对应的关系。问你需要在AB之间连至少连几条边。

思路:最理想的情况应该是每个A物品只连接M-(K-1)个B物品,剩下的K-1个B物品和剩下K-1个物品各连一条边即可,那么一共有K个A物品,所以答案就是K*(M-K+1)。

#include <bits/stdc++.h>using namespace std;const int maxn = 1e5 + 5;const int INF = 0x3f3f3f3f;int a[maxn], dp[maxn];int main(){    long long n, k;    while(~scanf("%lld%lld", &n, &k))    {        printf("%lld\n", k * (n - k + 1));    }    return 0;}

hdu 6197 array array array(LIS)

题意:问你能否做到,取出k个数字后,原序列a是一个非递增或非递减的序列。(kn1e5

思路:很显然如果把除了LIS外的数字删完了,显然是不满足条件的,所以最多可以删max(n-LID,n-LDS(最长递减子序列))。

#include <bits/stdc++.h>using namespace std;const int maxn = 1e5 + 5;const int INF = 0x3f3f3f3f;int a[maxn], dp[maxn];int main(){    int T;    scanf("%d", &T);    while(T--)    {        int n, k;        scanf("%d%d", &n, &k);        for(int i = 0; i < n; i++) scanf("%d", &a[i]);        for(int i = 0; i <= n; i++) dp[i] = INF;        for(int i = 0; i < n; i++)            *lower_bound(dp, dp + n, a[i]) = a[i];        int x = lower_bound(dp, dp + n, INF) - dp;        x = n - x;        for(int i = 0; i < n; i++)  a[i] = -a[i];        for(int i = 0; i <= n; i++) dp[i] = INF;        for(int i = 0; i < n; i++)            *lower_bound(dp, dp + n, a[i]) = a[i];        int y = lower_bound(dp, dp + n, INF) - dp;        y = n - y;        if(min(x, y) <= k)  puts("A is a magic array.");        else puts("A is not a magic array.");    }    return 0;}

hdu 6198 number number number(矩阵快速幂)

题意:把n表示成最少数量的斐波那契数加和。问你,最小的不能被k个斐波那契数表示的数字是多少。(K1e9

思路:51nod上有个题给过背景,任何一个数都能被斐波那契数表示。所以只要贪心的去放,就能求得每个数字的斐波那契表示至少需要几个数字。打出来的表只有:4、12、33、88的时候,正准备加大数字多打点表,龙叔突然一句我找到规律了。…..%%%%%%%%。orz。大概就是Fn=3Fn1Fn2+1。推个矩阵快速幂就好啦!这数感我是真的服。

#include <bits/stdc++.h>using namespace std;typedef long long LL;const LL mod = 998244353;const int mSize = 3;struct Matrix{    long long v[mSize][mSize];    friend Matrix operator* (const Matrix& a, const Matrix& b)    {        Matrix c;        memset(c.v, 0, sizeof(c.v));        for (int k = 0; k < mSize; k++)        {            for (int i = 0; i < mSize; i++)            {                if(a.v[i][k] == 0)  continue;                for (int j = 0; j < mSize; j++)                {                    if(b.v[k][j] == 0)  continue;                    c.v[i][j] += a.v[i][k] * b.v[k][j] % (mod);                    c.v[i][j]  %= (mod);                }            }        }        return c;    }    friend Matrix operator^ (Matrix x, LL n)    {        Matrix ans;        for (int i = 0; i < mSize; i++)            for (int j = 0; j < mSize; j++)                ans.v[i][j] = (i == j);        while (n)        {            if (n & 1)                ans = ans * x;            x = x * x;            n >>= 1;        }        return ans;    }};int main(){    long long k;    while(~scanf("%lld", &k))    {        if(k == 1)  puts("4");        else if(k == 2) puts("12");        else        {            Matrix mat;            mat.v[0][0] = 3, mat.v[0][1] = 1, mat.v[0][2] = 0;            mat.v[1][0] = mod-1, mat.v[1][1] = 0, mat.v[1][2] = 0;            mat.v[2][0] = 1, mat.v[2][1] = 0, mat.v[2][2] = 1;            mat = mat ^ (k - 2);            long long ans = (12LL * mat.v[0][0] % mod + 4LL * mat.v[1][0] % mod ) % mod+ 1LL * mat.v[2][0] % mod;            printf("%lld\n", ans % mod);        }    }    return 0;}

hdu 6199 gems gems gems

题意:两个人轮流拿物品,每一个物品都有一个价值,第 i 次拿 k 个那么 第 i + 1 次 可以拿 k 或者 k + 1 个。Alice想让价值差最大,Bob想让价值差最小,Alice先手,问最后差是多少。

思路:其实可以看做两个人都想自己拿的最多。所以直接dp就好啦。dp[i][j]:此人先手从第i个物品开始拿j个获得的最大值是多少。dp[i][j] = sum[i + j - 1] - sum[i - 1] - max(dp[i + j][j], dp[i + j + 1][j + 1]);

考虑到每个人每次最多拿n个,因为要想拿的最多,极端情况就是1+2+3…+x <= n。所以复杂度就是Onn
但是这题卡了内存,所以要注意使用int或者滚动数组。第一点,我们注意到dp方程最多只使用了两百多轮,所以可以去开一个[300][200]的数组,每次%一下两百多就行了。比如dp[i + j][k],就是dp[(i + j) % 255][k]。只要使用255的数组即可。
另外还有一个小技巧,如果把数组滚成2的幂次个的话,可以使用&,更快!。

#include <bits/stdc++.h>using namespace std;int n, T;int dp[300 + 5][200 + 5];const int cycle = 255;int sum[20000 + 5];//dp[i][j] = sum[i + j - 1] - sum[i - 1] - max(dp[i + j][j], dp[i + j + 1][j + 1]);int main(){    scanf("%d", &T);    while(T--)    {        scanf("%d", &n);        for(int i = 1, x; i <= n; i++)            scanf("%d", &x), sum[i] = sum[i - 1] + x;        int ans = -0x3f3f3f3f;        for(int i = n; i >= 1; i--)        {            for(int j = min(200, n - i + 1); j >= 1; j--)            {                if(i + j - 1 > n)   continue;                dp[i & cycle][j] = sum[i + j - 1] - sum[i - 1];                if(i + j - 1 + j <= n)                {                    int mx = dp[(i + j) & cycle][j];                    if(i + j + j <= n)  mx = max(mx, dp[(i + j) & cycle][j + 1]);                    dp[i & cycle][j] = dp[i & cycle][j] - mx;                }                if(i == 1 && j <= 2)    ans = max(ans, dp[i & cycle][j]);            }        }        printf("%d\n", ans);//dp[1][1],dp[1][2]可能某个没有解    }    return 0;}

hdu 6201 transaction transaction transaction(建图 or 树dp)

题意:给出一棵树,每个点都有点权,每条边有边权,问取两个点,使得T-S-sumw为最大(T为终点的点权,S为起点的点权,sumw为S到T的路径长度),可取相同的点。(100000

思路:
  大概就是把所有点的连一个超级源点和超级汇点,然后权值分别为点的点劝的正值和负值,然后那个树原样连,求一个最短路,就出来啦~。

#include <bits/stdc++.h>using namespace std;const int maxn = 100000 + 50;const int INF = 0x3f3f3f3f;int a[maxn];struct edge{int to, cost;};vector<edge>G[maxn];int inq[maxn], d[maxn];long long spfa(int s, int t, int n){    for(int i = 0; i <= n + 1; i++) inq[i] = 0, d[i] = INF;    d[s] = 0, inq[s] = true;    queue<int> q;    q.push(s);    while(!q.empty())    {        int u = q.front();q.pop();        inq[u] = false;        for(auto o : G[u])        {            if(d[o.to] > d[u] + o.cost)            {                d[o.to] = d[u] + o.cost;                if(!inq[o.to])  q.push(o.to), inq[o.to] = 1;            }        }    }    return d[n + 1];}int main(){    int T;    scanf("%d", &T);    while(T--)    {        int n;        scanf("%d", &n);        for(int i = 0; i <= n + 1; i ++ )G[i].clear();        for(int i = 1; i <= n; i++) scanf("%d", &a[i]), a[i] = -a[i];        for(int i = 1; i <= n; i++) G[0].push_back({i, a[i]}), G[i].push_back({n + 1, -a[i]});        for(int i = 0, x, y, c; i < n - 1; i++)        {            scanf("%d%d%d", &x, &y, &c);            G[x].push_back({y, c});            G[y].push_back({x, c});        }        long long ans = spfa(0, n + 1, n);        printf("%lld\n", -ans);    }    return 0;}

  emmmm赛后梦天说这题能树DP,满世界也确实都是树DP(居然还有人用费用流,雾)补一个树dp的码。树dp的话就是记录一个,在这个结点买了书,拥有最多的钱,和在这个结点卖了书,拥有最多的钱。
  

#include <bits/stdc++.h>using namespace std;const int maxn = 100000 + 50;const int INF = 0x3f3f3f3f;int a[maxn];struct edge{int to, cost;};vector<edge>G[maxn];long long dp[maxn][2];long long ans;void dfs(int u, int fa){    dp[u][0] = -a[u];    dp[u][1] = a[u];    for(auto o : G[u])        {        int v = o.to, w = o.cost;        if(v == fa)    continue;        dfs(v, u);        dp[u][0] = max(dp[u][0], dp[v][0] - w);        dp[u][1] = max(dp[u][1], dp[v][1] - w);    }    ans = max(ans, dp[u][0] + dp[u][1]);}int main(){    int T;    scanf("%d", &T);    while(T--)    {        int n;        scanf("%d", &n);        for(int i = 0; i <= n + 1; i ++ )G[i].clear();        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);        for(int i = 0, x, y, c; i < n - 1; i++)        {            scanf("%d%d%d", &x, &y, &c);            G[x].push_back({y, c});            G[y].push_back({x, c});        }        ans = -1e18;        dfs(1, -1);        printf("%lld\n", ans);    }    return 0;}

hdu 6205 card card card(water)

题意:给定数列a,b,它们都有n个数,保证a数列的和等于b数列的和,计算b[i]-a[i]的前缀和c[i]。i从1到n,如果c[i]大于等于0,则可以往下继续取,否则停止,取得的结果是停止时的i或者n。但有一个操作,就是把第一组(a[1],b[1])移至末尾,后面的元素都向前一位,问进行若干操作之后能够取得的最大值时需要的最小操作数。

思路:就随便搞搞啊?特判一个尾巴能和头接起来的就可以了。类似最大字段和的思路?

#include <bits/stdc++.h>using namespace std;const int maxn = 1e6 + 6;int a[maxn], b[maxn], sum[maxn];int main(){    int n;    while(~scanf("%d", &n))    {        for(int i = 1; i <= n; i++)  scanf("%d", &a[i]), sum[i] = sum[i - 1] + a[i];        for(int i = 1; i <= n; i++)  scanf("%d", &b[i]), b[i] = a[i] - b[i];        int st = 1, ansPos = 1, tempSum = 0, ans = -0x3f3f3f3f;        vector<int>vec;        for(int i = 1; i <= n; i++)        {            tempSum += b[i];            if(tempSum < 0)            {                if(st != i)                {                    int temp = sum[i] - sum[st - 1];                    if(temp > ans)                        ans = temp, ansPos = st;                    vec.push_back(temp);                }                st = i + 1;                tempSum = 0;            }        }        int p;        for(p = 1; p < st; p++)        {            tempSum += b[p];            if(tempSum < 0) break;        }        int temp = sum[n] - sum[st - 1] + sum[p];        if(temp > ans)   ansPos = st;        printf("%d\n", ansPos - 1);    }    return 0;}