22牛客多校1 补题

目录

G. Lexicographical Maximum (贪心)

 A. Villages: Landlines (区间合并)

D. Mocha and Railgun (几何 + 数学)

I. Chiitoitsu (概率dp)

C. Grab the Seat (几何 + 暴力)

J. Serval and Essay (启发式合并)


G. Lexicographical Maximum (贪心)

题目大意 : 给定 n,将 1, 2, . . . , n 视为不含前导零的字符串, 求这些字符串中字典序最大的字符串 1 ≤ n ≤ 10^1000000

思路 : 贪心的去在每一位取最大值(9)即可

代码如下: 

#include <bits/stdc++.h>

using namespace std;

#define endl "\n"
typedef long long ll;

string s;

void solve()
{
    cin>>s;
    string str;
    for(int i = 0; i<s.size()-1; i++)
    {
        str+='9';
    }
    for(char i = '9'; i>='0'; i--)
    {
        if(str+i<=s)
        {
            str+=i;
            break;
        }
    }
    cout<<str<<endl;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
//    int T;
//    cin>>T;
//    while(T--)
//    {
//        solve();
//    }
}

 A. Villages: Landlines (区间合并)

题目大意 : 数轴上有恰好一个发电站与 n − 1 个建筑物, 在数轴上放置一些电力塔使得所有建筑物通过电力塔与发电站连通, 能源站位于 xs,能与距离 rs 内的电力塔直接连通, 第 i 个建筑物位于 xi,能与距离 ri 内的电力塔直接连通, 可以消耗 |xA − xB| 长度的电线连通位于 xA 与 xB 的电力塔, 最小化消耗的电线长度.

思路 : 将影响范围看做一个区间, 要最小化的连接所有区间, 可以先进行区间合并, 在将没有交集的区间用端点连接即可

代码如下 : 

#include <bits/stdc++.h>
using namespace std;

stringstream ss;
#define endl "\n"
typedef long long ll;
typedef pair<ll, ll> PII;
typedef pair<pair<int, int>, int> PIII;
const int N = 2e5+10, M = 20, mod = 1e9+7;
const int INF = 0x3f3f3f3f;

int n;
int xs,rs;
struct Point
{
    int x,r;
}p[N];

bool cmp(Point a, Point b)
{
    return a.x-a.r<b.x-b.r;
}

vector<PII> seg;

void solve()
{
    cin>>n>>xs>>rs;
    p[1].x = xs, p[1].r = rs;
    for(int i = 2; i<=n; i++) cin>>p[i].x>>p[i].r;

    sort(p+1, p+n+1, cmp);

    int st = p[1].x-p[1].r, ed = p[1].x+p[1].r;

    ll res = 0;
    for(int i = 2; i<=n; i++)
    {
        int l = p[i].x-p[i].r, r = p[i].x+p[i].r;
        if(l>ed)
        {
            seg.push_back({st,ed});
            res += l-ed;
            st = l, ed = r;
        }else{
            st = min(st, l), ed = max(ed, r);
        }
    }
    cout<<res<<endl;
}

int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
//    int T;
//    cin>>T;
//    while(T -- )
//    {
//        solve();
//    }
}

D. Mocha and Railgun (几何 + 数学)

题目大意 : 给定一个圆和严格位于圆内的一点 P, 从点 P 向任意角度发射一个长度为 2d 的电磁炮, 电磁炮底边的中点为点 P 且两端位于圆内, 询问单次发射能摧毁的最大圆弧长

思路 : 由几何计算得, 当点P在某直径上, 射出方向与另一直径平行时, 弧长最长, 计算时注意精度问题, acos误差与标答较大可能会被卡.

 代码如下 : 

#include <bits/stdc++.h>

using namespace std;

stringstream ss;
#define endl "\n"
typedef long long ll;
typedef pair<ll, ll> PII;
typedef pair<pair<int, int>, int> PIII;
const int N = 2e5+10, M = 20, mod = 1e9+7;
const int INF = 0x3f3f3f3f;

void solve()
{
    double r,x,y,d;
    cin>>r>>x>>y>>d;

    double d1 = sqrt(x*x + y*y)+d, d2 = sqrt(x*x + y*y)-d;

    double l1 = sqrt(r*r - d1*d1), l2 = sqrt(r*r - d2*d2);
    double h = l2-l1, l = sqrt(h*h + 4.0*d*d);
    double res = 2.0*r*asin(l/(2.0*r));
    cout<<fixed<<setprecision(12)<<res<<endl;
}

int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//    solve();
    int T;
    cin>>T;
    while(T -- )
    {
        solve();
    }
}

I. Chiitoitsu (概率dp)

大致题意 : 初始手牌13张, 相同牌最多两张, 共34种牌, 每种牌4张, 每次从牌堆摸牌, 要求糊七对, 也就是摸到七对相同牌, 求最优策略下达到七对的轮数

思路 : 经典概率dp, 最优策略即上帝视角, 一定不会丢掉已有的单牌(即假定我一定摸到已有单排凑对)

考虑状态dp[i][j], 表示手上有i张单牌, 牌堆中还有j张牌时到达最终状态的轮数

, dp[i][j] = p1*dp[i-2][j-1] + p2*dp[i][j-1] + 1

p1表示从牌堆摸一张牌刚好与手牌中的一张组成对子, 手牌-2 牌堆-1, 概率为 3*i/j

p2 表示从牌堆摸一张牌与手牌没有组成对子, 直接弃牌 手牌不变, 牌堆-1, 概率为(j-3*i)/j

+1 表示摸一张牌

初始化最终状态 dp[1][i] = p*dp[1][i-1] + 1, 只剩一张手牌即游戏结束分界状态时, 从牌堆摸牌, 若凑对则结束, 若没有凑对继续 牌堆-1, 概率为 (i-3)/i,  +1 表示摸一张牌

因为答案取模, 所以要用逆元处理

代码如下 :

#include <bits/stdc++.h>
 
using namespace std;
 
stringstream ss;
#define endl "\n"
typedef long long ll;
typedef pair<ll, ll> PII;
typedef pair<pair<int, int>, int> PIII;
const int N = 1e5+10, M = 30, mod = 1e9+7;
const int INF = 0x3f3f3f3f;
 
int t,T;
int n;
ll dp[20][200]; // dp[i][j] 表示手上有i张单牌, 牌堆中还有j张牌时到达最终状态的期望
 
ll qmi(ll a, ll k)
{
    ll res = 1;
    while(k)
    {
        if(k & 1) res = res * a % mod;
        k>>=1;
        a = a*a % mod;
    }
    return res;
}
 
void solve()
{
    map<string,int> mp;
    string s;
    cin >> s;
    int cnt = 0;
    // 计算初始手牌单牌的数量
    for(int i = 0; i<s.size(); i+=2)
    {
        string str = "";
        str += s[i], str += s[i+1];
        mp[str]++;
    }
    for(int i = 0; i<s.size(); i+=2)
    {
        string str = "";
        str += s[i], str += s[i+1];
        if(mp[str] == 1) cnt++; 
    }
    cout<<"Case #"<<t-T<<": "<<dp[cnt][123]<<endl;
}
 
int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//    solve();
    // dp[i][j] 表示手上有i张单牌, 牌堆中还有j张牌时到达最终状态的期望
    for(int i = 3; i<=123; i++) // 初始化 dp[1][i] = 1 + (i-3)/i * dp[1][i-1]
    {
        dp[1][i] = (1 + (((i-3)*qmi(i, mod-2) % mod) * dp[1][i-1] % mod)) % mod;
    }
 
    // 预处理所有值 dp[i][j] = p1*dp[i-2][j-1] + p2*dp[i][j-1] + 1
    // p1 = (3*i)/j p2 = (j-3*i)/j; 
    for(int i = 3; i<=13; i+=2)
    {
        for(int j = 3; j<=123; j++)
        {
            dp[i][j] = (1 + (((i*3) * qmi(j, mod-2) % mod) * dp[i-2][j-1]%mod) + (((j-3*i) * qmi(j, mod-2) % mod)*dp[i][j-1]%mod))%mod;
        }
    }
//    int T;
    cin>>T;
    t = T;
    while(T -- )
    {
        solve();
    }
}

C. Grab the Seat (几何 + 暴力)

题目大意 : 二维平面, 在y轴上有一1到m的屏幕, 平面类, 有以(1,1) 到 (n,m)的矩阵, 矩阵中有k位座位已经就坐, q次询问, 每次修改一个人的坐标, 求到屏幕视线不被人挡住的座位

思路 : 对于y = 1 和 m 的情况, 只会挡住它身后即x坐标右方的人, 特判一下即可

一般情况, 可看作从(0, 1), (0, m)到该点的两条射线, 射线后的范围即为挡住范围, 整个区域的左边界可以看做为一个折线图, 只需计算折线图每行的横坐标即可, 我们可以分开计算

先看(0, 1)出发的射线, 红色区域即为被遮挡区域, 可以发现位于下面斜率小的点, 会被覆盖, 因为是位于下面的点, 所以斜率一定是小于的, 且观察不难发现 斜率具有单调性

同理从(0, m)一样, 反转一下就行, 最终答案其实就是二者并集, 即每次更新最小值即可

 代码如下 : 

#include <bits/stdc++.h>
 
using namespace std;
 
stringstream ss;
#define endl "\n"
typedef long long ll;
typedef pair<ll, ll> PII;
typedef pair<pair<int, int>, int> PIII;
const int N = 2e5+10, M = 30, mod = 1e9+7;
const int INF = 0x3f3f3f3f;
 
int n,m,k,q;
ll x[N], y[N]; // x, y记录每个编号的坐标
ll x1[N], minx[N]; // x1记录每行最靠左的横坐标(影响最大), minx记录影响
 
void solve()
{
    cin>>n>>m>>k>>q;
    for(int i = 1; i<=k; i++) cin>>x[i]>>y[i];
 
    while(q -- )
    {
        int id;
        cin>>id;
        cin>>x[id]>>y[id];
        for(int i = 0; i<m; i++) x1[i] = n+1; // 每次修改时初始化
 
        for(int i = 1; i<=k; i++) x1[y[i]-1] = min(x1[y[i]-1], x[i]); // 寻找每行影响范围更大的点(横坐标更靠前的), 纵坐标-1方便从(0, 0)点计算斜率
        
        for(int i = 0; i<m; i++) minx[i] = x1[i] - 1; // 初始化答案为每行坐人最小的横坐标
 
        // 先计算由(0, 0)到人的射线
        ll mx = n+1, my = 0;  // 记录最大斜率
        for(int i = 0; i<m; i++)
        {
            // cy/cx > my/mx 若当前斜率更大则更新斜率
            ll cx = x1[i], cy = i;
            if(cy*mx > cx*my) mx = cx, my = cy;
 
            ll res;
            if(my) res = ceil(mx*i*1.0 / my) - 1;   // 计算交点的横坐标 -1表示该交点可能也会被挡
            else res = n;  // 第一行不用计算, 直接特判为只会影响后面的人
 
            minx[i] = min(res, minx[i]);
        }
 
        // 反转依照(0, 0)射线代码来计算(0, m)的射线
        reverse(x1, x1+m);
        mx = n+1, my = 0;
        for(int i = 0; i<m; i++)
        {
            ll cx = x1[i], cy = i;
            if(cy*mx > cx*my) mx = cx, my = cy;
 
            ll res;
            if(my) res = ceil(mx*i*1.0 / my) - 1;
            else res = n;
 
            minx[m - i - 1] = min(res, minx[m - i - 1]);
        }
 
        ll ans = 0;
        for(int i = 0; i<m; i++) ans += minx[i];
        cout<<ans<<endl;
    }
}
 
int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
//    int T;
//    cin>>T;
//    while(T -- )
//    {
//        solve();
//    }
}

J. Serval and Essay (启发式合并)

大致题意 : 一个n个点m条边的无重边无自环的有向图, 初始均为白点, 若某个点的前驱节点均为黑点, 则该点可以被染黑, 初始可任意染黑一点, 求最大化图中黑点的数量

思路 : 首先可以画一张图, 如样例, 起始我们染黑1, 后续2, 3可被染黑, 不难发现, 1,2,3可以互相影响, 染黑3 不如 染黑2, 染黑2 不如 染黑 1, 这时我们可以将这三个点看做一个点, 即进行合并, 可以发现合并后并不会对原图产生影响, 如节点 5 在合并和 由{1, 2, 3} 和 {4} 控制并不能染黑, 若有另一点能到{1, 2, 3}, 则{1, 2, 3}并不能合并, 可知合并后的{1, 2, 3}的前后节点不受影响, 即当前合并后的节点集合的节点数即为最大, 若有多个集合取极大值即可

显然暴力是会超时的, 因为会遍历到所有重边来进行合并, 这里可以想到用启发式合并来进行操作, 对于两个可以连接的集合, 我们只遍历出边数小的那个集合, 将它与出边大的合并, 来优化时间复杂度, 最坏情况下m条边, 分为m/2, m/2两个集合, 每次只遍历一个, 不断向下合并, 复杂度为 O(m/2 * log m), 再加上需要用到set操作, 总时间复杂度为O(m/2 * log m * log m), 是可以做的

代码如下 :

 

#include <bits/stdc++.h>
 
using namespace std;
 
stringstream ss;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5+10, M = 30, mod = 1e9+7;
const int INF = 0x3f3f3f3f;
 
int n;
int p[N], sz[N]; // p为并查集集合, sz为集合大小
set<int> pre[N], ne[N]; // 分别记录前驱节点和后缀节点
 
int find(int x) 
{
    if(x != p[x]) p[x] = find(p[x]);
    return p[x];
}
 
void merge(int x, int y) // 启发式合并
{
    x = find(x), y = find(y);
    if(x == y) return;
    if(ne[x].size() < ne[y].size()) swap(x, y); // 保证x是出边大的那个
    p[y] = x, sz[x] += sz[y]; // 将y向x合并
    vector<PII> seg; // 合并一边x,y后, 对于{x, y}和其他点 可能还能继续合并, 用seg保存起来, dfs合并
    for(auto t : ne[y])
    {
        pre[t].insert(x), ne[x].insert(t), pre[t].erase(y); // 合并操作
        if(pre[t].size() == 1) seg.push_back({t, x});
    }
    for(auto t : seg) merge(t.first, t.second); // 继续合并
}
 
void solve(int t)
{
    cin>>n;
    for(int i = 1; i<=n; i++) p[i] = i, sz[i] = 1, pre[i].clear(), ne[i].clear();
 
    for(int i = 1; i<=n; i++)
    {
        int k;
        cin>>k;
        for(int j = 1; j<=k; j++)
        {
            int x;
            cin>>x;
            ne[x].insert(i);
            pre[i].insert(x);
        }
    }
    for(int i = 1; i<=n; i++)
    {
        if(pre[i].size() == 1) merge(*pre[i].begin(), i); // 对入边为1的点与当前点进行合并
    }
    int res = 0;
    for(int i = 1; i<=n; i++) res = max(res, sz[i]);
    cout<<"Case #"<<t<<": "<<res<<endl;
}
 
int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//    solve();
    int T;
    cin>>T;
    for(int i = 1; i<=T; i++)
    {
        solve(i);
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值