2022牛客多校第一场A、C、D、G、I、J

A- Villages: Landlines

题意:

在一条横轴上给定 n n n个点横坐标 x s x_s xs和半径 r s r_s rs,可以在横轴上任意两点连线,问连接这 n n n个点形成的圆的连线最小长度为多少。

1 ≤ n ≤ 2 ∗ 1 0 5 , − 1 0 9 ≤ x s ≤ 1 0 9 , 1 ≤ r s ≤ 1 0 9 1\le n\le 2*10^5,-10^9 \leq x_s \leq 10^9, 1\leq r_s\leq 10^9 1n2105,109xs109,1rs109

思路:

等价于给了 n n n个区间起点和终点,排序贪心即可。时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

代码:

	cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int a, b;
        cin >> a >> b;
        w[i] = {a - b, a + b};
    }
    sort(w + 1, w + 1 + n);

    int ans = 0;
    int now = w[1].y;
    for (int i = 2; i <= n; i++)
    {
        if (w[i].x > now)
        {
            ans += w[i].x - now;
        }
        now = max(now, w[i].y);
    }
    cout << ans << endl;

C- Grab the Seat!

题意:

给定 n ∗ m n*m nm的二维平面,有 k k k个坐标点已有人。 q q q次询问,每次将第 p i p_i pi个坐标点的人换到 ( x i , y i ) (x_i,y_i) (xi,yi),问二维平面中好位置的数量,修改永久成立。

好位置的定义:对于一个位置 ( x i , y i ) (x_i,y_i) (xi,yi)满足与 ( 0 , 1 ) (0,1) (0,1) ( 0 , m ) (0,m) (0,m)的直线相交区间内没有人。

思路:

( 0 , 1 ) (0,1) (0,1)为例,从 ( 0 , 1 ) (0,1) (0,1)往所有有人的坐标点发出射线,对于同一 y i y_i yi x i x_i xi越小斜率越大,显然斜率越大的遮盖的面积越大,因此对于每一行维护到当前为止的最大斜率,根据直线公式算出向上取整的 x x x ( 0 , m ) (0,m) (0,m)做法类似,两者 x x x m i n min min,则 x − 1 x-1 x1为当前列合法的位置数。复杂度 O ( m q ) O(mq) O(mq)

代码:

#include <bits/stdc++.h>
#define int long long
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N = 200010, M = 2 * N, _ = 0, P = 13331;
typedef pair<int, int> pii;

int k, t, q, n, m;
pii w[N];
int pos[N];//当前y x最小的位置
int ans[N];//合法位置数量

void work()
{
    for (int i = 1; i <= m; i++)
        pos[i] = n + 1;
    for (int i = 1; i <= k; i++)
    {
        auto [x, y] = w[i];
        pos[y] = min(pos[y], x);
    }

    int dx = 1, dy = 0;
    // y=(dy/dx)x+1  x=(y-1)*dx/dy
    for (int i = 1; i <= m; i++)
    {
        if (i == 1)
            ans[i] = pos[i] - 1;
        else
        {
            int x = pos[i], y = i;
            if ((y - 1) * dx >= x * dy)
                dx = x, dy = y - 1;
            ans[i] = ((i - 1) * dx + dy - 1) / dy - 1; //向上取整
        }
    }

    dx = 1, dy = m;
    // y=(dy/dx)x+m  x=(y-m)*dx/dy
    for (int i = m; i >= 1; i--)
    {
        if (i == m)
            ans[i] = min(ans[i], pos[i] - 1);
        else
        {
            int x = pos[i], y = i;
            if ((y - m) * dx <= x * dy)
                dx = x, dy = y - m;
            ans[i] = min(ans[i], ((i - m) * dx + dy + 1) / dy - 1); //向上取整,dy是负数因此dy+1
        }
    }
    int res = 0;
    for (int i = 1; i <= m; i++)
        res += ans[i];
    cout << res << endl;
}

void solve()
{
    cin >> n >> m >> k >> q;
    for (int i = 1; i <= k; i++)
    {
        int a, b;
        cin >> a >> b;
        w[i] = {a, b};
    }
    while (q--)
    {
        int a, b, c;
        cin >> a >> b >> c;
        w[a] = {b, c};
        work();
    }
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int T;
    // cin >> T;
    T = 1;
    while (T--)
    {
        solve();
    }

    return (0 ^ _ ^ 0);
}

D- Mocha and Railgun

猜结论过的。复杂度 O ( 1 ) O(1) O(1)

代码:

	double r, x, y, d;
    cin >> r >> x >> y >> d;
    double oa = sqrt(x * x + y * y);
    double oe = oa - d;
    double boe = acos(oe / r);
    double of = oa + d;
    double cof = acos(of / r);
    double jj = fabs(boe - cof);
    cout << fixed << setprecision(12) << jj * r << endl;

G- Lexicographical Maximum

题意:

1 ∼ n 1\sim n 1n中字典序最大的数。 1 ≤ n ≤ 1 0 1000000 1\le n\le 10^{1000000} 1n101000000

思路:

n n n长度为 l e n len len,若 s t r [ 1 ∼ l e n − 1 ] str[1\sim len-1] str[1len1]全是 9 9 9,则 n n n即为最大数;否则 l e n − 1 len-1 len1 9 9 9为最大数。复杂度 O ( n ) O(n) O(n)

代码:

	cin >> str + 1;
    n = strlen(str + 1);
    int f=1;
    for(int i=1;i<n;i++)
        if(str[i]!='9')
        {
            f=0;
            break;
        }
    if(f)for(int i=1;i<=n;i++)cout<<str[i];
    else for(int i=1;i<n;i++)cout<<9;
    cout<<endl;

H- Fly

待补。无限期鸽。


I- Chiitoitsu

题意:

给定 13 13 13张初始手牌,初始手牌不会出现超过两张相同的牌。问在最优策略下单人麻将自动摸切最终以七对子自摸胡牌的期望回合数是多少。

思路:

一回合中摸牌只有两种情况:需要/不需要。

考虑概率 d p dp dp。假设当前手上还有 r e s res res张牌没凑成对子,牌河里还有 n o w now now张牌。若摸到的牌是需要的,概率为 r e s ∗ 3 n o w \frac{res*3}{now} nowres3,没凑成对子的数量少一张,并且打出一张没凑成对子的牌,总计 r e s − = 2 res-=2 res=2,牌河少一张 n o w − = 1 now-=1 now=1;若摸到是不需要的,概率为 n o w − r e s ∗ 3 n o w \frac{now-res*3}{now} nownowres3 r e s res res不变,牌河少一张 n o w − = 1 now-=1 now=1。递归求解。有 1 0 5 10^5 105组数据,预处理后 O ( 1 ) O(1) O(1)询问。

代码:

#include <bits/stdc++.h>
#define int long long
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N = 1000010, M = 2 * N, _ = 0, P = 13331;
typedef pair<int, int> pii;

int k, t, q, n, m;
int w[N];
char str[N];
int po[200];
int dp[200][200];
int cnt;
int ans[20];
int qmi(int a, int b, int p = mod)
{
    int res = 1;
    while (b)
    {
        if (b & 1)
            res = (ll)res * a % p;
        a = (ll)a * a % p;
        b >>= 1;
    }
    return res;
}

int dfs(int res, int now) // res剩下几种牌  now牌河
{
    if (~dp[res][now])
        return dp[res][now];
    if (res == 1)
    {
        dp[res][now] = ((res * 3) * po[now] % mod +//不需要+1因为当前回合已经自摸了
                        (now - res * 3) * po[now] % mod * (dfs(res, now - 1) + 1) % mod) %
                       mod;
        return dp[res][now];
    }
    dp[res][now] = ((res * 3) * po[now] % mod * (dfs(res - 2, now - 1) + 1) % mod
    			 + (now - res * 3) * po[now] % mod * (dfs(res, now - 1) + 1) % mod) % mod;//dfs()+1代表多一轮
    return dp[res][now];
}

void work(int x)
{
    memset(dp, -1, sizeof dp);
    for (int i = 0; i < 200; i++)
        dp[i][0] = 0;
    ans[x] = dfs(x, 123);
}

void solve()
{
    cin >> str + 1;
    n = strlen(str + 1);
    map<string, int> mp;
    for (int i = 2; i <= n; i += 2)
    {
        string tmp = "";
        tmp += str[i - 1];
        tmp += str[i];
        mp[tmp]++;
    }
    cnt = 0;
    for (auto [a, b] : mp)
        if (b % 2)
            cnt++;

    cout << ans[cnt] << endl;
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int T;
    for (int i = 1; i <= 150; i++)
        po[i] = qmi(i, mod - 2) % mod;
    cin >> T;
    // T=1;
    for (int i = 0; i <= 13; i++)
        work(i);
    for (int i = 1; i <= T; i++)
    {
        cout << "Case #" << i << ": ";
        solve();
    }

    return (0 ^ _ ^ 0);
}

J- Serval and Essay

题意:

有一张 n n n个点 m m m 条边的无重边无自环的有向图,初始时可以选择一个点染黑,其余点均为白点。若某个点所有入边的起点均为黑点,则该点可以被染黑。求最大化图中黑点数量。

多组数据, ∑ n ≤ 2 ∗ 1 0 5 , ∑ m ≤ 5 ∗ 1 0 5 \sum n\le2*10^5,\sum m\le 5*10^5 n2105,m5105

思路:

初始每个节点属于一个集合,集合大小为 1 1 1。假设当某一点成立时 T T T集合内的点全部成立 s i z e ( T ) ≥ 1 size(T)\ge1 size(T)1,且对于一个点 v v v,其入度为 1 1 1 p r e [ v ] ∈ T pre[v]\in T pre[v]T,则 v v v也将属于 T T T集合,显然能由 v v v得出的所有点也属于 T T T集合,启发式合并将两个集合合并。 o u t [ i ] out[i] out[i]记录集合 i i i中的点能到达的所有其他点。若 a ∈ o u t [ u ] & & a ∈ o u t [ v ] a\in out[u]\&\&a\in out[v] aout[u]&&aout[v],则将 d i n [ a ] − − din[a]-- din[a],当 d i n [ a ] = 1 din[a]=1 din[a]=1时,将 p r e [ a ] = u pre[a]=u pre[a]=u,并将其加入合并队列中,说明 u u u所在集合成立时就可推出 a a a;若 a ∉ o u t [ u ] & & a ∈ o u t [ v ] a\notin out[u]\& \&a\in out[v] a/out[u]&&aout[v],则将 a a a加入 o u t [ u ] out[u] out[u]中。若干次操作后将会形成森林,取森林中 s i z e size size最大值即可。时间复杂度 O ( ( n + m ) l o g 2 n ) O((n+m)log^2n) O((n+m)log2n)

代码:

#include <bits/stdc++.h>
#define int long long
#define x first
#define y second
#define endl '\n'
using namespace std;
const int N = 1000010, M = 2 * N, _ = 0, P = 13331;

int k, t, q, n, m;
int w[N];
set<int> out[N]; //集合的所有出边
int din[N], pre[N];
int id[N]; // i所属集合的编号
int qe[N], hh, tt;
int p[N], si[N];

int find(int x)
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}

void init()
{
    for (int i = 1; i <= n; i++)
    {
        out[i].clear();
        p[i] = i;
        si[i] = 1;
    }
    hh = 1, tt = 0;
}

int merge(int u, int v)
{
    u = find(u), v = find(v);
    if (u == v)
        return id[u];

    int idu = id[u], idv = id[v];
    p[v] = u; //合并集合
    si[u] += si[v];
    if (out[idv].size() > out[idu].size()) //启发式合并
        swap(idu, idv);

    for (auto it : out[idv]) //更新集合v中能到达的所有点的入度
    {
        auto itu = out[idu].find(it);
        if (itu == out[idu].end())
            out[idu].insert(it);
        else
        {
            din[it]--;
            if (din[it] == 1)
            {
                qe[++tt] = it;
                pre[it] = u;
            }
        }
    }
    return idu;
}

void solve()
{
    cin >> n;
    init();

    for (int i = 1; i <= n; i++)
    {
        cin >> din[i];
        for (int j = 1; j <= din[i]; j++)
        {
            int a;
            cin >> a;
            pre[i] = a;
            out[a].insert(i);
        }
        if (din[i] == 1) //如果入度为1则加进合并队列,说明假设pre[i]成立,i也成立
            qe[++tt] = i;
        id[i] = i;
    }

    while (hh <= tt)
    {
        int cur = qe[hh++];
        int pp = pre[cur];
        pp = find(pp);
        id[pp] = merge(pp, cur);
    }

    int ans = 0;
    for (int i = 1; i <= n; i++)
        ans = max(ans, si[i]);
    cout << ans << endl;
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int T;
    cin >> T;
    // T=1;
    for (int i = 1; i <= T; i++)
    {
        cout << "Case #" << i << ": ";
        solve();
    }

    return (0 ^ _ ^ 0);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值