20261008 小测总结

省流:借助群众的力量成功冲入前五。tql。

T1 寻找小汽车

找不到原题。

题意概述

砝码John在 $ x $ ,小汽车在 $ y $ ,砝码John每次先走到 $ x+1 $ , $ x-2 $ , $ x+4 $ , $ x-8 $ ...问砝码John要走几步才能找到他的小汽车。

Tips:砝码John是一步步在数轴上蠕动的,遇到汽车就停止。另外 $ x $ 不随砝码John的移动改变。

solution

原题面给我恶心到了,一个红题写了半天。。。

直接模拟即可。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>a>>b;
    int k=1,cnt=0,ans=0,now=a;
    while(1){
        if(cnt%2==0){
            ans+=a+k-now;
            if(now<=b&&a+k>=b){
                ans-=a+k-b;
                cout<<ans;
                exit(0);
            }
            now=a+k;
        }
        else{
            ans+=now-a+k;
            if(now>=b&&a-k<=b){
                ans-=b-(a-k);
                cout<<ans;
                exit(0);
            }
            now=a-k;
        }
        k*=2;
        cnt++;
    }
    return 0;
}

T2 新冠亮码

找不到原题。

题意概述

对于每个顾客有一个到达时间和一个服务时间,这个顾客到达后需要占用 ta 所需的服务时间来完成服务,问总共需要多少时间。

solution

  • 维护一个当前时间。

  • 如果顾客在当前时间之后到达,那么当前时间就是顾客的到达时间加上顾客的服务时间。

  • 如果这个顾客需要等待到当前时间(上一个顾客服务结束)再开始接受服务,则将当前时间直接加上服务时间即可。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=105;
int n;
struct cus{
    int s;
    int t;
}a[N];
bool cmp(cus a,cus b){
    return a.s<b.s;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i].s>>a[i].t;
    sort(a+1,a+1+n,cmp);
    int tim=0;//当前时间
    for(int i=1;i<=n;i++){
        if(tim<a[i].s)tim=a[i].s;//不需要等待,当前时间延续到下一个顾客到来
        tim+=a[i].t;//加上顾客的服务时间
    }
    cout<<tim;
    return 0;
}

T3 策略前行

原题链接

题意概述

给定一个平面直角坐标系和两个人的行动路线,对于每一个时刻这两个人可以选择不行动或者按照行动路线走一步。每一个时刻结束移动后结算代价(代价为两人的欧几里得距离的平方),问两个人走完各自路径的最小代价。

solution

注意到两个人同时不走一定不优。考虑 $ dp $ 。设 $ dp_{i,j} $ 表示一个人走 $ i $ 步而另一个走 $ j $ 步的最小代价。那么显然有 $ dp[0][0] = 0 $ ,转移则有 $ dp[i][j] = \min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + w $ ( $ w $ 表示当前的代价) ,也就是枚举有哪些人走。最后输出 $ dp[n][m] $ ( $ n , m $ 表示两个人的行动步数)即可。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
int n,m,dp[N][N];
struct pt{
    int x;
    int y;
}a[N],b[N];//两个人移动一定步数后的坐标
inline int dis(int xa,int xb,int ya,int yb){//代价
    int cx=abs(xa-xb),cy=abs(ya-yb);
    return cx*cx+cy*cy;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    char c;
    int x,y;
    cin>>x>>y;
    int xa,ya;
    cin>>xa>>ya;
    a[0]={x,y};
    for(int i=1;i<=n;i++){
        cin>>c;
        if(c=='N')y++;
        if(c=='S')y--;
        if(c=='E')x++;
        if(c=='W')x--;
        a[i]={x,y};
    }
    x=xa,y=ya;
    b[0]={x,y};
    for(int i=1;i<=m;i++){
        cin>>c;
        if(c=='N')y++;
        if(c=='S')y--;
        if(c=='E')x++;
        if(c=='W')x--;
        b[i]={x,y};
    }
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            if(i==0&&j==0)continue;
            int w=dis(a[i].x,b[j].x,a[i].y,b[j].y);
            if(i==0)dp[i][j]=dp[i][j-1]+w;
            else if(j==0)dp[i][j]=dp[i-1][j]+w;
            else dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+w;
        }
    }
    cout<<dp[n][m];
    return 0;
}

T4 视频推荐

原题链接

题意概述

给定一个带权树和一些查询,每次查询给出一个 $ u $ 和一个 $ k $ 问所有除 $ u $ 以外的节点里有多少节点到 $ u $ 的路径上经过的最小边权大于等于 $ k $ 。

solution

因为如果从 $ u $ 出发到达一个 $ v $ ,途中有一个边的长度小于 $ k $ ,那么这个边往后的一整个连通块都是没用的,相当于这个边被删除了。

但是每次查询都删边硬统计显然是过不了的。于是我们考虑构造加边。

容易想到离线,然后将 $ k $ 从大到小处理,持续加入边权大于等于 $ k $ 边。用并查集维护连通块的大小,指定节点所在的连通块大小减一(因为自身不计入答案)就是答案了。

至于边权大于等于 $ k $ 的边,容易想到将边按边权从大到小排序,用一个指针标记加入了哪些边,每次 $ k $ 变小则指针后移直到边权不符合条件。

时间复杂度 $ O ( n + q ) $ 。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n,t,fa[N],sz[N],ans[N],res;
struct query{
    int k;
    int ved;
    int ans;
    int id;
}q[N];
struct edge{
    int u;
    int v;
    int w;
}eg[N];
inline bool cmp(edge a,edge b){return a.w>b.w;}
inline bool cmpb(query a,query b){return a.k>b.k;}
inline bool cmpc(query a,query b){return a.id<b.id;}
inline int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}
inline void merge(int a,int b){
    int faa=find(a),fab=find(b);
    fa[faa]=fa[fab];
    sz[fab]+=sz[faa];
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>t;
    for(int i=1;i<n;i++)cin>>eg[i].u>>eg[i].v>>eg[i].w;
    for(int i=1;i<=t;i++)cin>>q[i].k>>q[i].ved,q[i].id=i;
    for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
    sort(eg+1,eg+1+n,cmp);
    sort(q+1,q+1+t,cmpb);
    int pos=1;
    for(int i=1;i<=t;i++){
        while(pos<n&&eg[pos].w>=q[i].k){
            merge(eg[pos].u,eg[pos].v);
            pos++;
        }
        q[i].ans=sz[find(q[i].ved)];
    }
    sort(q+1,q+1+t,cmpc);
    for(int i=1;i<=t;i++)cout<<q[i].ans-1<<'\n';
    return 0;
}

T5 字符合并

原题链接

题意概述

有一个长度为 $ n $ 的 01 串,你可以每次将相邻的 $ k $ 个字符合并,得到一个新的字符并获得一定分数。给定一些合并方案,假定你合并了对应十进制值为 $ x $ 的一个 01 串,那么这个 01 串就会转化为方案 $ x $ 的对应字符并获得方案 $ x $ 的对应分数。求最大分数。

solution

(第一次写紫题题解,质量或许不高。。。)

注意到 $ k $ 很小,大概率是暴搜 / 状压。而看题目的操作大概是类似于石子合并这样的类似区间dp的东西。

于是我们可以往区间dp + 状压dp上想。

设 $ dp_{l,r,S} $ 表示把区间 $ l $ 到 $ r $ 合并成 $ S $ 的最大分数。初始化把 $ dp $ 数组设置为 $ -INF $ 。

然后因为每次合并 $ k $ 个字符之后会变成 $ 1 $ 个字符,所以一个区间可以合并成一个字符当且仅当这个区间的长度 $ len \bmod (k-1) = 0 $ 。

我们枚举合并的中间点。为了方便转移,我们设定右边合并成一个字符,左边合并成一个状态,最后再一起合并。

那么这样会不会 WA 飞呢,仔细思考可以发现任意多字符状态的拼接都能拆成“左边保留一个状态、右边每次只贡献一个字符”的逐步拼接,所以固定右边合并成一个字符不会漏掉任何合并顺序。

回到正题,这个时候就要注意了 $ mid $ 不能一个个的枚举,因为如果右边要合并成一个字符的话那么 $ mid $ 到 $ r $ 的长度一定要保证 $ len \bmod (k-1) = 0 $ 。至于怎么做到直接每次增加 $ mid $ 都增加 $ k - 1 $ 就可以了。

我们定义一个 $ len $ 表示左边区间合并后剩余的长度,求这个长度的方法很好理解因为右边的长度模 $ ( k - 1 ) $ 为 $ 0 $ 所以这个长度(也就是左边长度对 $ ( k - 1 ) $ 取模)一定等于整个区间的长度对 $ ( k - 1 ) $ 取模。

然后如果 $ len $ 模 $ ( k - 1 ) $ 为 $ 0 $ ,那么整个区间是可以合并成一个字符,我们就做合并的转移,否则我们就只拼接状态不合并。

转移:

  • 对于合并操作,我们枚举左半边合并成的状态,右边则枚举合并成 $ 0 / 1 $ ,然后统计合并方案的最大值。

  • 对于拼接操作,我们正常区间dp即可。

最后遍历所有的 $ S $ ,找最大的 $ dp_{n,m,S} $ 即可。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=305,M=1<<7,INF=1e15;
int n,k,a[N],c[1<<8],w[1<<8],dp[N][N][M];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>k;
    string s;cin>>s;
    for(int i=0;i<s.size();i++){
        if(s[i]=='0')a[i+1]=0;
        else a[i+1]=1;
    }
    for(int i=0;i<(1<<k);i++)cin>>c[i]>>w[i];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int msk=0;msk<M;msk++){
                dp[i][j][msk]=-INF;
            }
        }
    }
    for(int i=n;i>=1;i--){
        for(int j=i;j<=n;j++){
            if(i==j){
                dp[i][j][a[i]]=0;
                continue;
            }
            int len=(j-i)%(k-1);
            if(len==0)len=k-1;
            if(len==k-1){
                int g[2]={-INF,-INF};
                for(int mid=j;mid>i;mid-=k-1){
                    for(int msk=0;msk<(1<<len);msk++){
                        int S0=msk<<1,S1=msk<<1|1;
                        g[c[S0]]=max(g[c[S0]],dp[i][mid-1][msk]+dp[mid][j][0]+w[S0]);
                        g[c[S1]]=max(g[c[S1]],dp[i][mid-1][msk]+dp[mid][j][1]+w[S1]);
                    }
                }
                dp[i][j][0]=g[0];
                dp[i][j][1]=g[1];
            } else {
                for(int mid=j;mid>i;mid-=k-1){
                    for(int msk=0;msk<(1<<len);msk++){
                        dp[i][j][msk<<1]=max(dp[i][j][msk<<1],dp[i][mid-1][msk]+dp[mid][j][0]);
                        dp[i][j][msk<<1|1]=max(dp[i][j][msk<<1|1],dp[i][mid-1][msk]+dp[mid][j][1]);
                    }
                }
            }
        }
    }
    int ans=-INF;
    for(int msk=0;msk<M;msk++)ans=max(ans,dp[1][n][msk]);
    cout<<ans;
    return 0;
}
posted @ 2026-10-09 14:04  fireclouds_11  阅读(57)  评论(6)    收藏  举报