蓝桥杯真题练习【第十一届】【省赛】【B组】

前面水题

A.枚举
B.枚举
C.模拟
D.日期
E.并查集(合并的时候竟然写错 写完检查代码)

//E题代码
#include<bits/stdc++.h>
using namespace std;
int g[7][7];
//a b c d e f g
//0 1 2 3 4 5 6
int fa[7];
int ff(int x){return fa[x]==x?x:fa[x]=ff(fa[x]);}
int main(){
    g[0][1]=g[0][5]=g[1][6]=g[1][2]=g[2][6]=
    g[2][3]=g[3][4]=g[4][5]=g[4][6]=g[5][6]=1;
    int ans=0;
    for(int i=0;i<7;i++){
        for(int j=0;j<7;j++){
            if(g[i][j])g[j][i]=1;
        }
    }
    for(int i=1;i<1<<7;i++){
        bool vis[7]={false};
        for(int j=0;j<7;j++)fa[j]=j;
        int cur=i,num=0;
        while(cur){
            if(cur%2)vis[num]=true;
            cur/=2;
            num++;
        }
        for(int j=0;j<7;j++){
            for(int k=0;k<7;k++){
                if(vis[j]&&vis[k]&&g[j][k]){
                    int fj=ff(j),fk=ff(k);
                    fa[fj]=fk;
                }
            }
        }
        int f=-1,fl=1;
        for(int j=0;j<7;j++){
            if(!vis[j])continue;
            if(f==-1)f=ff(j);
            else if(f!=-1&&ff(j)!=f){
                fl=0;break;
            }
        }
        if(fl) ans++;
        else {
            for(int j=0;j<7;j++)cout<<vis[j];cout<<endl;
        }
    }
    cout<<ans<<endl;
}

成绩分析

四舍五入用了这个:
n u m num num四舍五入到小数点后n位
n u m = ( f l o o r ( n u m + 5 × 1 0 − n + 1 ) × 100 ) / 100 num=(floor(num+5×10^-n+1^)×100)/100 num=(floor(num+5×10n+1)×100)/100
或者
n u m = ( c e i l ( n u m − 5 × 1 0 − n + 1 ) × 100 ) / 100 num=(ceil(num-5×10^-n+1^)×100)/100 num=(ceil(num5×10n+1)×100)/100

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int maxx=0,minn=100;
int main(){
    int n;cin>>n;
    double all=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        maxx=max(maxx,a[i]),minn=min(minn,a[i]);
        all+=a[i];
    }
    double eval=(floor((all/n+0.005)*100)/100);
    printf("%d\n%d\n%.2lf",maxx,minn,eval);
    return 0;
}

回文日期

感觉枚举会超时,所以通过打表将全部合法的回文日期和ABABBABA型日期保存下来(升序),然后二分查找。

  1. 先判断日期是否合法,再判断是否为回文
#include<bits/stdc++.h>
using namespace std;
int a[10];
int n1=9899;
int mon[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool legal(int year,int month,int day){
    if((year%4==0&&year%100!=0)||year%400==0)mon[2]++;
    if(month>0&&month<13&&day>0&&day<mon[month]){
        if(mon[2]==29)mon[2]--;return true;
    }
    if(mon[2]==29)mon[2]--;
    return false;
}
int main(){
    freopen("output.txt","w",stdout);
    int cnt=0;
    for(int i=1000101;i<100000000;i++){
        int cur=i;
        if(!legal(cur/10000,cur%10000/100,cur%100))continue;
        for(int j=0;j<8;j++){
            a[7-j]=cur%10;cur/=10;
        }
        int fl=1;
        for(int j=0;j<4;j++){
            if(a[j]!=a[7-j]){
                fl=0;break;
            }
        }
        if(fl){
            cout<<i<<',';cnt++;
        }
    }
    cout<<cnt<<endl;
    return 0;
}

  1. 保存ABABBABA型的日期
#include<bits/stdc++.h>
using namespace std;
const int n1=354;
int a[n1]={打表出来的n1个回文日期};
int b[10];
int main(){
    freopen("output.txt","w",stdout);
    int cnt=0;
    for(int i=0;i<n1;i++){
        int cur=a[i];
        int fl=0;
        for(int j=0;j<8;j++){
            b[7-j]=cur%10;cur/=10;
        }
        if(b[0]==b[2]&&b[1]==b[3])fl=1;
        if(fl){
            cout<<a[i]<<',';
            cnt++;
        }
    }cout<<cnt<<endl;
    return 0;
}
  1. 直接输出表中的答案
#include<bits/stdc++.h>
using namespace std;
const int n1=354;
const int n2=12;
int a[n1]={打表出来的n1个回文日期};
int b[n2]={打表出来的n2个ABABBABA型的日期};
int main(){
    int date;cin>>date;
    cout<<a[upper_bound(a,a+n1,date)-a]<<endl;//a数组中第一个大于num的数字
    cout<<b[upper_bound(b,b+n2,date)-b];
    return 0;
}

平面切分

很直观的一点是:增加一个交点,就会增加一个区域,即该新产生的交点将原来的一个区域拆分成两个区域。同时每次多一条边,不管有没有产生新的交点,都会增加一个区域。因此每增加一条边,计算由它多产生的交点个数 k i k_i ki,则该边新增的区域个数为 k i + 1 k_i+1 ki+1

#include<bits/stdc++.h>
using namespace std;
struct Node{int k,b;}node[1000];
const double inf=1e18;
struct Point{
    double x,y;
    bool cmp(const Point a){
        return a.x==x&&a.y==y;
    }
};
Point cal(Node a,Node b){
    if(a.k==b.k)return {inf,inf};
    double k1=a.k,b1=a.b,k2=b.k,b2=b.b;
    return Point{(b2-b1)/(k1-k2),k2*(b2-b1)/(k1-k2)+b2};
}
int main(){
    int n;cin>>n;
    int cnt=0;
    for(int i=1;i<=n;i++){
        int x,y;cin>>x>>y;
        int fl=0;
        for(int j=1;j<=cnt;j++){
            if(x==node[j].k&&y==node[j].b){
                fl=1;break;
            }
        }
        if(fl)continue;
        node[++cnt]={x,y};
    }
    int ans=2;//只要有一条边就有两个区域
    for(int i=2;i<=cnt;i++){
        vector<Point>v;
        for(int j=1;j<i;j++){
            int fl=0;
            Point point=cal(node[j],node[i]);
            if(point.x==inf)continue;
            for(int k=0;k<v.size();k++){
                if(point.cmp(v[k])){fl=1;break;}
            }
            if(!fl)v.push_back(point);
        }
        ans+=v.size()+1;
    }
    cout<<ans<<endl;
    return 0;
}

子串分值和

/* 子串无法枚举,有很多情况都是重复的,因为一个子串后再加上一个字符形成新的子串,如果该字符已经在原来的子串中出现过了,那么新子串的分值和原来一样,否则应该在原子串的分值基础上加1。应该就是由这个基本的子串间转换删除冗余时间和空间来获得答案。
考虑dp,由小的子串形成新的字符。用到dp首先用目标状态来确定状态设置和状态转移方程。最终的答案是由整个字符串的子串的分值和,我们设 d p [ i ] dp[i] dp[i]为以 i i i为结尾 位置的子串分值和。最后 d p [ n − 1 ] dp[n-1] dp[n1]即为答案。用上面的子串间转换来确定状态转移方程:前面 i − 1 i-1 i1个的子串分值和已经求得,现在多了一个字符,以递增的新子串个数枚举包含新加的字符而产生的新子串:
包含一个字符的新子串:1
包含两个字符的新子串 :若 s t r [ i − 1 ] = = s t r [ i ] str[i-1]==str[i] str[i1]==str[i],则答案为1,否则答案为2

考虑到数据范围是 1 e 5 1e5 1e5,因此查找长度的复杂度应该缩小,我思考了一个复杂度为O(nlog2n)的做法:二分+st表。因为是静态的,并且在考虑新子串时需要计算 第 i 第i i个字符是否有出现过,考虑使用st表。二分查找最后一个位置 k k k,使得从第 0 0 0个位置到第 k k k个位置字符 s t r [ i ] str[i] str[i]的出现次数为0,则从 k + 1 k+1 k+1 i − 1 i-1 i1 s t r [ i ] str[i] str[i]没有出现,因此可由
其实还是隔开了很多的字符,最后一个出现 s t r [ i ] str[i] str[i]的位置,后面的将答案*2,前面的答案不变。 */

//然而我不会写 暴论:这是个思维题 !

枚举子串必定会爆,那就枚举每个位置的字符对结果的贡献(真是个小机灵鬼,这应该是很常见的技巧),即每个位置的字符可以导致哪些包含它的子串增加分值。
设当前考虑的位置为 i i i, i i i 设前面的字符个数是 l l l,则 l = i l=i l=i, i i i位置的字符为 s t r [ i ] str[i] str[i],下一个字符为 s t r [ i ] str[i] str[i]的位置为 n x t [ s t r [ i ] ] nxt[str[i]] nxt[str[i]],设i到下一字符之间的字符个数(不包含i和下一位置)为 r r r,则 i i i位置的字符可以对以下子串产生贡献:以 s t r [ i ] str[i] str[i]为结尾的一共有 l l l个,以 s t r [ i ] str[i] str[i]开头的子串有 r r r个,仅以 s t r [ i ] str[i] str[i]一个字符为子串有 1 1 1个,而 s t r [ i ] str[i] str[i]在中间的有 l ∗ r l*r lr个,一共有 l + r + 1 + l ∗ r l+r+1+l*r l+r+1+lr个,枚举每个位置即可。

#include<bits/stdc++.h>
#define ll long long
#define ms(n) memset(n,-1,sizeof n)
using namespace std;
const int N=1e5+10;
int nxt[N],last[N];
int main(){
    ms(nxt),ms(last);
    ll ans=0;
    string str;cin>>str;
    for(int i=0;i<str.length();i++){
        if(last[str[i]]!=-1)
            nxt[last[str[i]]]=i;
        last[str[i]]=i;
    }
    for(int i=0;i<str.length();i++){
        ll l=i,r=str.length()-i-1;
        if(nxt[i]!=-1)r=nxt[i]-i-1;
        ans+=l+r+1+l*r;
    }
    cout<<ans<<endl;
    return 0;
}

字串排序

使新获得的字串最短,即对该字串采用冒泡排序的交换次数应最多,即其字典序是最大的,题目要求使用字典序最小的,因此要用靠前的字母。如果将相同的字母放在连续的位置,则每个字符的交换次数为最后一个字符之后的字母个数。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值