湖南雅礼培训 1.4

模拟赛

一、题目概览

中文题目名称

序列

轰炸

字符串

英文题目名称

sequence

bomb

string

可执行文件名

sequence

bomb

string

输入文件名

sequence.in

bomb.in

string.in

输出文件名

sequence.out

bomb.out

string.out

时间限制

1s

1s

1s

空间限制

256MB

256MB

256MB

测试点数目

10

10

10

测试点分值

10

10

10

题目类型

传统

传统

传统

比较方式

全文比较

全文比较

全文比较

是否有部分分

 

 

 

二、注意事项:

1.文件名(程序名和输入输出文件名)必须使用小写。

2.C/C++中函数main()的返回值类型必须是int,程序正常结束时的返回值必须是0。

3.开启O2优化,栈空间开大至256M。

 

序列(sequence)

【题目描述】

    给定一个1~n的排列x,每次你可以将x1~xi翻转。你需要求出将序列变为升序的最小操作次数。有多组数据。

【输入数据】

       第一行一个整数t表示数据组数。

每组数据第一行一个整数n,第二行n个整数x1~xn。

【输出数据】

每组数据输出一行一个整数表示答案。

【样例输入】

1

8

8 6 1 3 2 4 5 7

【样例输出】

       7

【数据范围】

       对于100%的测试数据,t=5,n<=25。

对于测试点1,2,n=5。

对于测试点3,4,n=6。

对于测试点5,6,n=7。

对于测试点7,8,9,n=8。

对于测试点10,n=9。

对于测试点11,n=10。

对于测试点i (12<=i<=21),n=i。

对于测试点22,23,n=22。

对于测试点24,25,n=23。

/*
    每次将n翻转到x1再翻转到xn,可以得到一个不超过2n-2步的做法。由于步数不多,我们可以使用迭代加深搜索。
    我们发现每次翻转只会改变一对相邻数对,因此对于一个状态求出相差>1的相邻数对的数量,剩余步数一定大于这个值。加上这个剪枝就能通过本题。
    时间复杂度O(能过)。
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define maxn 510
using namespace std;
int n,a[maxn],m,p;
void rev(int x){
    for(int i=1;i<=(x>>1);i++)swap(a[i],a[x+1-i]);
}
void dfs(int x,int y){
    if(x+y>m)return;
    int pos=0;
    for(int i=1;i<=n;i++)
        if(a[i]!=i){pos=i;break;}
    if(pos==0){p=1;return;}
    for(int i=n;i>=2;i--){
        int w=y+(i<n && abs(a[i]-a[i+1])==1)-(i<n && abs(a[i+1]-a[1])==1);
        rev(i);
        dfs(x+1,w);
        rev(i);
        if(p)return;
    }
}
int main(){
    freopen("sequence1.in","r",stdin);
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        int cnt=0;
        for(int i=1;i<n;i++)
            if(abs(a[i]-a[i+1])>1)cnt++;//统计相差>1的相邻数对的数量 
        m=0;
        while(1){
            p=0;
            dfs(0,cnt);
            if(p)break;
            m++;//总步数(迭代加深的层数) 
        }
        printf("%d\n",m);
    }
    return 0;
}
100分 迭代加深搜索+剪枝

 

轰炸(bomb)

【题目描述】

有n座城市,城市之间建立了m条有向的地下通道。

你需要发起若干轮轰炸,每轮可以轰炸任意多个城市。但每次轰炸的城市中,不能存在两个不同的城市i,j满足可以通过地道从城市i到达城市j。

你需要求出最少需要多少轮可以对每座城市都进行至少一次轰炸。

【输入数据】

       第一行两个整数n,m。接下来m行每行两个整数a,b表示一条从a连向b的单向边。

【输出数据】

一行一个整数表示答案。

【样例输入】

5 4

1 2

2 3

3 1

4 5

【样例输出】

       3

【数据范围】

       对于20%的数据,n,m<=10。

对于40%的数据,n,m<=1000。

对于另外30%的数据,保证无环。

对于100%的数据,n,m<=1000000。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define maxn 1000010
using namespace std;
vector<int>group[maxn];
int n,m,head[maxn],num,head2[maxn],num2,belong[maxn],g,du[maxn];
int low[maxn],dfn[maxn],top,st[maxn],cnt,sz[maxn],ans;
bool in[maxn];
struct node{
    int to,pre;
}e[maxn*2],e2[maxn*2];
void Insert(int from,int to){
    e[++num].to=to;
    e[num].pre=head[from];
    head[from]=num;
}
void Insert2(int from,int to){
    e2[++num2].to=to;
    e2[num2].pre=head2[from];
    head2[from]=num2;
}
void Tarjan(int u){
    cnt++;st[++top]=u;in[u]=1;low[u]=dfn[u]=cnt;
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].to;
        if(!dfn[v]){
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else{
            if(in[v])low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        g++;
        while(st[top]!=u){
            int x=st[top];
            top--;
            in[x]=0;
            group[g].push_back(x);
            belong[x]=g;
        }
        top--;in[u]=0;
        belong[u]=g;
        group[g].push_back(u);
    }
}
bool v[maxn];
void dfs(int now,int w){
    ans=max(ans,w);
    for(int i=head2[now];i;i=e2[i].pre){
        int to=e2[i].to;
        if(!v[to]){
            v[to]=1;
            dfs(to,w+sz[to]);
            v[to]=0;
        }
    }
}
int main(){
//    freopen("Cola.txt","r",stdin);
    freopen("bomb.in","r",stdin);freopen("bomb.out","w",stdout); 
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        Insert(x,y);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i])Tarjan(i);
    }
    for(int i=1;i<=g;i++)sz[i]=group[i].size();
    for(int i=1;i<=g;i++)
        for(int j=0;j<group[i].size();j++){
            int from=group[i][j];
            for(int k=head[from];k;k=e[k].pre){
                int to=e[k].to;
                if(belong[from]!=belong[to])
                    Insert2(belong[from],belong[to]),du[belong[to]]++;
            }
        }
    for(int i=1;i<=g;i++){
        if(du[i]==0){//ru du wei 0
            v[i]=1;
            dfs(i,sz[i]);
            v[i]=0;
        }
    }
    printf("%d",ans);
    return 0;
}
60分 Tarjan缩点+dfs
/*
    考虑无环的情况,显然答案为最长链长度,证明如下:
    1、由于最长链上的点两两不能同时轰炸,因此最优解>=最长链长度。
    2、令fi表示从i出发的最长链长度,那么在第fi轮轰炸i,可以得到一个恰
    容易发现将一个大小为x的强连通分量替换成一个长度为x的链,所有点两两之间的连通关系不变。好为最长链长度的方案,因此最优解<=最长链长度。
    时间复杂度O(n+m)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define maxn 1000010
using namespace std;
vector<int>group[maxn];
int n,m,head[maxn],num,head2[maxn],num2,belong[maxn],g,du[maxn],f[maxn];
int low[maxn],dfn[maxn],top,st[maxn],cnt,sz[maxn],ans;
bool in[maxn];
struct node{
    int to,pre;
}e[maxn*2],e2[maxn*2];
void Insert(int from,int to){
    e[++num].to=to;
    e[num].pre=head[from];
    head[from]=num;
}
void Insert2(int from,int to){
    e2[++num2].to=to;
    e2[num2].pre=head2[from];
    head2[from]=num2;
}
void Tarjan(int u){
    cnt++;st[++top]=u;in[u]=1;low[u]=dfn[u]=cnt;
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].to;
        if(!dfn[v]){
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else{
            if(in[v])low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        g++;
        while(st[top]!=u){
            int x=st[top];
            top--;
            in[x]=0;
            group[g].push_back(x);
            belong[x]=g;
        }
        top--;in[u]=0;
        belong[u]=g;
        group[g].push_back(u);
    }
}
queue<int>q; 
int qread(){
    int i=0,j=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')j=-1;ch=getchar();}
    while(ch<='9'&&ch>='0')i=i*10+ch-'0',ch=getchar();
    return i*j;
}
int main(){
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++){
        x=qread();y=qread();
        Insert(x,y);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i])Tarjan(i);
    }
    for(int i=1;i<=g;i++)sz[i]=group[i].size();
    for(int i=1;i<=g;i++)
        for(int j=0;j<group[i].size();j++){
            int from=group[i][j];
            for(int k=head[from];k;k=e[k].pre){
                int to=e[k].to;
                if(belong[from]!=belong[to])
                    Insert2(belong[from],belong[to]),du[belong[to]]++;
            }
        }
    for(int i=1;i<=g;i++)
        if(du[i]==0)q.push(i);
    while(!q.empty()){
        int now=q.front();q.pop();
        f[now]+=sz[now];
        ans=max(ans,f[now]);
        for(int i=head2[now];i;i=e2[i].pre){
            int to=e2[i].to;
            f[to]=max(f[to],f[now]);
            du[to]--;
            if(!du[to])q.push(to);
        }
    }
    printf("%d",ans);
    return 0;
}
100分 Tarjan缩点+拓排

 

 

字符串(string)

【题目描述】

给定正整数m以及n个01串s1~sn,你需要求出长度为2m的反对称的包含这n个01串作为子串的01串的个数。对998244353取模。

一个01串s是反对称的当且仅当它对于1<=i<=|s|都满足s[i]≠s[|s|-i+1]。

【输入数据】

第一行两个整数n,m。接下来n行每行一个字符串s1~sn。

【输出数据】

一行一个整数表示答案。

【样例输入】

       2 3

       011

       001

【样例输出】

       4

【数据范围】

对于10%的数据,m<=15。

对于40%的数据,n<=4,|si|<=20。

对于60%的数据,n<=6,|si|<=30,m<=100。

对于另外20%的数据,n=1。

对于100%的数据,n<=6,|si|<=100,m<=500。

/*
    每个串有4种情况:在前一半出现,在后一半出现,跨越中间并且在前一半的长度大,跨越中间并且在后一半的长度大。
    前两种情况只要在正串和反串(翻转+取反)的AC自动机上状压dp即可。
    对于第三种情况,我们需要对正串的所有超过一半的前缀判断如果前一半以它结尾是否会构造出这个串,第四种情况就对反串这样处理一下。dp结束的时候,将字符串的出现情况对结束位置对应的跨越中间的字符串取个并。
    时间复杂度O(2nn|si|m)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 998244353
using namespace std;
int n,m,x[1210][10],f[510][1210][70],g[1210],p;
char s[110];
bool pd(int j,int n){
    for(int i=j+1;i<n;i++)
        if(j-(i-j)+1<0||s[j-(i-j)+1]==s[i])return 0;
    return 1;
}
void add(int k){
    int n=strlen(s);
    int i=0;
    for(int j=0;j<n;j++){
        if(!x[i][s[j]-'0']){
            x[i][s[j]-'0']=++p;
            x[p][2]=i;
            x[p][5]=s[j]-'0';
        }
        i=x[i][s[j]-'0'];
        if(pd(j,n))x[i][4]|=k;
    }
    x[i][3]|=k;
}
int main(){
    scanf("%d%d",&n,&m);
    int k;
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        add(1<<(i-1));
        k=strlen(s);
        for(int j=0;k-1-j>j;j++)swap(s[j],s[k-1-j]);
        for(int j=0;j<k;j++)s[j]^=1;//翻转并取反
         
        add(1<<(i-1));
    }
    k=1;g[1]=0;
    for(int u=1;u<=k;u++){
        int i=g[u];
        if(x[i][0])g[++k]=x[i][0];
        if(x[i][1])g[++k]=x[i][1];
        if(!i)continue;
        int j;
        for(j=x[x[i][2]][6];j&&!x[j][x[i][5]];j=x[j][6]);
        if(x[j][x[i][5]]&&x[j][x[i][5]]!=i)x[i][6]=x[j][x[i][5]];
        if(!x[i][0])x[i][0]=x[x[i][6]][0];
        if(!x[i][1])x[i][1]=x[x[i][6]][1];
        for(int j=3;j<=5;j++)x[i][j]|=x[x[i][6]][j];
    }
    f[0][0][0]=1;
    for(int i=1;i<=m;i++)
        for(int j=0;j<=p;j++)
            for(k=0;k<(1<<n);k++)
                for(int l=0;l<=1;l++){
                    int u=x[j][l];
                    int v=k|x[u][3];
                    f[i][u][v]=(f[i][u][v]+f[i-1][j][k])%mod;
                }
    k=0;
    for(int i=0;i<=p;i++)
        for(int j=0;j<(1<<n);j++)
        if((j|x[i][4])==(1<<n)-1)
        k=(k+f[m][i][j])%mod;
    printf("%d\n",k);
}
100分 ac自动机+dp+后缀数组

 

posted @ 2018-01-04 15:06  Echo宝贝儿  阅读(568)  评论(0编辑  收藏  举报