首页 > 其他分享 >CSP-S2023 全场题解

CSP-S2023 全场题解

时间:2023-11-03 15:44:58浏览次数:38  
标签:return name int 题解 ll len S2023 typ CSP

lock

这题就是个模拟吧,赛时被迷惑了以为是什么不可做题,仔细看只有 \(10^5\) 种状态,那就枚举好了。
我们分别从状态串出发,枚举它能达到的答案,加到 set 取个并集,不过注意给定的状态不能是密码,要减掉。注意不要直接计数器减减,不然如果有相同的算在状态里面的会多减,我考场代码就这么被 \(hack\) 了,好在官方数据没有这种数据。

\(Code\)

set<int>S,s;
int n,a[10][6];
inline int chang(int x){
    return a[x][1]+a[x][2]*10+a[x][3]*100+a[x][4]*1000+a[x][5]*10000;
}
int main(){
    freopen("lock.in","r",stdin);
    freopen("lock.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
       for(int j=1;j<=5;j++)
           a[i][j]=read();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=5;j++){
              for(int o=1;o<=10;o++){
                     a[i][j]=(a[i][j]+1)%10;
                     if(i==1)s.insert(chang(i));
                     else if(S.find(chang(i))!=S.end())s.insert(chang(i));
              }
       }
       for(int j=1;j<=4;j++){
              for(int o=1;o<=10;o++){
                     a[i][j]=(a[i][j]+1)%10;
                     a[i][j+1]=(a[i][j+1]+1)%10;
                     if(i==1)s.insert(chang(i));
                     else if(S.find(chang(i))!=S.end())s.insert(chang(i));
              }
       }
       S=s,s.clear();
    }
    for(int i=1;i<=n;i++)if(S.find(chang(i))!=S.end())S.erase(chang(i));
    cout<<S.size();
    //WA:
    //int ans=S.size();
    //for(int i=1;i<=n;i++)if(S.find(chang(i))!=S.end())ans--;
    //cout<<ans;
    return 0;
}

game

赛时没有想到栈的 \(O(n^2)\) 做法,不然说不定就过了(禁止马后炮,就是你菜),就直接打了个 \(O(n^3)\) 的区间 dp。

\(35pts\ Code\)

const int N=2e6+7;
int n,a[N];
namespace subtask1{
    bool f[802][802];
    bool work(){
        ll ans=0;
        for(int len=2;len<=n;len+=2){
            for(int i=1;i+len-1<=n;i++){
                int j=i+len-1;
                if(a[i]==a[j]&&(len==2||f[i+1][j-1]))f[i][j]=1;
                else{
                    for(int k=i+1;k<j;k+=2){
                        if(f[i][k]&&f[k+1][j]){
                            f[i][j]=1;
                            break;
                        }
                    }
                }
                ans+=f[i][j];
            }
        }
        cout<<ans;
        return 0;
    }
}
int main(){
    string ss;
    cin>>n>>ss;
    for(int i=0;i<n;i++)a[i+1]=ss[i]-'a';
    if(n<=800)return subtask1::work(),0;
    return 0;
}

赛后听说了 \(O(n^2)\) 的栈做法。
关注到我们消的顺序不会对结果造成影响,因此我们随意地能消则消如果消不掉那么就必然不是可消除串。所以我们维护一个没有被消的字符串栈,每加入一个字符就判断是否与栈顶字符相同,相同就删,如果不相同就加入,这样如果中途发现栈空就说明可以消除,计入答案即可。
如果我们直接这样子枚举左端点和右端点消,复杂度是 \(O(n^2)\) 的,考虑优化。
瓶颈在于每次枚举左端点的话有很多内容是与之前的一样的,有很多重复运算,所以不能枚举左端点。然后发现一个如果一个串的后缀是可消的,我们这个要一直枚举左端点直到栈为空,而去掉这个后缀后栈也是一样的。
那么我们是不是只需要找前面与当前串的消除栈相同的串的个数就行了?
可以用哈希或者字典树维护消除串。因为每次只增加或者减少一个字符,所以用字典树不能从头加到尾,而是用一个类似于回跳指针维护;对于哈希就是历史哈希值。时间复杂度 \(O(n)\),用哈希的话时间复杂度平均是 \(O(n\log n)\),但是空间复杂度少了个 \(26\),当然用 unordered_map 可以实现 \(O(n)\),不过复杂度够的话不是很敢用了,之前被卡过。

\(Code\)

//hash
typedef unsigned long long ull;
const ull P=1331,N=2e6+6;
int n,r;
ull a[N],ans;
stack<char>S;
map<ull,int>M;
int main(){
    string ss;
    cin>>n>>ss;
    for(int i=0;i<n;i++,M[a[r]]++)
        if(S.empty()||S.top()!=ss[i]){
            S.push(ss[i]);
            a[++r]=a[r]*P+ss[i]-'a'+1;//must +1!!不然a串与空串无异,卡了挺久
            ans+=M[a[r]];
        }else{
            S.pop(),r--;
            ans+=M[a[r]]+S.empty();
        }
    cout<<ans;
    return 0;
}
//Trie
int n,a[N],tr[26][N],en[N],tot,zero,r;
long long ans;
stack<int>S;
int main(){
    string ss;
    cin>>n>>ss;
    for(int i=0;i<n;i++){
        int w=ss[i]-'a';
        if(S.empty()||S.top()!=w){
            S.push((w));
            if(!tr[w][a[r]])tr[w][a[r]]=++tot;
            a[++r]=tr[w][a[r]];//注意优先级是从右到左
            //等价于a[r+1]=tr[w][a[r]],r++;
            ans+=++en[a[r]]-1;
        }
        else{
            S.pop(),r--;
            if(S.empty())ans+=++zero;
            else ans+=++en[a[r]]-1;
        }
    }
    cout<<ans;
    return 0;
}

本题的类似题目:CF1223F Stack Exterminable Arrays
不过这个因为值域太大不能用字典树,单哈希也能过。

struct

一眼大模拟,考场上没想到正解,没多少时间了就寻思着顺手拿个 \(65\) 分吧,结果因为没有对应的样例,自己的小样例过于水,导致少写两句话也能过自己的样例然后就开开心心地交了。不过 CCF 的慈善数据给我留了 \(30\) 分,非常感谢。

对于 \(65\) 分的 \(ABC\) 部分分,只需要看清楚题目然后想清楚就行了,不需要什么思考。

\(65pts\)

//错因:
//一开始什么东西都没有的时候ERR没有预处理导致很多点RE,结构体加完以后没有把尾部内存加回去,导致地址错误
//再一次体验了什么叫少写两行代码流两行泪。(悲)
int n,tot;//number of struct
struct node{
    string typ;
    ll st;
};
struct Struct{
    string me,typ[102],nam[102];
    int num,len=0,siz=0;
};
map<int,string>S;//address for opt=4
map<string,node>M;//for opt=3
map<string,Struct>Str;
inline int gett(string tpy){
    if(tpy=="byte")return 1;
    if(tpy=="short")return 2;
    if(tpy=="int")return 4;
    if(tpy=="long")return 8;
    return Str[tpy].siz;
}
ll t;
int main(){
    n=read();
    S[0]="ERR";//少写这句
    for(int ww=1;ww<=n;ww++){
        int opt=read();
        if(opt==1){
            Struct p;
            cin>>p.me;
            p.num=read();
            ll now=0;
            for(int i=1;i<=p.num;i++){
                cin>>p.typ[i]>>p.nam[i];
                p.len=max(p.len,gett(p.typ[i]));
            }
            for(int i=1;i<=p.num;i++){
                if(now%gett(p.typ[i])!=0)now=(now/gett(p.typ[i])+1)*gett(p.typ[i]);
                now+=gett(p.typ[i]);
            }
            if(now%p.len!=0)now=(now/p.len+1)*p.len;
            p.siz=now;
            Str[p.me]=p;
            printf("%d %d\n",p.siz,p.len);
        }else if(opt==2){
            string tpy,nam;
            cin>>tpy>>nam;
            if(Str.find(tpy)!=Str.end()){
                Struct p=Str[tpy];
                if(t%p.len!=0)S[t]="ERR",t=(t/p.len+1)*p.len;
                printf("%lld\n",t);
                M[nam]={p.me,t};
                for(int i=1;i<=p.num;i++){
                    if(t%gett(p.typ[i])!=0)S[t]="ERR",t=(t/gett(p.typ[i])+1)*gett(p.typ[i]);
                    S[t]=nam+"."+p.nam[i];
                    M[nam+"."+p.nam[i]]={p.typ[i],t};
                    t+=gett(p.typ[i]);
                }
                if(t%p.len!=0)S[t]="ERR",t=(t/p.len+1)*p.len;//少写这句
                S[t]="ERR";
                continue;
            }
            if(t%gett(tpy)!=0)S[t]="ERR",t=(t/gett(tpy)+1)*gett(tpy);
            printf("%lld\n",t);
            S[t]=nam;
            M[nam]={tpy,t};
            t+=gett(tpy);
            S[t]="ERR";
        }else if(opt==3){
            string w;
            cin>>w;
            printf("%lld\n",M[w].st);
        }else{
            ll addr=read();
            auto it=--S.upper_bound(addr);
            cout<<(it->second)<<endl;
        }
    }
    return 0;
}

思考正解吧,正解肯定不能是完全展开所有变量,因为这个 \(addr\le 10^{18}\)。
本题的复杂度瓶颈在结构体嵌套,我们发现除了在操作 \(2\) 定义的变量外,其他的变量都与结构体内部变量一致,所以我们只需要对每个结构体开一个 map,然后每次遇到结构体就递归向内部查找即可,复杂度 \(O(nk\log k)\)。

不开 long long 见祖宗
具体细节见代码:

\(Code\)

struct variate{
    string typ,name;
    ll addr;
}ERR;
struct Struct{
    int n,len=0;//元素个数,对齐规则
    ll size=0;//占用大小
    map<ll,variate>atn;//address to name 由相对位置到变量名
    map<string,variate>nta;//name to address(and type)
};
map<string,Struct>str;//类型对应的结构体
map<string,variate>st;//无嵌套的变量查找
map<ll,variate>ad;//无嵌套的地址查找
inline int find(string typ){
    if(typ=="byte")return 1;
    if(typ=="short")return 2;
    if(typ=="int")return 4;
    if(typ=="long")return 8;
    return 0;
}
void define_struct(ll t=0){
    Struct p;//一定要赋初值
    string name;
    cin>>name;
    p.n=read();
    for(int i=1,len;i<=p.n;i++){
        string typ,name;
        cin>>typ>>name;
        len=find(typ);ll siz=len;
        if(!len)len=str[typ].len,siz=str[typ].size;
        p.len=max(p.len,len);
        if(t%len)p.atn[t]=ERR,t=(t/len+1)*len;
        p.atn[t]={typ,name,t};
        p.nta[name]={typ,name,t};
        t+=siz;
    }
    if(t%p.len)p.atn[t]=ERR,t=(t/p.len+1)*p.len;
    p.size=t;
    p.atn[t]=ERR;
    str[name]=p;
    printf("%lld %d\n",p.size,p.len);
}
int n;ll t;
inline void define_element(){
    string typ,name;
    cin>>typ>>name;
    int len;ll siz;//这里siz要ll
    if(str.find(typ)!=str.end())len=str[typ].len,siz=str[typ].size;
    else len=siz=find(typ);
    if(t%len)ad[t]=ERR,t=(t/len+1)*len;
    printf("%lld\n",t);
    st[name]={typ,name,t};
    ad[t]={typ,name,t};
    t+=siz;
    ad[t]=ERR;
}
void search_element(){
    string name,top;
    int lentop;
    cin>>name;
    for(lentop=0;lentop<name.length()&&name[lentop]!='.';lentop++);
    top=name.substr(0,lentop);
    if(lentop==name.length())return printf("%lld\n",st[top].addr),void();
    Struct from=str[st[top].typ];
    ll addr=st[top].addr;
    while(name.length()>lentop){
        name=name.substr(lentop+1);
        for(lentop=0;lentop<name.length()&&name[lentop]!='.';lentop++);
        top=name.substr(0,lentop);
        addr+=from.nta[top].addr;
        if(name.length()>lentop)from=str[from.nta[top].typ];
    }
    printf("%lld\n",addr);
}
void search_address(){
    ll addr=read();
    variate p=(--ad.upper_bound(addr))->second;
    string name=p.name;
    addr-=p.addr;
    while(!find(p.typ)){
        name+=".";
        Struct o=str[p.typ];
        p=(--o.atn.upper_bound(addr))->second;
        name+=p.name;
        addr-=p.addr;
        if(p.name=="ERR")name="ERR";
    }
    cout<<name<<endl;
}
int main(){
    freopen("struct.in","r",stdin);
    freopen("struct.out","w",stdout);
    n=read();
    ad[0]=ERR={"long","ERR",0};
    while(n--){
        int opt=read();
        if(opt==1)define_struct();
        else if(opt==2)define_element();
        else if(opt==3)search_element();
        else search_address();
    }
    return 0;
}

打大模拟的时候一定要注意力集中,不能凭借肌肉记忆,不然打代码一时,调代码一世。认真打的话就可以微调过了。(虽然写其他的题目每一步也需要非常清楚自己在干什么,不能神游)。

tree

考场上第二接近满分的题,虽然也能随便被 hack,但是在 ccf 上也有 90 分的好成绩。
在我被软件问题调试和第一题浪费了 \(1\) 个小时后,又被第二题和第三题的没有思路折磨的不想打时,是它让我重拾信心。
看见他的第一眼:树题唉,大概率树形 dp,之前模拟赛做飞起来了,可能可做?
第二眼:最小答案?保证在 \(10^9\) 内有解?裸的二分答案加树形 dp 啊。
第三眼:关系线性?分类讨论加等差数列公式,懒得管推什么公式了,\(10^5\) 直接再来一个 \(\log n\) 跑二分答案!找出对于当前天数至少需要从什么时候开始种树。再来个树上贪心吧,对每一个点记录最晚什么时候到即可(我当时想的是对儿子排个序然后每个儿子减去对应序号取最小值,但是因为每次服务的不一定是同一棵子树,所以这样不一定合法。正解是取值最小的儿子 \(-1\),然后排序看看能不能满足条件)。

复杂度 \(O(n\log^2 n)\),准能过。

然后打代码就完事了。因为 ccf 样例太水导致我甚至等差数列的公式打错了都能全部通过。所以也预料到了赛后被 hack。

\(Code\)

typedef __int128 INT;
int n;
int cal1(int las,ll b,ll c,INT a){
    INT h2=b+las*c;
    if(h2>=a)return n;
    int l=0,r=n;
    while(l<r){
        int mid=(l+r+1>>1);
        if((INT)(h2+b+c*mid)*(las-mid+1)>=a*2)l=mid;//A.P
        else r=mid-1;
    }
    return l;
}
int cal2(int las,ll b,ll c,INT a){
    ll o=(b-1)/(-c);//f(o)>=1
    int l=0,r=n;
    while(l<r){
        int mid=(l+r+1>>1);
        INT S;
        if(mid>o)S=las-mid+1;
        else if(las>o)S=(INT)((INT)b*2+c*(o+mid))*(o-mid+1)/2+(las-o);
        else S=(INT)((INT)b*2+c*(las+mid))*(las-mid+1)/2;
        if(S>=a)l=mid;
        else r=mid-1;
    }
    return l;
}
ll a[N],b[N],c[N],t[N],f[N],id[N];
vector<int>F[N];
bool dfs(int x,int fa){
    f[x]=t[x];
    for(int i=0;i<F[x].size();i++){
        int y=F[x][i];
        if(y==fa)continue;
        if(!dfs(y,x))return 0;
        f[x]=min(f[x],f[y]-1);
        if(f[x]<1)return 0;
    }
    return 1;
}
inline bool cmp(int x,int y){
    return f[x]<f[y];//排序小技巧
}
bool check(int x){
    for(int i=1;i<=n;i++){
        id[i]=i;
        if(c[i]>=0){
            int w=cal1(x,b[i],c[i],a[i]);
            if(w==0)return 0;
            t[i]=w;
        }else{
            int w=cal2(x,b[i],c[i],a[i]);
            if(w==0)return 0;
            t[i]=w;
        }
    }
    if(!dfs(1,0))return 0;
    sort(id+1,id+1+n,cmp);
    for(int i=1;i<=n;i++)
        if(i>f[id[i]])return 0;
    return 1;
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read(),b[i]=read(),c[i]=read();
    }
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        F[u].push_back(v);
        F[v].push_back(u);
    }
    int l=n,r=1e9+2;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid))r=mid-1;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}

后记

不得不说这次 \(CSP-S\) 真的不难,高估了。希望 \(NOIP\) 也不要太难啊,rp 加满!

finished at 2023.11.3 15:36

标签:return,name,int,题解,ll,len,S2023,typ,CSP
From: https://www.cnblogs.com/NBest/p/17807722.html

相关文章

  • NOIP 提高组 题解
    NOIST2023涂色游戏对于每一行每一列记录一个时间戳,对于每个格子颜色即为时间戳较大的颜色。幂次考虑暴力,我们发现\(O(\sqrt[3]{n})\)的复杂度是可以接受的,所以可以枚举\(\sqrt[3]{n}\)内的数然后暴力往上乘,可以用一个unordered_map判重,时间复杂度大概为\(O(\sqrt[3]{n}......
  • 【POJ 1731】Orders 题解(全排列)
    描述商店经理按照标签的字母顺序对各种商品进行了分类。所有标签以同一字母开头的种类都存放在标有该字母的同一仓库(即在同一建筑物内)。在白天,商店经理接收并预订要从商店发货的货物订单。每个订单只需要一种货物。商店经理按照预订的顺序处理请求。你事先知道今天商店经理必须处......
  • flask部署在腾讯云上,但在本地使用网页无法访问——问题解决
    flask部署在腾讯云上,但在本地使用网页无法访问——问题解决1.修改腾讯云防火墙,把对应的port开放:2.修改代码if__name__=='__main__':app.run(host="0.0.0.0",port=5000,debug=True)参考链接:https://zhuanlan.zhihu.com/p/611969276......
  • HCIP Datacom H12-831考题解析
    OSPFv3专题6.关于0SPFv3报文,以下哪个描述是正确的?郑锦程校对整理2023.11.03A.OSPFv3的Hello报文携带了路由器所有接口的IPv6地址B.OSPFv3使用链路本地地址作为发送报文的源地址,报文可以被转发到始发链路范围之外C.OSPFv3使用IPv6组播地址FF02:1和FF02:2发送OSPFv3报文D.可以在OSPFv......
  • 【真题解析】软件工程-重点题目解析(1)
    截止2023年4月本系列是我自己在学习过程中记录的资料;因为内容比较格式比较多样;用markdown靠记录非常浪费时间;再加上对时效性的考虑;就以PPT的形式记录了;本系列因为是自己的理解为主,因此,难免与教材中的内容有误差,主要是从自己的知识角度解释题目的答案,个人感觉是有助于记忆的。如果有......
  • 苏格拉底问答、实践过程截图、遇到问题解决问题截图,代码链接
    #include<signal.h>#include<stdio.h>#include<sys/time.h>intcount=0;structitimervalt;voidtimer_handler(intsig){printf("timer_handler:signal=%dcount=%d\n",sig,++count);if(count>=8){printf("cancel......
  • [ARC104B] DNA Sequence 题解
    题意对于一个只含有A,C,T,G的字符串\(s\),定义其为匹配的当且仅当其中A的数量和T的数量相等,C的数量和G的数量相等。给定一个长度为\(N\)的字符串\(S\),求其有多少个非空子串是匹配的。\(1\leN\le5000\)。题解\(\mathcal{O}(N)\)做法。首先考虑如果字符集只有......
  • [ARC104D] Multiset Mean 题解
    题意给定\(N,K\)和\(M\)。对于每个大小在\([1,N]\)中的\(x\),求每个元素大小在\([1,N]\)中,平均数为\(x\)且相同元素不超过\(K\)个的可重集的数量,对\(M\)取模。\(1\leN,K\le100\),\(M\)为质数。题解发现对于\(x\),若存在一个合法的集合\(S\),那么有\[\sum\li......
  • [ARC104C] Fair Elevator 题解
    题意有\(N\)个区间\([a_i,b_i](a_i<b_i)\),都是\([1,2n]\)内的整数且互不相同(\(a_i\neb_j,a_i\nea_j,b_i\neb_j\))。现在给定一些\(a\)和\(b\)的值,剩下不知道的用\(-1\)表示。问是否存在一种替换掉\(-1\)的方案,使得:若两个区间有交,那么它们长度相等。即\(\forall......
  • 题解:USACO23OPEN-Silver
    题解:USACO23OPEN-SilverT1MilkSum给定一个长度为\(N\)的序列\(a_1,a_2,...,a_n\),现在给出\(Q\)次操作每次将\(a_x\)修改为\(y\),每次修改后,求将序列重排后的\(T\)的最大值,定义\(T=\sum_{i=1}^{n}i\timesa_i)\)每次修改数组后会将序列还原成原样(修改不继承)有一......