【ACM-ICPC】Nowcoder Summer Training Camp 2

B. Cannon

题意

第一行x个炮棋子,第二行y个炮棋子,隔着吃子。f1(i)表示吃i个棋子的吃法数,f2(i)表示先吃第一行再吃第二行吃i个棋子的吃法数
问:f1(i)异或和、f2(i)异或和对p取模?(x,y范围5e6)

解法分析

首先,在最后每行剩下2个棋子,就是说我们有第一行x - 2个操作,第二行y - 2个操作。
那么f1(i)就是从x + y - 4个操作中选择i个再进行排列,即 \(A_{x+y-4}^i\) 而且每个操作都有正反两遍,所以$$f1(i) = 2^i * A_{x+y-4}^i$$
第二问比较复杂,类似一问推出公式$$ f2(i) = 2^i * \sum_{k=0}i(A_{x-2}kA_{y-2}^{i-k})$$
将排列化为阶乘,最终得到$$f2(i) = 2^i * \frac{(x-2)!(y-2)!}{(x + y - i)!}
\sum_{k=n-i}nC_{x+y-4-i}k$$
左边预处理阶乘和阶乘逆元,右边要O(1)转移,预处理组合数前缀和,采用步移算法推导。

代码

#include <cstdio>
using namespace std;
#define LL long long
const LL p = 1e9 + 9;
LL f1[10000010];
LL fac[10000010], inv[10000010];
LL sc[10000010];
LL quick_pow(LL x, LL y)
{
    LL ans = 1;
    while (y)
    {
        if (y & 1)
            ans = ans * x % p;
        x = x * x % p;
        y >>= 1;
    }
    return ans;
}
LL C(LL x, LL y)
{
    if (x < y || x < 0 || y < 0)
        return 0;
    return fac[x] * inv[y] % p * inv[x - y] % p;
}
int main()
{
    int n, m;
    scanf("%d %d",&n,&m);
    n -= 2;
    m -= 2;
    LL ans1 = 0;
    f1[0] = 1;
    fac[0] = 1;
    for (int i = 1; i <= n + m; ++i)
        fac[i] = fac[i - 1] * i % p;
    inv[n + m] = quick_pow(fac[n + m], p - 2);
    for (int i = n + m; i >= 1; --i)
        inv[i - 1] = inv[i] * i % p;
    sc[n + m] = 1;
    for (int i = n + m; i > 0; --i)
        sc[i - 1] = sc[i] * 2 - C(n + m - i, n) - C(n + m - i, n - i),
        sc[i - 1] = (sc[i - 1] % p + p) % p;
    LL ans2 = 0, p2 = 1;
    for (int i = 0; i <= n + m; ++i)
    {
        ans1 ^= p2 * fac[i] % p * C(n + m, i) % p;
        ans2 ^= (LL)p2 * fac[n] % p * fac[m] % p * inv[n + m - i] % p * sc[i] % p;
        p2 = p2 * 2 % p;
    }
    printf("%lld %lld\n",ans1,ans2);
    return 0;
}

C. Draw Grids

题意

给出一个n * m的点阵,两个人进行连线游戏,连线规则是:每个人选定一个点和距离它为1的另一个点连接(上下左右),A开始连,连线过程中不能出现封闭几何图形,最终无法连线者输。

解法分析

是签到题捏!n * m的点阵,能连线n * m - 1
证明:以n * m个点连成树考虑,任意加一条边都会成环。

代码

//咳咳。。。。
#include <bits/stdc++.h>
using namespace std;
int a[5][5];
int main()
{
    for (int i = 1; i <= 4; ++i)
        for (int j = 1; j <= 4; ++j)
            a[i][j] = 1;
    a[1][1] = a[1][3] = a[3][1] = a[3][3] = 0;
    int n, m;
    cin>>n>>m;
    if (a[n][m])
        cout<<"YES";
    else 
        cout<<"NO";
    return 0;
}
#include <bits/stdc++.h>
using namespace std;
int a[5][5];
int main()
{
    int n, m;
    cin>>n>>m;
    if ((n * m - 1) & 1)
        cout<<"YES";
    else 
        cout<<"NO";
    return 0;
}

小结

比赛的时候没想很多因为nm 范围都是1 ~ 4。(直接打了表)

D. Er Ba Game

题意

二八游戏,按照规则判定赢家。

解法分析

模拟一下。

代码

//by hqy
#include <iostream>
typedef long long ll;
using namespace std;
ll t,a1,b1,a2,b2;
ll calc(ll x1,ll x2)
{
    if(x1>x2)
    {
        ll tt=x1;
        x1=x2;
        x2=tt;
    }
    
    if(x1==2 && x2==8)
    {
        return 1000000;
    }
    else if(x1==x2)
    {
        return x1*10000;
    }
    else 
    {
        return ((x1+x2)%10) * 100 + x2;
    }
}
int main() {
    cin>>t;
    while(t--)
    {
        cin>>a1>>b1>>a2>>b2;
        ll s1=0,s2=0;
        if(a1>b1)
        {
            ll tt=a1;
            a1=b1;
            b1=tt;
        }
        if(a2>b2)
        {
            ll tt=a2;
            a2=b2;
            b2=tt;
        }
    
        s1=calc(a1,b1);
        s2=calc(a2,b2);
        if(s1>s2)cout<<"first\n";
        else if(s1<s2)cout<<"second\n";
        else
        {
            cout<<"tie\n";
        }
    
    }
    return 0;
}

小结

采用计分方式

F. Girlfriend

题意

在三维空间中给定四个固定点A、B、C、D,两个不定点P1、P2,和比例系数k1、k2,满足

\[∣AP_1​∣ ≥ k_1​∣BP_1​∣,∣CP_2​∣ ≥ k_2​∣DP_2​∣ \]

求P1和P2轨迹所形成两个立体图形的共同区域(交集)的体积

解法分析

数学知识告诉我们P点所形成的立体图形是一个球(参考阿波罗尼斯圆),所以问题转化为了,求出两圆心和半径之后的体积交,分三种情况:相离(答案为0),包含(小球体积),相交(采用积分得出公式进行计算)。

代码

//by jrt
#include <bits/stdc++.h>
#define fir first
#define sec second
using namespace std;
const double eps=1e-3;
typedef pair<int,int> pii;
struct po {
    double x,y,z;
};
const double PI=acos(-1);
po A,B,C,D;
double k1,k2;
int main() {
    double ans1,ans2,Tx;
    int kase;
    po p1,p2,p3,p4,o1,o2;
    double r1,r2,L;
    scanf("%d",&kase);
    while(kase--) {
        scanf("%lf%lf%lf",&A.x,&A.y,&A.z);
        scanf("%lf%lf%lf",&B.x,&B.y,&B.z);
        scanf("%lf%lf%lf",&C.x,&C.y,&C.z);
        scanf("%lf%lf%lf",&D.x,&D.y,&D.z);
        scanf("%lf%lf",&k1,&k2);
        p1= {A.x+k1/(k1+1.0)*(B.x-A.x),A.y+k1/(k1+1.0)*(B.y-A.y),A.z+k1/(k1+1.0)*(B.z-A.z)};
        p2= {A.x+k1/(k1-1.0)*(B.x-A.x),A.y+k1/(k1-1.0)*(B.y-A.y),A.z+k1/(k1-1.0)*(B.z-A.z)};
        o1= {(p1.x+p2.x)/2.0,(p1.y+p2.y)/2.0,(p1.z+p2.z)/2.0};
        r1=sqrt((o1.x-p1.x)*(o1.x-p1.x)+(o1.y-p1.y)*(o1.y-p1.y)+(o1.z-p1.z)*(o1.z-p1.z));
        //printf("%lf %lf %lf %lf\n",o1.x,o1.y,o1.z,r1);

        p3= {C.x+k2/(k2+1)*(D.x-C.x),C.y+k2/(k2+1)*(D.y-C.y),C.z+k2/(k2+1)*(D.z-C.z)};
        p4= {C.x+k2/(k2-1)*(D.x-C.x),C.y+k2/(k2-1)*(D.y-C.y),C.z+k2/(k2-1)*(D.z-C.z)};
        o2= {(p3.x+p4.x)/2.0,(p3.y+p4.y)/2.0,(p3.z+p4.z)/2.0};
        r2=sqrt((o2.x-p3.x)*(o2.x-p3.x)+(o2.y-p3.y)*(o2.y-p3.y)+(o2.z-p3.z)*(o2.z-p3.z));
        //printf("%lf %lf %lf %lf\n",o2.x,o2.y,o2.z,r2);

        L=sqrt((o1.x-o2.x)*(o1.x-o2.x)+(o1.y-o2.y)*(o1.y-o2.y)+(o1.z-o2.z)*(o1.z-o2.z));
        Tx=(r1*r1-r2*r2+L*L)/(2.0*L);
        //printf("%lf %lf\n",L,Tx);
        if(L>=r1+r2)printf("0.0\n");
        else if(r1>=L+r2)printf("%.6lf\n",4.0/3.0*PI*r2*r2*r2);
        else if(r2>=L+r1)printf("%.6lf\n",4.0/3.0*PI*r1*r1*r1);
        else {
            ans1=PI*r1*r1*(r1 - Tx) - (1.0/3.0)*PI*(r1*r1*r1 - Tx*Tx*Tx);
            //	printf("%lf %lf\n",ans1,PI*r1*r1*(r1 - Tx));

            ans2=PI*r2*r2*(Tx-(L-r2)) + (1.0/3.0)*PI*((L-Tx)*(L-Tx)*(L-Tx) - r2*r2*r2);

            printf("%.6lf\n",ans1+ans2);
        }

    }
    return 0;
}

小结

中间由于不仔细除了些小差错,而且一开始忘记考虑相离、相包含情况,好在后来de出来了。

G. League of Legends

题意

n个区间\([a_i,b_i)\)要分成k组,求每组区间总交集的和的最大值。每组必须有一个区间,每组必须要有交集。无解输出0。

解法分析

对于能包含其他小区间的大区间,最优方案有两种:

  1. 单独成组,影响为少一个组,贡献为大区间总长。
  2. 放入自己能包含的区间内。影响贡献都无。

对于独立小区间,进行左端点排序,然后分组选取就成为连续的,若不连续肯定不会优于连续选取。考虑动态规划dp[i][j]表示排好后前j个区间分成i组的最优答案,dp转移方程:$$ dp[i][j] = max{dp[i - 1][t - 1] + b_t - a_j}$$其中t满足bt - aj > 0。
若暴力求解,时间复杂度为O(n^3),TLE。然后我们发现\(max\{dp[i - 1][t - 1] + b_t\}\)相当于滑动窗口取最大值,用单调队列优化,时间复杂度为O(n^2)。
然后大区间从大到小排序得到答案:

\[ans = max\{ dp[k-i][m1] + \Sigma_1^i len[t]\} \]

代码

#include <bits/stdc++.h>
using namespace std;
struct node
{
    int l, r;
} a[5010], b[5010];
int len[5010], dp[5010][5010];
int q[5010];
bool my_cmp(struct node a, struct node b)
{
    return a.r == b.r ? a.l > b.l : a.r < b.r;
}
bool my_cmp2(int a, int b)
{
    return a > b;
}
int main()
{
    int n, k, m1 = 0, m2 = 0;
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        cin >> a[i].l >> a[i].r;
    sort(a + 1, a + n + 1, my_cmp);
    // for (int i = 1; i <= n; ++i)
    //     cout << a[i].l<<' '<<a[i].r<<endl;
    for (int i = 1; i <= n; ++i)
    {
        if (!m1 || b[m1].l < a[i].l)
            b[++m1] = a[i];
        else
            len[++m2] = a[i].r - a[i].l;
    }
    memset(dp, -1, sizeof(dp));
    dp[0][0] = 0;
    for (int i = 1; i <= k; ++i)
    {
        int head = 1, tail = 0;
        for (int j = 1; j <= m1; ++j)
        {
            if (~dp[i - 1][j - 1])//可入队
            {
                while (head <= tail && dp[i - 1][j - 1] + b[j].r >= dp[i - 1][q[tail] - 1] + b[q[tail]].r)
                    --tail;
                q[++tail] = j;
            }
            while (head <= tail && b[q[head]].r <= b[j].l)
                ++head;
            if (head <= tail)
                dp[i][j] = dp[i - 1][q[head] - 1] + b[q[head]].r - b[j].l;
        }
    }
    int ans = 0, sum = 0;
    len[0] = 0;
    sort(len + 1, len + m2 + 1, my_cmp2);
    for (int i = 0; i <= min(m2, k); ++i)
    {
        sum += len[i];
        if (~dp[k - i][m1])
            ans = max(ans, dp[k - i][m1] + sum);
    }
    cout << ans;
    return 0;
}

小结

I. Penguins

题意

两个20 * 20的迷宫,A从迷宫1的(20,20)走向(1,20),B从迷宫2的(20,1)走向(1,1),A,B呈镜像走法:A向左,则B向右,但上下不变。问最短走法的最小字典序。

解法分析

显然,同一个局面不可能出现两次,用vis[a][b][c][d]记录A到(a,b),B到(c,d)的局面,然后按照“DLRU”的顺序bfs。
bfs队列长度最多20^4

代码

#include <bits/stdc++.h>
using namespace std;
bool vis[25][25][25][25];
struct node
{
    int a, b, c, d, pre, dep;
    char t;
}q[500 * 500];
char map1[25][25], map2[25][25];
void dfs(int x)
{
    map1[q[x].a][q[x].b] = 'A';
    map2[q[x].c][q[x].d] = 'A';
    if (q[x].pre)
        dfs(q[x].pre),
        cout<<q[x].t;
}
int main()
{
    for (int i = 1; i <= 20; ++i)
        cin>> map1[i] + 1 >> map2[i] + 1;
    for (int i = 1; i <= 20; ++i)
        map1[i][0] = map2[i][0] = map1[i][21] = map2[i][21]
      = map1[0][i] = map2[0][i] = map1[21][i] = map2[21][i] = '#';
    int x, head = 1, tail = 1;
    q[head] = (node){20, 20, 20, 1, 0, 0};
    vis[20][20][20][1] = 1;
    while (head <= tail)
    {
        int a = q[head].a, b = q[head].b, c = q[head].c, d = q[head].d;
        if (a == 1 && b == 20 && c == 1 && d == 1)
        {
            x = head;
            break;
        }
        int ta, tb, tc, td;
        //D
        if (map1[a + 1][b] == '#')
            ta = a, tb = b;
        else 
            ta = a + 1, tb = b;
        if (map2[c + 1][d] == '#')
            tc = c, td = d;
        else 
            tc = c + 1, td = d;
        if (!vis[ta][tb][tc][td])
        {
            vis[ta][tb][tc][td] = 1;
            q[++tail] = (node){ta, tb, tc, td, head, q[head].dep + 1, 'D'};
        }
        //L
        if (map1[a][b - 1] == '#')
            ta = a, tb = b;
        else 
            ta = a, tb = b - 1;
        if (map2[c][d + 1] == '#')
            tc = c, td = d;
        else 
            tc = c, td = d + 1;
        if (!vis[ta][tb][tc][td])
        {
            vis[ta][tb][tc][td] = 1;
            q[++tail] = (node){ta, tb, tc, td, head, q[head].dep + 1, 'L'};
        }
        //R
        if (map1[a][b + 1] == '#')
            ta = a, tb = b;
        else 
            ta = a, tb = b + 1;
        if (map2[c][d - 1] == '#')
            tc = c, td = d;
        else 
            tc = c, td = d - 1;
        if (!vis[ta][tb][tc][td])
        {
            vis[ta][tb][tc][td] = 1;
            q[++tail] = (node){ta, tb, tc, td, head, q[head].dep + 1, 'R'};
        }
        //U
        if (map1[a - 1][b] == '#')
            ta = a, tb = b;
        else 
            ta = a - 1, tb = b;
        if (map2[c - 1][d] == '#')
            tc = c, td = d;
        else 
            tc = c - 1, td = d;
        if (!vis[ta][tb][tc][td])
        {
            vis[ta][tb][tc][td] = 1;
            q[++tail] = (node){ta, tb, tc, td, head, q[head].dep + 1, 'U'};
        }
        ++head;
    }
    cout<<q[x].dep<<endl;
    dfs(x);
    cout<<endl;
    for (int i = 1; i <= 20; ++i)
        map1[i][0] = map2[i][0] = map1[i][21] = map2[i][21]
      = map1[0][i] = map2[0][i] = map1[21][i] = map2[21][i] = 0;
    for (int i = 1; i <= 20; ++i)
        cout << map1[i] + 1 <<' '<< map2[i] + 1 <<endl;
    return 0;
}

小结

暴力yyds

K. Stack

题意

模拟一个单调递增的单调栈,1 ~ n元素按照某种排列入栈,给定序列<ai,bi>,表示ai个元素入栈后栈中有bi个元素。问:是否有一种排列答案符合序列<ai,bi>?

解法分析

若不考虑元素取遍1 ~ n,考虑元素可重复情况,那么解法很容易,对于每个 <ai,bi>将ai取为bi,1取为1,其他未处理赋值为前一个+1,就是一个满足序列。然后考虑取遍1 ~ n,按照已经计算好的bi确定每个位置的优先级顺序,采用链式桶进行记录,从前向后遍历桶,每个桶中从后向前,能得到优先级顺序,然后得到每个数的位置,形成合法序列。
当然,排列不存在的情况就是 $$ b_1 != 1, b_i + 1 < b_{i+1} $$

代码

#include <bits/stdc++.h>
using namespace std;
int n, k;
int a[2333333], b[2333333], pos[2333333];
vector<int> bas[2333333];
int ans[2333333];
int main()
{
    int flag = 1;
    cin >> n >> k;
    memset(pos, 0, sizeof(pos));
    for (int i = 1; i <= k; ++i)
    {
        cin >> a[i] >> b[i];
        if (a[i] == 1)
        {
            if (b[i] != 1)
                flag = 0;
            --i;
            --k;
            continue;
        } //忽略1
        pos[a[i]] = b[i];
    }
    pos[1] = 1;
    for (int i = 1; i <= n; ++i)
    {
        if (!pos[i])
            pos[i] = pos[i - 1] + 1;
        if (pos[i] + 1 < pos[i + 1])
            flag = 0;
        bas[pos[i]].push_back(i);
    }
    if (!flag)
    {
        cout << -1;
        return 0;
    }
    int now = 0;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = bas[i].size() - 1; j >= 0; --j)
            ans[bas[i][j]] = ++now;
    }
    for (int i = 1; i < n; ++i)
        cout << ans[i] << ' ';
    cout << ans[n];
    return 0;
}

小结

非常巧妙的做法。

比赛总结

这次比赛由于只有一台电脑,反而在一定程度上更促使我们之间充分交流,在许多题目上都是两个人同时讨论来确定算法,这种方式之后可以继续沿用。

流程

  • 开局时,hqy看了A - D,lcj看了E - H,jrt看了I - L。
  • 很快我们发现了签到题的C和D,分别由lcj和hqy完成。
  • 然后jrt和lcj之前在分析K和F,后来两人提议交换分析,lcj提出阿波罗尼斯圆的思路,jrt完成了剩下题目的数学公式推导。
  • 与此同时hqy和lcj推出了K的规律,jrt上机写F,由于上机提交有一些错误,加上hqy那里K已经完全推出,所以hqy上机完成了K。
  • jrt再修改F,完成了F。
  • 与此同时lcj和hqy分析了I广搜的复杂度分析,lcj写了I。
  • jrt和hqy分析J题。再之后jrt上机写J题,但遇到2的次幂的取模问题始终无法解决。此时hqy和lcj分析G题,最后三人一起分析B题直到比赛结束。
    (由jrt记录编写)
posted @ 2021-07-21 11:49  cacu  阅读(52)  评论(0编辑  收藏  举报