CROC 2016 - Elimination Round

<题目链接>

 

 

A - Amity Assessment (思维 || 暴搜)

题目大意:
就是给定两个2*2的矩阵,矩阵上的每个位置给定一个初始字符,'X'代表空地,能够将其它字符在空地上移动,问你这两个矩阵经过移动后,能否变成相同的矩阵(注意,矩阵不具有翻转和对称相等的性质,即,必须所有相同坐标的字符相同,这两个矩阵才能算完全相同)。

解题分析:

就是一道思维题,但是我当时是用暴搜A的,有点浪费时间。

#include<bits/stdc++.h>
using namespace std;
int main(){
    string a,a1,a2,b,b1,b2;
    cin>>a1>>a2>>b1>>b2;
    swap(a2[0],a2[1]);a = a1+a2;   
    swap(b2[0],b2[1]);b = b1+b2;     //得到这两个矩阵沿一个方向走的字符串(顺时针)
    a.erase(a.find('X'),1);
    b.erase(b.find('X'),1);
    a=a+a;
    if((a).find(b)!= string::npos)puts("YES");
    else puts("NO");
}
思维

 

 

我的写法,当'X'数量为1的时候,对一个2*2的矩阵进行暴搜重排,看是否能够得到第二个矩阵。

#include <bits/stdc++.h>
using namespace std;

#define rep(i,s,t) for(int i=s;i<=t;i++)
char mpa[5][5],mpb[5][5];
bool ans=false;

inline bool check(){
    for(int i=1;i<=2;i++){
        for(int j=1;j<=2;j++){
            if(mpa[i][j]!=mpb[i][j])return false;
        }
    }return true;
}

map<string,int>vis;      //进行状态标记

inline bool mark(){       //表示当前状态是否已经被标记过
    string str;
    rep(i,1,2) rep(j,1,2) str+=mpa[i][j];
    return vis[str];   
}//记录那个状态是否走过,走过返回true

inline void do_mark(){
    string str;
    rep(i,1,2) rep(j,1,2) str+=mpa[i][j];
    vis[str]=1;
}//进行状态标记

inline void cg(int x1,int y1,int x2,int y2){      //对应位置字符进行替换
    char c1=mpa[x1][y1],c2=mpa[x2][y2];
    mpa[x1][y1]=c2,mpa[x2][y2]=c1;
}

void dfs(){
    if(check()){ ans=true;return; }
    rep(i,1,2) {
        rep(j,1,2){
            if(mpa[i][j]=='X')continue;     //x不能自己动
            if(i+1<=2&&mpa[i+1][j]=='X'){
                cg(i,j,i+1,j);
                if(!mark()){
                    do_mark();
                    dfs();
                }
                cg(i,j,i+1,j);        //将这种情况还原
            }
            if(i-1>=1&&mpa[i-1][j]=='X'){
                cg(i,j,i-1,j);
                if(!mark()){
                    do_mark();
                    dfs();
                }
                cg(i,j,i-1,j);
            }
            if(j-1>=1&&mpa[i][j-1]=='X'){
                cg(i,j,i,j-1);
                if(!mark()){
                    do_mark();
                    dfs();
                }
                cg(i,j,i,j-1);
            }
            if(j+1<=2&&mpa[i][j+1]=='X'){
                cg(i,j,i,j+1);
                if(!mark()){
                    do_mark();
                    dfs();
                }
                cg(i,j,i,j+1);
            }
        }
    }
}

int main(){
    string str1,str2;
    int nx1=0,nx2=0;
    for(int i=1;i<=2;i++){
        scanf("%s",mpa[i]+1);
        for(int j=1;j<=2;j++){
            if(mpa[i][j]=='X')nx1++;
            str1+=mpa[i][j];
        }
    }

    for(int i=1;i<=2;i++){
        scanf("%s",mpb[i]+1);
        for(int j=1;j<=2;j++){
            if(mpb[i][j]=='X')nx2++;
            str2+=mpb[i][j];
        }
    }    
    sort(str1.begin(),str1.end());
    sort(str2.begin(),str2.end());

    if(str1!=str2)return puts("NO"),0;
    if(nx1==0){     //no x
        for(int i=1;i<=2;i++){
            for(int j=1;j<=2;j++){
                if(mpa[i][j]!=mpb[i][j])return puts("NO"),0;
            }
        }return puts("YES"),0;
    }
    if(nx2>=2)return 0*puts("YES");
    if(nx1==1){
        if(check())return puts("YES"),0;
        //对第一个矩阵进行变换
        do_mark(); 
        dfs();
        if(ans)puts("YES");
        else puts("NO");
    }
}
搜索

 

 

 

 

 

 

B - Mischievous Mess Makers (找规律)

题目大意:
给定n、k,表示一个为1~n的初始序列,现在允许交换其中的元素k次,问你交换后,该序列所能得到的最大逆序数的个数。

解题分析:

思维,找规律,每次肯定是将第$i$个元素和第$n-i$个元素进行互换,即,序列未交换部分的最小、最大元素。$2*(n-2)+1$为交换1和n位置的元素产生的逆序数,然后每次n的数量都会-2(即未交换的序列的元素个数),所以每轮增加的逆序数都是在原来的基础上-4.

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    int n,k;scanf("%d%d",&n,&k);
    ll ans=0,num=2*(n-2)+1;
    while(k>0 && num>0){
        ans+=num;
        num-=4;
        k--;
    }printf("%lld\n",ans);
}
View Code

 

 

 

 

 

 

 

C - Enduring Exodus (二分答案)

题目大意:
给定一个01序列,0表示能够在该位置放东西,1不能,现在要在这个序列中放1个人和k头牛,问你这k头牛距这个人的位置的差值的最小最大值是多少。

解题分析:
二分答案。先预处理一下前缀和,表示前$i$个中0的个数,$O(n)$枚举这个人所在的位置,然后二分答案,枚举这个最大值,再利用前缀和判断是否符合即可。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5+5;
int n,k;
int sum[N];char s[N];

int main(){
    cin>>n>>k;    
    scanf("%s",s+1);
    for(int i=1;i<=n;i++){
        if(s[i]=='0')sum[i]=sum[i-1]+1;
        else sum[i]=sum[i-1];
    }
    int ans=1e9;
    for(int pos=1;pos<=n;pos++){      //枚举人所站的位置
        if(s[pos]=='1')continue;
        int l=1,r=n;
        while(l<=r){        //二分答案,最远的距离
            int mid = l+r>>1;
            int ri=min(n,pos+mid),le=max(0,pos-mid);
            int num=sum[ri]-sum[le-1];
            if(num>=k+1){
                ans=min(ans,mid);r=mid-1;
            }else l=mid+1;
        }
    }    
    printf("%d\n",ans);
}
View Code

 

 

 

 

 

D - Robot Rapping Results Report (拓扑排序+二分答案)

题目大意:
有n个人,现在给你他们之间的m关系,a b关系表示a打败了b,关系能够传递,问你最少只需要前几对关系,就能够唯一确定他们之间的等级。

解题分析:

二分枚举前x对关系,然后根据前x对关系建图,跑一遍拓扑排序,如果任何时候,队列中都只有一个元素,表示能够确定他们之间的关系。

复杂度:$O(nlogn)$

#include <bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T&x){     
  x=0;int f=1;char ch=getchar();
  while(ch<'0' ||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); }
  while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
  x*=f;
}
#define clr(a,b) memset(a,b,sizeof a)
#define pb push_back
#define REP(i,s,t) for(int i=s;i<=t;i++)
const int N = 1e5+5;

int n,m,a[N],b[N],ind[N];
vector<int>G[N];

inline void init(){
    REP(i,0,n)G[i].clear(),ind[i]=0;
}
bool TopoSort(){
    queue<int>q;
    REP(i,1,n) if(!ind[i]){
        q.push(i);
    }
    while(!q.empty()){
        if(q.size()>1)return false;
        int u=q.front();q.pop();
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i];
            --ind[v];
            if(ind[v]==0)q.push(v);
        }
    }
    return true;
}
bool check(int x){
    init();
    REP(i,1,x){
        int u=a[i],v=b[i];
        G[u].pb(v);
        ind[v]++;
    }
    return TopoSort();
}
int main(){
    cin>>n>>m;
    REP(i,1,m)
        read(a[i]),read(b[i]);
    int l=1,r=m,ans=-1;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
}    
View Code

 

 

 

 

 

E. Intellectual Inquiry(DP+贪心) (好题)

题目大意:

给定你能够在字符串后添加的字符数量$n$,并且给定添加的字符种类$k$(代表只能添加前$k$个字符),然后给你初始的字符串,让你对这个字符串进行构造,使得其的子串的种类数最大,并且输出这个最大值。

解题分析:

考虑如何计算真实的子串种类数。

$dp[i]$表示前$i$个字符中,子串种类数的最大值。

对于$dp[i]$,它能够接在前$i-1$所对应的所有子串后面,与之前的子串构成不同的子串,并且它还要加上前$i-1$个初始的子串数,所以$dp[i]=2*dp[i-1]$。

但是这样会发生重复计算,因为如果之前也出现了当前的字符$s[i]$,那么之前出现的字符已经计算过以这个字符为结尾的情况,所以我们需要记录每个字符之前出现的最尾端的位置$pre[s[i]]$,然后重复计算的次数就是$dp[pre[s[i]]-1]$。用$dp[i]$减去重复计算的即可得到正确答案。

考虑构造后面的$n$个字符,计算$dp[i]$的时候,$dp[i]=2*dp[i-1]-dp[pre[s[i]]-1]$,所以要使$dp[i]$尽可能的大,就要让$dp[pre[s[i]]-1]$要尽可能的小,但是因为本题涉及取模运算,所以不能直接找最小值。但是显然$dp$值递增,所以只需要找$pre$最靠前的字符就好了啦。

复杂度:$O((n+m)*k)$

#include <bits/stdc++.h>
using namespace std;

#define id(c) c-'a'
#define REP(i,s,t) for(int i=s;i<=t;i++)
const int MOD = 1e9+7;
const int N = 2e6+5;
typedef long long ll;
ll n,k,dp[N],pos[30];
char s[N];
//dp[i]表示前i个字符的子串种类数的最大值
int main(){
    cin>>n>>k;scanf("%s",s+1);
    int m=strlen(s+1);
    dp[0]=1;    
    REP(i,1,m){
        dp[i]+=dp[i-1]*2;      //前i-1的真实子串种类数为dp[i-1],然后再加上之前的子串以它结尾的种类数
        if(pos[id(s[i])]>0)    //如果这个字符出现过了,才需要减dp[pre[c]-1],因为在pre[c]的时候,所有pre[c]-1之前的元素计算过了一遍相同的情况(即以c为最尾端的字符所构成的字符串)
            dp[i]-=dp[pos[id(s[i])]-1];
        dp[i]%=MOD;
        pos[id(s[i])]=i;      //更新
    }
    REP(i,m+1,m+n){
        dp[i]+=dp[i-1]*2;
        int c,loc=1e9;      
        REP(j,0,k-1) if(pos[j]<loc){    //查找前k个字符最后一个字符最早出现的位置
            loc=pos[j];
            c=j;
        }
        if(loc>0)dp[i]-=dp[loc-1];  //同上,如果c所对应的字符之前没有出现过,那么就不需要减去重复计算的部分(因为根本就没有重复计算)
        dp[i]%=MOD;
        pos[c]=i;     
    }
    printf("%lld\n",(dp[m+n]+MOD)%MOD);
}    
View Code

 

转载于:https://www.cnblogs.com/00isok/p/10803125.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值