2013 Multi-University Training Contest 4

HDU-4632 Palindrome subsequence(区间dp)

题意:求一个字符串有多少个子序列是回文串。

题解:dp[i][j]表示i到j有多少的自序列是回文串,具体转移看代码

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int mod = 10007;
char s[1010];
int dp[1010][1010];
int n;

int main(){
    int T;
    scanf("%d", &T);
    for(int t = 1; t<=T; t++){
        memset(dp, 0, sizeof(dp));
        scanf("%s", s+1);
        n = strlen(s+1);
        for(int j = 1; j<=n; j++){
            dp[j][j] = 1;
            for(int i = j-1; i>=1; i--){
                if(j-1>=i+1){
                    dp[i][j] = (dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+mod)%mod;
                    if(s[i] == s[j]){
                        dp[i][j] = (dp[i][j]+dp[i+1][j-1]+1)%mod;
                    }
                }
                else{
                    if(s[i] == s[j]){
                        dp[i][j] = 3;
                    }
                    else{
                        dp[i][j] = 2;
                    }
                }
            }
        }

        printf("Case %d: %d\n", t, dp[1][n]%mod);
    }

    return 0;
}

 

HDU-4635 Strongly connected(强连通缩点)

题意:给一个有向图,问能加的最大边数,使得加完边之后的图任然不是强连通。

题解:显然要使加的边数最大,那么加完后的图应该只剩两个强连通分量,设其中一个强连通分量点的个数为x,另一个为y,则x+y=n。然后考虑整个图最多的边数是多少。在一个强连通分量中,边数最多为x*(x-1),另一个为y*(y-1),然后是所有的x点向所有的y点连边(或者反过来,因此一个强连通分量必然是出度为0或者入度为0)有x*y条边,所以总边数便是x*(x-1)+y*(y-1)+x*y=n*n-n-x*y。我们找一个最小的x*y,然后总边数-m就是答案了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define ll long long
using namespace std;

const int maxn = 100010;

int n, m;
vector<int> G[maxn];
vector<int> scc[maxn];
int dfn[maxn], low[maxn], sta[maxn], ins[maxn], c[maxn], num, top, cnt;
int in[maxn], out[maxn];

int Max(ll a, ll b){
    return a>b?a:b;
}

void init(){
    for(int i = 1; i<=maxn; i++){
        G[i].clear();
        scc[i].clear();
    }
    num = 0; top = 0; cnt = 0;
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(sta, 0, sizeof(sta));
    memset(ins, 0, sizeof(ins));
    memset(c, 0, sizeof(c));
    memset(in, 0, sizeof(in));
    memset(out, 0, sizeof(out));
}

void tarjan(int x){
    dfn[x] = low[x] = ++num;
    sta[++top] = x; ins[x] = 1;
    for(int i = 0; i<G[x].size(); i++){
        int v = G[x][i];
        if(!dfn[v]){
            tarjan(v);
            low[x] = min(low[x], low[v]);
        }
        else if(ins[v]){
            low[x] = min(low[x], low[v]);
        }
    }
    if(dfn[x] == low[x]){
        cnt++; int y;
        do{
            y = sta[top--], ins[y] = 0;
            c[y] = cnt, scc[cnt].push_back(y);
        }while(x!=y);
    }
}

int main(){
    int T;
    scanf("%d", &T);
    for(int t = 1; t<=T; t++){
        scanf("%d %d", &n, &m);
        init();
        for(int i = 1; i<=m; i++){
            int u, v;
            scanf("%d %d", &u, &v);
            G[u].push_back(v);
        }
        for(int i = 1; i<=n; i++){
            if(!dfn[i]) tarjan(i);
        }
        printf("Case %d: ", t);
        if(cnt == 1){
            printf("-1\n");
            continue;
        }
        for(int i = 1; i<=n; i++){
            for(int j = 0; j<G[i].size(); j++){
                int v = G[i][j];
                if(c[i]!=c[v]){
                    out[c[i]]++; in[c[v]]++;
                }
            }
        }


        ll ans = 0;

        for(int i = 1; i<=cnt; i++){
            if(in[i]&&out[i]) continue;
            int tmp = scc[i].size();
            ans = Max(ans, (ll)n*n-(ll)n-(ll)(n-tmp)*tmp-(ll)m);
        }
        printf("%lld\n", ans);

    }

    return 0;
}

HDU-4638 Group(莫队)

题意:有一个1到n的序列,问在某一个区间内,能将其中的数字最少分为几组,要求每一组的数字都是连续的。

题解:裸的莫队。

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define ll long long
struct Query{
    int L,R,id;
}q[MAXN];
int s,vis[MAXN],id[MAXN];
ll ans[MAXN];
ll res;
bool cmp(Query a,Query b){
    if(a.L/s == b.L/s) return a.R<b.R;
    return a.L/s < b.L/s;
}
void add(int idx){//加入编号为idx的点
    if(vis[idx-1] && vis[idx+1]) res--;//如果两边都是连续的,那么组可以减一
    else if(!vis[idx-1] && !vis[idx+1]) res++;//如果两边都是断开的,那么加入该点可以使组加一
    vis[idx]=1;
}
void dec(int idx){
    if(vis[idx-1] && vis[idx+1]) res++;
    else if(!vis[idx-1] && !vis[idx+1]) res--;
    vis[idx]=0;
}
int main(){
    int n,m,t;
    scanf("%d",&t);
    while(t--){
        memset(vis,0,sizeof vis);
        scanf("%d%d",&n,&m);
        s=(int)sqrt(n);
        for(int i=1;i<=n;i++)
            scanf("%d",&id[i]);
        for(int i=0;i<m;i++){
            scanf("%d%d",&q[i].L,&q[i].R);
            q[i].id=i;
        }
        sort(q,q+m,cmp);
        int L=1,R=0;
        res=0;
        for(int i=0;i<m;i++){
            while(R<q[i].R){
                R++;
                add(id[R]);
            }
            while(R>q[i].R){
                dec(id[R]);
                R--;
            }
            while(L<q[i].L){
                dec(id[L]);
                L++;
            }
            while(L>q[i].L){
                L--;
                add(id[L]);
            }
            ans[q[i].id]=res;
        }
        for(int i=0;i<m;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

HDU-4639 Hehe(找规律+斐波那契)

题意:给一个字符串,hehe能有wqnmlgb替换,问该字符串最终能有几种表达方式。

题解:发现几个hehe连在一起的表达方式种数符合斐波那契数列,然后最后乘法原理就行。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define ll long long
using namespace std;

const int mod = 10007;

char s[20010];
int f[20010];
int len;

vector<int> G;

void init(){
    f[0] = 1;
    f[1] = 1;
    f[2] = 2; f[3] = 3;
    for(int i = 4; i<=20005; i++){
        f[i] = (f[i-1]%mod+f[i-2]%mod)%mod;
    }
}

int main(){
    init();
    int T;
    scanf("%d", &T);
    for(int t = 1; t<=T; t++){
        G.clear();
        scanf("%s", s+1);
        len = strlen(s+1);
        int i = 1;
        int tmp = 0;
        while(i<=len+1){
            if(s[i] == 'h' && s[i+1] == 'e'){
                tmp++;
                i+=2;
            }
            else{
                if(tmp) G.push_back(tmp);
                i++;
                tmp = 0;
            }
        }
        ll ans = 1;
        for(i = 0; i<G.size(); i++){
           ans = ((ans%mod)*(f[G[i]]%mod))%mod;
        }
        printf("Case %d: %lld\n", t, ans%mod);
    }

    return 0;
}

 HDU-4642 Fliping game(矩阵博弈)

题意:给一个01矩阵,每次可以选1的块作为左上角,然后将其右下部分的矩阵全部翻转,最后不能翻转的人输。

题解:对于最右下角的那个矩阵,每次都会将其翻转,先手翻转为1的话永远赢不了,翻转为0的话,后手如果已经不能翻转为1,先手就赢了;如果后手能翻转为1,那么游戏继续。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define ll long long
using namespace std;

int ma[110][110];
int n, m;
int main(){
    int T;
    cin>>T;
    while(T--){
        cin>>n>>m;
        for(int i = 1; i<=n; i++){
            for(int j = 1; j<=m; j++){
                cin>>ma[i][j];
            }
        }
        if(ma[n][m] == 0) cout<<"Bob"<<endl;
        else cout<<"Alice"<<endl;
    }

    return 0;
}

 

 

HDU-4641 K-string(后缀自动机)

题意:给一个字符串和k,有两种操作,每次往字符串末尾加一个字符,每次询问字符串中出现至少k次的子串个数是多少?

题解:做的第一道后缀自动机,看了两天算是模模糊糊看懂了。

学习的博客:

https://blog.csdn.net/qq_35649707/article/details/66473069 (有完整的证明很详细,但是我看不懂)

https://blog.csdn.net/cdy1206473601/article/details/80206582 (比较简单的构造讲解)

https://blog.csdn.net/liyuanshuo_nuc/article/details/53561527 (hihocoder讲的基本概念,看了这个才懂)

https://blog.csdn.net/vocaloid01/article/details/82902180 (hihocoder讲的构造方法)

首先记录一下一些性质:

后缀自动机的每个状态定义的是在 一些位置的集合 结束的 子串的集合

每个状态的子串个数=从起始点到该点的路径数=step[p]-step[pre(p)]   p表示当前状态

每个状态的子串都是该状态中最长的那个子串的后缀,并且是连续的,直到断开为止。断开的含义是某一个较小的后缀在其他位置结束了,这时候新开一个状态来存他。所以沿着pre指针走,就是走到最大的那个断开的后缀所在的状态。

关于这道题,我们用一个num数组存当前状态中所有子串出现的次数。在构建自动机进行状态复制的时候,num记得也要复制。复制的含义相当于,将某一个状态的子串在某一处断开,较小的后缀送给新的复制的状态。

新加一个字母后,得到的最新状态就是字符串本身以及一些未断开的后缀,沿着pre走,每次走到某个状态,这个状态的num加一。这里可以理解为,这些后缀既在字符串的末尾出现(所以加1),又在别的地方出现。

当到达某个状态是通过加1得到大于等于k后,答案加上该状态的所有子串的个数,也就是step[p]-step[pre(p)] ,p表示当前状态

可以发现,沿着pre走,num值是递增的,因为,较短的后缀一定会在更多的地方出现,大于等于k后,他们本身就已经为答案做出贡献了,就不能再继续走下去了。

但感觉还是很抽象.....

代码:

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

const int maxn = 500010;

int n, m, k;

int ch[maxn][26];
int pre[maxn], step[maxn];
int last, id, ans;
int num[maxn];
void init(){
    ans = last = id = 0;
    memset(pre, 0, sizeof(pre));
    memset(num, 0, sizeof(num));
    memset(step, 0, sizeof(step));
    for(int i = 0; i<=n+m; i++){
        memset(ch[i], 0, sizeof(ch[i]));
    }
    memset(ch[0], -1, sizeof(ch[0]));
    pre[0] = -1; step[0] = 0;
}
void Insert(int c){
    int p = last, np = ++id;
    step[np] = step[p]+1;
    memset(ch[np], -1, sizeof(ch[np]));
    num[np] = 0;
    while(p!=-1 && ch[p][c] == -1){
        ch[p][c] = np, p = pre[p];
    }
    if(p == -1) pre[np] = 0;
    else{
        int q = ch[p][c];
        if(step[q]!=step[p]+1){
            int nq = ++id;
            memcpy(ch[nq], ch[q], sizeof(ch[q]));
            num[nq] = num[q];
            step[nq] = step[p]+1;
            pre[nq] = pre[q];
            pre[np] = pre[q] = nq;
            while(p!=-1 && ch[p][c] == q){
                ch[p][c] = nq, p = pre[p];
            }
        }
        else pre[np] = q;
    }
    last = np;
    while(np!=-1 && num[np]<k){
        num[np]++;
        //cout<<"! "<<np<<" "<<num[np]<<endl;
        if(num[np]>=k){
            ans+=step[np]-step[pre[np]];
            //cout<<"? "<<np<<" "<<pre[np]<<" "<<step[pre[np]]<<" "<<step[np]<<endl;
        }
        np = pre[np];
    }
}

char str[250010];

int main(){
    while(scanf("%d %d %d", &n, &m, &k)!=EOF){
        scanf("%s", str);
        init();
        int len = strlen(str);
        for(int i = 0; i<len; i++){
            Insert(str[i]-'a');
        }
        /*cout<<id<<endl;
        for(int i = 0; i<=id; i++){
            cout<<pre[i]<<" "<<step[i]<<endl;
        }

        for(int i = 0; i<=id; i++){
            cout<<i<<": ";
            for(int j = 0; j<5; j++){
                cout<<ch[i][j]<<" ";
            }
            cout<<endl;
        }
        cout<<ans<<endl;*/
        while(m--){
            int ch; char c;
            cin>>ch;
            if(ch == 2){
                cout<<ans<<endl;
            }
            if(ch == 1){
                cin>>c;
                Insert(c-'a');
            }
        }


    }

    return 0;
}

 

posted @ 2018-10-28 22:05  grimcake  阅读(152)  评论(0编辑  收藏  举报