随笔- 22  文章- 0  评论- 10  阅读- 1571 

2022-10-6

T1

炼心

题目背景

Cafeiin神情认真,专心的听着老师讲话。

“既然你已拜入算法门下,我便为你讲讲这算法的修炼之道,算法之道,分几重境界,初入编程,便是普及,普及之上更有提高。提高之上,一劫一境界。提高巅峰强者引天雷入体渡劫,便能成就省选,省选再渡劫,便是集训队,集训队再渡劫,便证得IOI大道。天榜之下还有地榜,从校赛市赛省赛一路渡劫,证得ICPC world final总决赛。不过,天榜学历皆不过高中,个个都是万里挑一的奇才。为师便考考你,这天地二榜,谁为首。”

题目描述

算法之路中有n名同学。第i个同学有ai,表示该名同学的程序设计能力,bi表示该名同学的学历(1 表示幼儿园,27 表示小学,813 表示中学,大于13 表示更高学历),它的名字是si为字符串。

天榜学历不超过13,地榜学历不小于14,满足学历要求的同学会分别在两个榜单中排名。

你需要分别输出,天榜地榜当中,程序设计能力最强的同学中,学历最小的名字,存在多个则输出字典序最小的那一个,如果榜上无人,输出1

输入格式

第一行一个整数n
接下来n行,每行有三个数据,分别是名字字符串si,程序设计能力ai,学历bi

输出格式

两行,第一行为天榜强者的名字,第二行为地榜强者的名字。

样例

样例输入

5
cafeiin 18 11
George_Plover 20 12
Karshilov 19 12
wzk 23 14
Lenska 18 12

样例输出

George_Plover
wzk

数据范围与提示

对于10%的数据,天榜强者为George_Plover
对于10%的数据,地榜强者为George_Plover
对于30%的数据,1n100ai10bi10
对于100%的数据,1n105ai100bi1001si15
保证名字互不相同

思路

简单排序题,按学历分成天榜地榜两类,分别按能力第一关键字、学历第二关键字、名字第三关键字排序输出即可。

代码

#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
int n,a,b;
string s;
struct node
{
    string nam;
    int a,b;
};
bool cm(string a,string b)//a<b
{
    int la=a.length(),lb=b.length();
    int len=min(la,lb);
    for(int i=0;i<len;i++)
    {
        if(a[i]>b[i])return false;
    }
    return la<lb?true:false;
}
node tian[MAXN],di[MAXN];
int tott=0,totd=0;
bool cmp(node a,node b)
{
    if(a.a!=b.a)return a.a>b.a;
    if(a.b!=b.b)return a.b<b.b;
    return a.nam<b.nam;
}
string sread()
{
    getchar();
    char c=getchar();
    string ans;
    while(c!=' ')
    {
        ans+=c;
        c=getchar();
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        //cin>>s;
        s=sread();
        scanf("%d%d",&a,&b);
        if(b<=13)
        {
            tian[++tott].nam=s;
            tian[tott].a=a;
            tian[tott].b=b;
        }
        else
        {
            di[++totd].nam=s;
            di[totd].a=a;
            di[totd].b=b;
        }
    }
    sort(tian+1,tian+tott+1,cmp);
    sort(di+1,di+totd+1,cmp);
    if(tott)cout<<tian[1].nam<<endl;
    else cout<<"-1"<<endl;
    if(totd)cout<<di[1].nam;
    else cout<<"-1";
    return 0;
}

T2

炼气

题目背景

师傅讲完算法大道,便将一本黑色厚书丢给了Cafeiin,让Cafeiin好好阅读,随后又开始聊起了当年往事:“想当年,为师也是天榜榜上有名的天骄,年纪轻轻便入半步省选,只可惜为了宗门强行渡劫损了元神......”

虽然Cafeiin知道师傅是在吹牛,但Cafeiin还是看起了师傅给自己的书。

题目描述

这本书可以看作一个长度为n的字符串S,这个字符串由小写字母组成。

师傅说,这本书有千万种变化,如果对于一个区间[l,r](1lrn),如果字符串A=slsl+1...sr1sr满足,有不超过1种字符出现了奇数次,则这个区间是一个“法门”。

Cafeiin想知道这本书总共有多少个“法门”。

输入格式

第一行一个整数n,表示字符串S的长度。
接下来1行,一个长度为n的字符串S

输出格式

一行一个整数,表示字符串S中的法门数量。

样例

样例输入

4
abab

样例输出

7

样例解释:区间(1,1),(1,3),(1,4),(2,2),(2,4),(3,3),(4,4)满足条件

数据范围与提示

对于30%的数据,1n100
对于60%的数据,1n104
对于100%的数据,1n106,字符串S只由小写字母组成

思路

一个区间是“法门”的条件是有不超过1种字符出现了奇数次。根据此性质容易想到异或^。只要一个区间的所有元素的异或和是0或‘a’~‘z’之间,那么该区间就是一个法门。由此而来的一个暴力想法是处理一个异或前缀和,每次枚举区间的两个端点,计算区间异或和并判断。

但这样的复杂度是O(n2)的,无法胜任此题。

考虑从左到右扫一边,同时记录一个26位的状态sta,它表示从最左端到当前点,所有字母出现了奇数次还是偶数次。 那么每次到一个地方,能使ans增加的只会是 以当前节点为右端点且异或和为0或‘a’~‘z’的区间数,即之前sta异或的结果 的二进制码中 只有一个1或没有1的状态数。因此我们每次更新sta的时候,ans+=cnt[sta^(01<<kk[0,25])],同时用于计数的cnt[sta]++。这样就统计出了答案。

代码

#include<bits/stdc++.h>
#define MAXN 67108865
#define int long long
using namespace std;
string s;
int n,st,a[MAXN]={0},ans=0;
bool flag=0;
string sread()
{
    char c=getchar();
    string ans;
    while(c>='a'&&c<='z')
    {
        ans+=c;
        c=getchar();
    }
    return ans;
}
string to_b(int x)
{
    string ans;
    while(x)
    {
        ans+=(x%2)+'0';
        x>>=1;
    }
    while(ans.length()<2){ans+='0';}
    return ans;
}
signed main()
{
    scanf("%lld",&n);
    getchar();
    s=sread();
    st=0;
    a[st]=1;
    for(int i=1;i<=n;i++)
    {
        flag=0;
        st^=(1<<(s[i-1]-'a'));
        for(int j=1;j<=26;j++)
            ans+=a[st^(1<<(j-1))];
        ans+=a[st];
        a[st]++;
    }
    printf("%d",ans);
    return 0;
}

T3

炼体(原题:将军令)

题目背景

“又说回来了,虽说为师当年损失了元神,但是为师的图论......啊不炼体之术可没有落下,这炼体之
道,打通奇经八脉......”

题目描述

人体的经脉可以简化成一个有n个穴位的图,他们之间通过n1条经络互相连通.每条经络长度
1
根据修行的炼体之术,一旦有一个穴位被打通,那么与它距离不超过k的穴位会被"活化"(包括自己)
Cafeiin想知道,最少需要打通自己多少个穴位,才能"活化"自己全身所有的穴位。

输入格式

第一行一个整数n,表示共有n个穴位。
接下来n行,每行2个整数xy,表示x穴位到y穴位之间存在一条长度为1的边。

输出格式

一行一个整数,表示所需打通最少穴位数量样例。

样例输入1

4 1
1 2
1 3
1 4

样例输出1

1

样例输入2

6 1 
1 2 
1 3 
1 4 
4 5 
4 6

样例输出2

2

样例1:打通1号,所有穴位被激活。
样例2:打通1,4号,所有穴位被激活。

数据范围与提示

对于30%的数据,n102
对于50%的数据,n104k1
对于100%的数据,1n1050k20

思路

这道题是一类经典的树形DP的拓展,详见战略游戏消防局的设立这两题。

但对于这道题较简单的思路并不需要树形DP。考虑一个贪心策略:先以一个点为根dfs一遍,求出每个点的层数,并按层数从大到小排序。然后每次选择排序后数组的第i项,看该点是否被覆盖,如果没有就覆盖它的k级祖先。显然,我们要想让打通的穴位尽可能少,那么每个打通的穴位之间的重叠应尽可能小,即每个打通的穴位要独立覆盖尽可能大的面积。而每次选择k级祖先覆盖之所以最优,因为选择k级祖先一定可以覆盖完以k级祖先为根的整棵子树(最深的节点都能覆盖了,其他深度更浅的一定能被覆盖),且向子树以外的地方延伸最远,所以覆盖最广。

至于如何求出一个点是否被覆盖,我们使用一个dis数组表示每个点距离它最近的打通点的距离。如果一个点的父节点的dis>=k,则这个点一定没有被覆盖。每次确定打通的点时更新dis即可。

代码

#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
int n,k,t,x,y,now,son,ans=0;
vector<int> tmap[MAXN];
int fa[MAXN],dis[MAXN];
struct node
{
    int dep,num;
}no[MAXN];
bool cmp(node a,node b)
{
    return a.dep>b.dep;
}
void dfs(int st,int fat,int depp)
{
    no[st].dep=depp;
    fa[st]=fat;
    for(int i=0;i<tmap[st].size();i++)
    {
        if(tmap[st][i]==fat)continue;
        dfs(tmap[st][i],st,depp+1);
    }
}
int main()
{
    scanf("%d%d%d",&n,&k,&t);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        tmap[x].push_back(y);
        tmap[y].push_back(x);
    }
    for(int i=1;i<=n;i++)
        no[i].num=i;
    dfs(1,1,0);
    memset(dis,0x3f,sizeof(dis));
    sort(no+1,no+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        now=son=no[i].num;
        for(int j=1;j<=k;j++)
        {
            now=fa[now];
            dis[son]=min(dis[son],dis[now]+j);
        }
        if(dis[son]>k)
        {
            dis[now]=0;
            ans++;
            for(int j=1;j<=k;j++)
            {
                now=fa[now];
                dis[now]=min(dis[now],j);
            }
        }
    }
    printf("%d",ans);
    return 0;
}
 posted on   hu_led  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示