2023CCPC女生赛 女生专场题解 ABFGHKL(更新至7题)

文章讲述了在算法竞赛中处理模拟题目(如区间搜索和字符串匹配)的方法,涉及二分查找、AC自动机、动态规划等技术,以及如何优化查询过程以减少重复计算。

碎碎念

场上5题铜牌中流,比赛前一晚跟队友打狂父打到一点,早上起来不出意外右手手腕肿了。。(凹了太多次追逐战)
复盘了一下发现确实也只有5题能在场上做出来,实在是时间不够+能力不足捏
目标大概补到8~9题

A

按题意模拟即可,但我写挂了,队友过的

B

一眼看出可以对 xxxyyy 分别二分求解,设左边界为 L=−1000L = -1000L=1000 ,右边界为 R=1000R = 1000R=1000 ,初始时固定 yyy000,询问 x=Lx = Lx=Lx=Rx = Rx=R ,得到左边界与目标点距离 d1d1d1 ,右边界与目标点距离 d3d3d3 ,若 d1<d3d1 < d3d1<d3 可知目标点必定在中点左侧,使 R=L+R2R = \frac{L+R}{2}R=2L+R ,再次询问。否则,可知目标点在中点或中点右侧,由于 intintint 是向下取整,需要使 L=L+R+12L = \frac{L+R+1}{2}L=2L+R+1 ,再次询问,否则可能导致死循环 。距离收敛后,固定 xxx ,对 yyy 进行相同询问即可。
注意询问得到的值为 000 时需立即结束程序,否则会导致 WRONG ANSWER。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
#define inf 0x3f3f3f3f

int x1,x2,x3;
int y1,y2,y3;
int nowx,nowy;
string s;

int ask(int x,int y)
{
    cout<<x-nowx<<' '<<y-nowy<<endl;
    fflush(stdout);
    nowx=x,nowy=y;
    int ans;
    cin>>ans;
    return ans;
}

void solve()
{
    int d0=0,d1=0,d3=0;
    int L=-1000,R=1000,MID;
    cin>>d0;
    if(d0==0) return;
    
    d1=ask(L,0);
    if(d1==0) return;
    
    d3=ask(R,0);
    if(d3==0) return;
    
    while(L<R)
    {
        
        if(d1<d3)
        {
            MID=(L+R)>>1;
            d3=ask(MID,nowy);
            if(d3==0) return;
            R=MID;
        }
        else if(d1>d3)
        {
            MID=(L+R+1)>>1;
            d1=ask(MID,nowy);
            if(d1==0) return;
            L=MID;
        }
        else
        {
            MID=(L+R)>>1;
            if(ask(MID,nowy)==0) return;
            break; 
        }
    }
    
    L=-1000,R=1000;
    d1=ask(nowx,L);
    if(d1==0) return;
    
    d3=ask(nowx,R);
    if(d3==0) return;

    while(L<R)
    {
        if(d1<d3)
        {
            MID=(L+R)>>1;
            d3=ask(nowx,MID);
            if(d3==0) return;
            R=MID;
            
        }
        else if(d1>d3)
        {
            MID=(L+R+1)>>1;
            d1=ask(nowx,MID);
            if(d1==0) return;
            L=MID;
        }
        else
        {
            MID=(L+R)>>1;
            if(ask(nowx,MID)==0) return;
            break; 
        }    
    }
}

main()
{
    solve();
}

F

构造方式很简单,按照元素递增的顺序倒着填即可。 若数组为 1 1 2 2 3 3 1 11\ 1\ 2\ 2\ 3\ 3\ 1\ 11 1 2 2 3 3 1 1 ,可以构造出 4 3 6 5 8 7 2 14\ 3\ 6\ 5\ 8\ 7\ 2\ 14 3 6 5 8 7 2 1 。问题在于如何判断非法。有以下三个条件必须满足:

  1. f[i]≤if[i]\le if[i]i
  2. f[i]≤max(f[j]+1)  (1≤j<i)f[i] \le max(f[j]+1)\ \ (1\le j<i)f[i]max(f[j]+1)  (1j<i)
  3. f[i]≥1f[i]\ge 1f[i]1

场上读假题一次,判断非法错了若干次,到最后一小时才通过,令人感叹。。

G

可以用两个队列存双方宝可梦的信息,也是按照题意模拟即可。
场上没有看出来能量是属于每个宝可梦的,而不是属于双方角色的,又喜提3发罚时,乐

H

瞪了40min才看懂题意,其实就是求每个SiS_iSiTiT_iTi 的所有子串的出现次数总和。

对于一个字符串 sss ,假设它在字符串 ttt 中匹配到的位置是 iii (这里指 sss 中最后一个字母在 ttt 中的位置是 iii),则 ttt 的所有子串中包含 sss 的有 (i−∣s∣+2)×(∣t∣−i)(i-|s|+2)\times (|t|-i)(is+2)×(ti) 个,且题目需要求总的出现次数,因此即使 ttt 中包含多个 sss ,也只需要将每一个匹配到 sss 时的答案加和即可,不需要去重。由于题目是多个字符串在 ttt 中匹配,因此,需要构建AC自动机进行多模匹配。每次查询TiT_iTi的下一个字符,如果在AC自动机上匹配到 sss ,将答案加上 (i−∣s∣+2)×(∣t∣−i)(i-|s|+2)\times (|t|-i)(is+2)×(ti),再沿着 $ fail$ 边接着统计答案。

交了喜提TLE,问题就在于每次匹配到一个字符串后总会沿着 failfailfail 边暴力上跳到这个字符串的最大后缀处,导致了很多重复计算,如果数据够强,这种做法复杂度会被卡到 ∑∣T∣⋅∑∣S∣\sum|T| \cdot \sum|S|TS。想到我们每次相当于查询 TTT 的一个前缀,即 ∣T∣−i|T|-iTi 是固定的,如果能求出这个前缀所有后缀的总匹配次数 cntcntcnt 和总字符数 allsizeallsizeallsize ,则当前前缀的所有可匹配后缀的总答案就是 (∣T∣−i)⋅((i+2)⋅cnt−allsize)(|T|-i)\cdot ((i+2)\cdot cnt-allsize)(Ti)((i+2)cntallsize) 。这样就能 O(1)O(1)O(1) 地统计答案。

可以采用类似前缀和的形式求,使用 visvisvis 数组判断当前点是否已经被加和,如果没有,则对 failfailfail 边dfs统计前缀和,并将路径上的 visvisvis 数组设为true。否则,直接计算当前点的答案即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
#define inf 0x3f3f3f3f
const int N=1e6+100,mod=1e9+7;
int ch[N][26],ne[N],cnt[N],in[N],allsize[N],idx;
bool vis[N];
string s;
vector<int> vec[N];

void insert(string s)
{
    int p=0;
    for(int i=0;s[i];i++)
    {
        if(!ch[p][s[i]-'a']) ch[p][s[i]-'a']=++idx;
        p=ch[p][s[i]-'a'];
    }
    cnt[p]++;
    allsize[p]+=s.size();
}

void build()
{
    queue<int> q;
    for(int i=0;i<26;i++)
        if(ch[0][i]) q.push(ch[0][i]);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=0;i<26;i++)
        {
            int v=ch[u][i];
            if(v)
            {
                ne[v]=ch[ne[u]][i],q.push(v);
            }
            else ch[u][i]=ch[ne[u]][i];
         } 
    }
}

void dfs(int j)
{
    if(vis[j]||j<=0) return;
    dfs(ne[j]);
    vis[j]=1;
    if(ne[j]>0)
    {
        allsize[j]+=allsize[ne[j]];
        cnt[j]+=cnt[ne[j]];
    }
}

long long query(string s)
{
    int ans=0;
    for(int k=0,i=0;s[k];k++)
    {
        i=ch[i][s[k]-'a'];
        dfs(i);
        ans=(ans+(long long)(s.size()-k)%mod*(cnt[i]*(k+2)-allsize[i])%mod)%mod;
        
    }
    return ans;
}

void solve()
{
    IOS;
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        insert(s);
    }
    build();
    for(int i=1;i<=m;i++)
    {
        cin>>s;
        cout<<query(s)<<'\n';
    }
    
}

main()
{
    solve();
}

K

狠狠地猜结论,输出 1n\frac{1}{n}n1 即可。

L

注意到数据范围很小,刚开始以为是什么差分约束题,一看数据范围直接暴力枚举 AiA_iAi 所有取值,对于每一种情况都统计评委得分,取最大值即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值