2022“杭电杯”中国大学生算法设计超级联赛(3)题解报告

Problem C. Cyber Language

题意:

给定小写的中文拼音,输出大写的首字母。

题解:

将第一个字母和空格后的字母大写输出就可以了。

代码:

#include<iostream>
#include<string>
#include<cstring>
using namespace std;

string s;

void solve(){
    getline(cin, s);
    cout << (char)(s[0] - 'a' +  'A');
    int len = s.length();
    bool flag = false;
    for(int i = 0; i < len; i++){
        if(s[i] == ' '){
            flag = true;
            continue;
        }
        if(flag){
            cout << (char)(s[i] - 'a' + 'A');
            flag = false;
        }
    }
    cout << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    getline(cin, s);
    while(t--){
        solve();
    }
}

Problem I. Package Delivery

题意;

有n个快递,给定到货日期和截止日期,每次最多拿k个,问最少需要几次拿完。

题解:

贪心。

首先按到货日期为第一要素排序,然后按照截止日期从早到晚依次看。我们设置一个缓冲地带,对于一个截止日期,我们首先看缓冲地带是否有截止日期早于当先考虑的截止日期,有则拿;然后考虑快递站里的包裹是否有到货日期早于该截止日期的,有则考虑它的截止日期,如果早于当前考虑的截止日期,则拿,否则放入缓冲区;最后再检查缓冲区,因为要尽可能拿满k个。

代码:

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#define int long long
using namespace std;

const int N = 1e5 + 10;

int n, k;

struct pak{
    int l, r;
    bool operator < (const pak a) const{
        return r > a.r;
    }
}p[N];

bool cmp(pak a, pak b){
    return a.l < b.l;
}

void solve(){
    cin >> n >> k;
    set<int> s;
    for(int i = 1; i <= n; i++){
        cin >> p[i].l >> p[i].r;
        s.insert(p[i].r);
    }
    sort(p + 1, p + 1 + n, cmp);
    priority_queue<pak> q;
    int cnt = 0, ans = 0, now = 1;
    auto i = s.begin();
    while(i != s.end()){
        int it = *i;
        while(!q.empty() && q.top().r <= it && cnt < k){
            q.pop();
            cnt ++;
        }
        while(now <= n && p[now].l < it && cnt < k){
            if(p[now].r <= it) cnt ++;
            else q.push(p[now]);
            now ++;
        }
        while(cnt && !q.empty() && cnt < k){
            cnt ++;
            q.pop();
        }
        if(cnt) cnt = 0, ans ++;
        else i ++;
    }
    cout << ans  << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem K. Taxi

题意:

二维坐标n个点,每个点有一个费用w,q次查询,每次给定一个坐标,社曼哈顿距离为d,问给定点到n个点中min(d, w)的最大值。

题解:

如果没有 w 的限制,那么是经典问题。根据 |x| = max(x, −x),有

max {|x′ − xi| + |y′ − yi|}

= max {max(x′ − xi, −x′ + xi) + max(y′ − yi, −y′ + yi)}

= max {x′ − xi + y′ − yi, −x′ + xi + y′ − yi, x′ − xi − y′ + yi, −x′ + xi − y′ + yi}

= max {(x′ + y′) + (−xi − yi), (x′ − y′) + (−xi + yi), (−x′ + y′) + (xi − yi), (−x′ − y′) + (xi + yi)}

分别记录 xi + yi、xi − yi、−xi + yi、−xi − yi 的最大值即可在 O(1) 时间内求出所有点到
(x′, y′) 的曼哈顿距离的最大值。
现在考虑加入 w 的限制。将所有城镇按照 w 从小到大排序,并记录排序后每个后缀的
xi + yi、xi − yi、−xi + yi、−xi − yi 的最大值,用于 O(1) 求给定点 (x′, y′) 到该后缀中所有点
的距离最大值。
选取按 w 排序后的第 k 个城镇,O(1) 求出给定点 (x′, y′) 到第 k..n 个城镇的距离最大值
d,有两种情况:

(1)w_{_{k}} < d,那么第 k..n 个城镇对答案的贡献至少为 w_{_{k}}。用 w_{_{k}} 更新答案后,由于第 1..k 个
城镇的 w 值均不超过 w_{_{k}},因此它们不可能接着更新答案,考虑范围缩小至 [k + 1, n]。

(2)w_{_{k}} ≥ d,那么第 k..n 个城镇对答案的贡献为 d。用 d 更新答案后,考虑范围缩小至
[1, k − 1]。

最后二分答案即可。

代码:

#include<iostream>
#include<algorithm>
#define int long long 
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;

const int N = 1e5 + 10;

int n, q, x, y, a[N], b[N], c[N], d[N];

struct node{
    int x, y, w;
}p[N];

bool cmp(node a, node b){
    return a.w < b.w;
}

void solve(){
    cin >> n >> q;
    for(int i = 1; i <= n; i++){
        cin >> p[i].x >> p[i].y >> p[i].w;
    }
    sort(p + 1, p + 1 + n, cmp);
    a[n + 1] = b[n + 1] = c[n + 1] = d[n + 1] = - INF;
    for(int i = n; i; i --){
        a[i] = max(a[i + 1], p[i].x + p[i].y);
        b[i] = max(b[i + 1], p[i].x - p[i].y);
        c[i] = max(c[i + 1], - p[i].x + p[i].y);
        d[i] = max(d[i + 1], - p[i].x - p[i].y);
    }
    while(q--){
        cin >> x >> y;
        int l = 1, r = n, ans = 0, tmp;
        while(l <= r){
            int mid = (l + r) >> 1;
            tmp = max(max(- x - y + a[mid], - x + y + b[mid]), max(x - y + c[mid], x + y + d[mid]));
            if(p[mid].w >= tmp){
                ans = max(ans, tmp);
                r = mid - 1;
            }
            else {
                ans = max(ans, p[mid].w);
                l = mid + 1;
            }
        }
        cout << ans << '\n';
    }
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

 Problem L. Two Permutations

题意:

给定长度为n的两个排列P和Q,一个长度为2n的数列S。每次从P或Q的最左侧去一个数字,加在R的末尾,问使R = S的取法有几种。

题解:

首先特判序列 S 中每个数字出现次数不都为 2 的情况,此时答案为 0。
动态规划,设 f (i,j) 表示 P 的前 i 项匹配上了 S,且 Pi 匹配 S 中数字 Pi 第 j 次出现的位
置时,有多少种合法的方案。由于 S 中每个数字出现次数都为 2,因此状态数为 O(n)。转移时
枚举 Pi+1 匹配哪个位置,那么 Pi 匹配的位置与 Pi+1 匹配的位置中间的那段连续子串需要完
全匹配 Q 中对应的子串,使用字符串 Hash 进行 O(1) 判断即可。

代码:

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<map>
#include<set>
#include<queue>
using namespace std;
#define debug(x) cout<<#x<<": "<<(x)<<endl
#define debug2(x,y) cout<<#x<<endl;for(int i=1;i<=y;++i)cout<<x[i]<<": ";cout<<endl
#define mem(x,y) memset(x,y,sizeof(x));
#define int long long
#define ll unsigned long long
#define double long double
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
const double PI=acos(-1.0);
const int b=131;
const int maxn=5e6+10;
ll bb[maxn];
const int mod=998244353;

int n;
int a[maxn],c[maxn];
int s[maxn];
int pos[maxn][3];
ll has1[maxn],has2[maxn];
int dp[maxn][3];

void init()
{
    bb[0]=1;
    for(int i=1;i<maxn;++i)
    bb[i]=bb[i-1]*b;
}

void hashhash()
{
    for(int i=1;i<=n;++i)
    has1[i]=has1[i-1]*b+c[i]*b;
    for(int i=1;i<=2*n;++i)
    has2[i]=has2[i-1]*b+s[i]*b;
}

ll get1(int l,int r)
{
    return has1[r]-has1[l-1]*bb[r-l+1];
}
ll get2(int l,int r)
{
    return has2[r]-has2[l-1]*bb[r-l+1];
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // cout<<fixed<<setprecision(6);
    init();
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n;
        for(int i=1;i<=n;++i)
        pos[i][1]=pos[i][2]=dp[i][1]=dp[i][2]=0;
        for(int i=1;i<=n;++i)
        cin>>a[i];
        for(int i=1;i<=n;++i)
        cin>>c[i];
        for(int i=1;i<=2*n;++i)
        {
            cin>>s[i];
            if(pos[s[i]][1])pos[s[i]][2]=i;
            else pos[s[i]][1]=i;
        }
        hashhash();
        if(get1(1,pos[a[1]][1]-1)==get2(1,pos[a[1]][1]-1))dp[1][1]=1;
        if(get1(1,pos[a[1]][2]-1)==get2(1,pos[a[1]][2]-1))dp[1][2]=1;
        for(int i=1;i<n;++i)
        {
            for(int j=1;j<=2;++j)
            {
                if(dp[i][j]==0)continue;
                int l=pos[a[i]][j],r=pos[a[i+1]][1];
                if(l<r&&get1(l-i+1,r-i-1)==get2(l+1,r-1))dp[i+1][1]=(dp[i+1][1]+dp[i][j])%mod;
                r=pos[a[i+1]][2];
                if(l<r&&get1(l-i+1,r-i-1)==get2(l+1,r-1))dp[i+1][2]=(dp[i+1][2]+dp[i][j])%mod;
            }
        }
        // for(int i=1;i<=n;++i)
        // cout<<dp[i][1]<<" ";cout<<'\n';
        // for(int i=1;i<=n;++i)
        // cout<<dp[i][2]<<" ";cout<<'\n';
        int ans=0;
        if(get1(pos[a[n]][1]-n+1,n)==get2(pos[a[n]][1]+1,2*n))ans=(ans+dp[n][1])%mod;
        if(get1(pos[a[n]][2]-n+1,n)==get2(pos[a[n]][2]+1,2*n))ans=(ans+dp[n][2])%mod;
        cout<<ans<<'\n';
    }
    return 0;
}

Problem B. Boss Rush

题意:

n个技能,打一个H血量的BOSS,每个技能有一个演出时间和持续时间,在演出时间里不能发动其他技能,给定持续效果每秒的伤害值。问最短时间干掉BOSS。

题解:

二分答案,转化为判断 T 帧内能否打败 BOSS,即求出 T 帧内能打出的最高伤害,判断是
否大于等于 H。

从前往后依次发动若干个技能,则下一个技能可以发动的时刻等于之前发动过的技能的演
出时间之和,因此只和之前发动过哪些技能有关。设 fS 表示发动了 S 集合的技能,在 T 帧内
最多能结算多少伤害,枚举不在 S 中的某个技能 x 作为下一个技能进行转移,由于技能发动时
刻已知,因此可以 O(1) 计算出在 T 帧内下一个技能可以结算多少伤害。

代码:

#include<iostream>
#include<algorithm>
#define int long long
using namespace std;

const int N = 1e5 + 10;
const int M = 18;

int n, t[M], d[M], ans, sum[(1 << M) + 1];

int hp, f[(1 << M) + 1], dmg[M][N];

bool check(int x){
    for(int i = 0; i < 1 << n; i++) f[i] = -1;
    f[0] = 0;
    for(int i = 0; i < 1 << n; i++){
        int tmp = f[i];
        if(tmp < 0) continue;
        if(tmp >= hp) return true;
        int tmpp = sum[i];
        if(tmpp > x) continue;
        for(int j = 0; j < n; j++){
            if(!(i >> j & 1)){
                if(tmpp + d[j] - 1 <= x) f[i | (1 << j)] = max(f[i | (1 << j)], tmp + dmg[j][d[j] - 1]);
                else f[i | (1 << j)] = max(f[i | (1 << j)], tmp + dmg[j][x - tmpp]);
            }
        }
    }
    return false;
}

void solve(){
    cin >> n >> hp;
    ans = -1;
    int l = 0, r = 0;
    for(int i = 0; i < n; i++){
        cin >> t[i] >> d[i];
        r += t[i] + d[i] - 1;
        for(int j = 0; j < d[i]; j++) cin >> dmg[i][j];
        for(int j = 1; j < d[i]; j++) dmg[i][j] += dmg[i][j - 1];
    }
    for(int i = 1; i < 1 << n; i++){
        sum[i] = sum[i - (i & -i)] + t[__builtin_ctz(i & -i)];
    }
    while(l <= r){
        int mid = (l + r) >> 1;
        if(check(mid)){
            ans = mid;
            r = mid - 1;
        }
        else l = mid + 1;
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

 Problem H. Laser Alarm

题意:

就是问给定空间内若干条线段,插入一个平面最多能和几条线段有交点。

题解:

三个不共线的点可以确定一个平面。对于任意一个平面,将其调整至经过三个顶点,结果
不会变差。因此枚举三个顶点得到平面,然后 O(n) 计算触碰了该平面的线段数,更新答案即
可。所有点都共线的情况需要特判。

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#define int long long
#define double long double
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;

const double eps = 1e-9;
const int N = 510;

struct dot{
    double x, y, z;
    dot(double xx = 0, double yy = 0, double zz =0) : x(xx), y(yy), z(zz){}
};

inline void scan(dot &a){cin >> a.x >> a.y >> a.z;}
inline void print(dot &a){cout << a.x << a.y << a.z;}

struct line{
    dot d, a, b;
    line(dot dd, dot aa, dot bb) : d(dd), a(aa), b(bb){}
};

struct plane{
    dot d, a;
    plane(dot dd, dot aa) : d(dd), a(aa){}
};

inline dot operator + (const dot &a, const dot &b){return dot(a.x + b.x, a.y + b.y, a.z + b.z);}
inline dot operator - (const dot &a, const dot &b){return dot(a.x - b.x, a.y - b.y, a.z - b.z);}
inline double operator * (const dot &a, const dot &b){return a.x * b.x + a.y * b.y + a.z * b.z;}
inline dot operator ^ (const dot &a, const dot &b)
{
    return dot(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
inline double len(const dot &a){return sqrt(a.x * a.x + a.y * a.y + a.z * a.z);}

int n;
dot a[N];

inline bool inte(const plane &p, const line &l){
    double t1 = (l.a - p.a) * p.d, t2 = (l.b - p.a) * p.d;
    if(abs(t1) < eps || abs(t2) < eps) return true;
    if((t1 > eps) ^ (t2 > eps)) return true;
    return false;
}

inline bool coll(const dot &a, const dot &b){
    return abs(len(a ^ b)) < eps;
}

void solve(){
    cin >> n;
    for(int i = 1; i <= 2 * n; i ++) scan(a[i]);
    int ans = 0;
    for(int i = 1; i < 2 * n; i ++){
        for(int j = 1; j < i; j ++){
            for(int k = 1; k < j; k++){
                dot t = (a[i] - a[j]) ^ (a[i] - a[k]);
                if(len(t) < eps) continue;
                plane p = plane(t, a[i]);
                int res = 0;
                for(int c = 1; c <= n; c++){
                    line l = line(a[2 * c - 1] - a[2 * c], a[2 * c - 1], a[2 * c]);
                    if(inte(p, l)) res ++;
                }
                ans = max(ans, res);
            }
            int res = 0;
            for(int c = 1; c <= n; c++){
                dot t = a[i] - a[j];
                if(coll(t, a[i] - a[2 * c - 1]) || coll(t, a[i] - a[2 * c])) res ++;
            }
            ans = max(ans, res);
        }
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值