AtCoder Grand Contest 001 做题记录

欢迎回来

A - BBQ Easy

题意: 2N 2 N 根烤肉扦,第 i i 个烤肉扦长度为Li,每一块肉需要横跨两根烤肉扦,长度为 1 1 。可以随意分配烤肉扦的顺序,询问最多同时可以烤多少块肉。
N100,Li100,Li是整数

解答:将烤肉扦从小到大排序,下标为奇数的位置之和即为答案。

#include <bits/stdc++.h>
#define N 1050
using namespace std;
int a[N],n,ans;
inline int rd() {
    int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
int main() {
    n = rd() * 2;
    for (int i=1;i<=n;i++) a[i] = rd();
    sort(a+1,a+n+1);
    for (int i=1;i<=n;i+=2) ans += a[i];
    cout << ans << endl;
    return 0;
}

B - Mysterious Light

题意:有一个边长为 N N 的正三角形,顶点分别为a b b c,有一束激光从 ab a b 边上,距离 a a x的位置,平行于 bc b c 射入正三角形。激光遇到三角形的三边或者已经走过的光路时,会发生镜面反射。询问最后激光回到出发点时,走过光路的长度。
这里写图片描述
2N1012,1XN1 2 ≤ N ≤ 10 12 , 1 ≤ X ≤ N − 1

解答:答案为 3(Ngcd(N,X)) 3 ( N − g c d ( N , X ) )
原图形走两步就会变成一个平行四边形,两边分别为 (X,NX) ( X , N − X ) ,这两步的长度为N
考虑这样一个长宽为 a a b平行四边形,设需要的步数为 F(a,b) F ( a , b )
那么有这样一个关系式:
a>b a > b F(a,b)=F(b,a) F ( a , b ) = F ( b , a )
a=b a = b F(a,b)=a=b F ( a , b ) = a = b
a<b a < b F(a,b)=2a+F(ba,b) F ( a , b ) = 2 a + F ( b − a , b )
这样我们就已经得到一个log级别的递推算法了
我们还可以对这个式子化简
我们定义 phi(F(a,b))=a+b p h i ( F ( a , b ) ) = a + b
当a b ≠ b 时, phi p h i 每下降 x x F的值会增加 2x 2 x
a=b a = b 时, phi p h i 每下降 2x 2 x F F 的值会增加x
加上最开始走的两部,可以得知答案为 3(Ngcd(N,X)) 3 ( N − g c d ( N , X ) )

#include <bits/stdc++.h>
using namespace std;
inline int rd() {
    int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
int main() {
    long long n,m;
    cin >> n >> m;
    cout << 3LL * (n-__gcd(n,m));
    return 0;
}

C - Shorten Diameter

题意:给定一棵 N N 个节点的树,至少移除多少个节点,使得该树的直径K
2N2000 2 ≤ N ≤ 2000 1KN1 1 ≤ K ≤ N − 1 1AiN 1 ≤ A i ≤ N 1BiN 1 ≤ B i ≤ N
解答:若一个树的直径小于等于 K K ,则一定存在一条边,使得删除这条边之后,以边的两个端点为根的子树深度分别小于等于K12 K12 ⌊ K − 1 2 ⌋ 。爆枚这条边再dfs判深度即可,时间复杂度 O(N2) O ( N 2 )

#include <bits/stdc++.h>
#define N 100050
#define INF (1<<29)
using namespace std;
inline int rd() {
    int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

int a[N],b[N],n,k,tot;
vector<int> E[N];

void dfs(int u,int f,int d) {
    if (d > k/2) return ;
    tot++;
    for (int i=0;i<(int)E[u].size();i++) {
        int v = E[u][i];
        if (v == f) continue;
        dfs(v,u,d+1);
    }
}

int main() {
    n = rd(), k = rd();
    for (int i=1;i<n;i++) {
        int u = rd(), v = rd();
        a[i] = u, b[i] = v;
        E[u].push_back(v);
        E[v].push_back(u);
    }

    int ans = INF;
    if (k%2 == 0) {
        for (int i=1;i<=n;i++) {
            tot = 0;
            dfs(i, i ,0);
            ans = min(ans, n - tot);
        }
    } else {
        for (int i=1;i<n;i++) {
            tot = 0;
            dfs(a[i], b[i], 0);
            dfs(b[i], a[i], 0);
            ans = min(ans, n - tot);
        }
    }

    cout << ans << endl;
    return 0;
}

D - Arrays and Palindrome

题意:
给定一个长度为 M M ,和为N的序列{ A A },需要重排A并且构造一个数列{ B B }满足:
1、{B}的元素和为 N N
2、任何序列满足(1) (2) ( 2 ) 的都满足 (3) ( 3 )
(1)序列的这些元素是回文串:前 A1 A 1 个项,接下来 A2 A 2 项,接下来 A3 A 3 项……
(2)序列的这些元素是回文串:前 B1 B 1 个项,接下来 B2 B 2 项,接下来 B3 B 3 项……
(3)该序列的所有元素相同
1N105 1 ≤ N ≤ 10 5
1M100 1 ≤ M ≤ 100
Ai105 A i ≤ 10 5
解答:
转化一下模型,有一排 N N 个点,长度为L的回文串相当于连接 L2 ⌊ L 2 ⌋ 条无向边,要求最后所有的点都在一个联通分量里面。
显然至少需要 N1 N − 1 条边,那么无解的情况我们就可以通过这个来判断了。如果 A A 中的奇数项大于等于3,无解。
单独讨论一下M=1的情况
M2 M ≥ 2 时,将 A A 中的奇数项移动到两端,B1=A11 BM=AM1 B M = A M − 1 ,其余的 Bi=Ai B i = A i 即可。

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

int a[N],b[N],n,m,cnt,t;

inline int rd() {
    int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

int main() {
    n = rd(), m = rd();
    for (int _=1;_<=m;_++) a[_] = rd();
    sort(a+1,a+m+1, greater<int>() );

    if (m==1) {

        if (n==1) {printf("1\n1\n1\n"); return 0;}

        printf("%d\n",a[1]);
        printf("2\n");
        printf("%d %d\n",a[1]-1,1);
        return 0;
    }

    for (int _=1;_<=m;_++) (a[_]&1) ? ++cnt : 0;
    if (cnt > 2) return puts("Impossible"), 0;
    for (int _=2;_<=m-1;_++)
        if (a[_]&1) (a[1]&1) ? swap(a[_],a[m]) : swap(a[_],a[1]);
    for (int _=1;_<=m;_++) printf("%d%c",a[_],_==m?'\n':' ');
    (a[1]==1) ? (t=2) : (t=1);
    printf("%d\n",m-t+1);
    memcpy(b,a,sizeof(a));
    b[1]--, b[m]++;
    for (int _=t;_<=m;_++) printf("%d%c",b[_],_==m?'\n':' ');
    return 0;
}

E - BBQ Hard

题意:
给定 N N 个数{A}和{ B B },求

i=1nj=i+1nCAi+Bi+Aj+BjAi+Aj

解答:
CAi+AjAi+Bi+Aj+Bj C A i + B i + A j + B j A i + A j 可以看做是平面直角坐标系中,从 (Ai,Bi) ( − A i , − B i ) (Aj,Bj) ( A j , B j ) 最短路的数量。(只能从整点走到整点)
那么将每个 (Ai,Bi) ( − A i , − B i ) 作为起点,每个 (Ai,Bi) ( A i , B i ) 作为终点,最短路条数之和就能得到

i=1nj=1nCAi+BiAi+Bi+Aj+Bj ∑ i = 1 n ∑ j = 1 n C A i + B i + A j + B j A i + B i

减去 ni=1CAi+Bi2(Ai+Bi) ∑ i = 1 n C 2 ( A i + B i ) A i + B i 后再除以二即可

#include <bits/stdc++.h>
#define N 4050
#define c 2010
#define mod 1000000007
using namespace std;
typedef long long LL;
int n,a[50*N],b[50*N],F[N][N],jc[5*N];
inline int rd() {
    int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

inline void inc(int &x,int y) {x=(x+y)%mod;}

inline int qp(int a,int b) {
    int ret = 1;
    while (b) {
        if (b&1) ret = 1LL * ret * a % mod;
        b >>= 1, a = 1LL * a * a % mod;
    }
    return ret;
}

inline int C(int a,int b) {
    return 1LL * jc[a] * qp(jc[b], mod-2) % mod * qp(jc[a-b], mod-2) % mod;
}

int main() {
    n = rd();
    for (int _=1;_<=n;_++) a[_] = rd(), b[_] = rd();

    for (int _=1;_<=n;_++) F[c-a[_]][c-b[_]]++;
    for (int x=c-2000;x<=c+2000;x++)
        for (int y=c-2000;y<=c+2000;y++)
            inc(F[x][y], F[x-1][y]), inc(F[x][y], F[x][y-1]);

    jc[0] = 1;
    for (int i=1;i<=8000;i++) jc[i] = 1LL * jc[i-1] * i % mod;

    int ans = 0;
    for (int _=1;_<=n;_++) inc(ans, F[ c+a[_] ][ c+b[_] ]);
    for (int _=1;_<=n;_++) inc(ans, mod - C(2*a[_]+2*b[_], 2*a[_]) );
    ans = 1LL * ans * qp(2, mod-2) % mod;
    cout << ans << endl;
    return 0;
}

F - Wide Swap

题意:
给定一个长度为 N N 的排列P,对于 i i ,j满足 jiK j − i ≥ K |PiPj|=1 | P i − P j | = 1 就可以交换这两个元素,求字典序最的序列。
解答:
阅读和参考了官方题解以及
https://www.cnblogs.com/BearChild/p/7895719.html
Q Q P的转置,即 QPi=i Q P i = i
由字典序的性质可得,当 Q Q 字典序最小时,P字典序最小。
对排列 P P ,只有在两个元素相邻并且权值差不小于K时才能交换。
由此得出一个性质,当两元素 Px P x Py P y x<y x < y )的权值差小于 K K 时,无论怎么操作Px元素始终在 Py P y 元素前面。
显然,满足所有上述限制的排列,一定能够通过合法的交换得到。
那么对于权值差小于 K K 的两个元素Px Py P y ,我们从 Px P x Py P y 连一条边,那么一组合法解便满足该图的拓扑序。
这么连边的边数是 O(N2) O ( N 2 ) ,下面优化连边:
形如A–>B, B–>C, A–>C的连边中,A–>C这条边显然在拓扑关系中无用。
我们考虑如何避免加入 A–>C 这种边:将 Pi P i 连向 (PiK,Pi) ( P i − K , P i ) (Pi,Pi+K) ( P i , P i + K ) 两个区间下标最小的那一个即可。线段树维护区间最小值。
时间复杂度: O(NlogN) O ( N l o g N )

#include <bits/stdc++.h>
#define N 500050
#define INF (1<<29)

#define mid ((l+r)>>1)
#define ls l,mid,t<<1
#define rs mid+1,r,t<<1^1

using namespace std;
inline int rd() {
    int x=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

priority_queue<int, vector<int>, greater<int> >q;
int n,k,e[N],a[N],p[N],d[N];
int x,ll,rr,res;
int tr[4*N];
vector<int> E[N];

void query(int l,int r,int t) {
    if (!tr[t]) return ;
    if (l >= ll && r <= rr) {
        if (res == 0 || p[ tr[t] ] < p[res] )
            res = tr[t];
        return ;
    }
    if (ll <= mid) query(ls);
    if (rr > mid) query(rs);
}

void update(int l,int r,int t) {
    if (l > x || r < x) return ;
    if (l == r) {tr[t] = x; return ;}
    update(ls), update(rs);
    if (p[tr[t<<1]] < p[tr[t<<1^1]])
        tr[t] = tr[t<<1];
    else
        tr[t] = tr[t<<1^1];
}


bool cmp(int p1,int p2) {return p[p1] < p[p2];}
int main() {
    n = rd(), k = rd();
    for (int _=1;_<=n;_++) p[_] = rd();
    p[0] = INF;

    for (int _=1;_<=n;_++) e[_] = _;
    sort(e+1,e+n+1,cmp);

    for (int _=n;_>=1;_--) {
        x = e[_];

        ll = x - k + 1, rr = x, res = 0;
        query(1,n,1);
        if (res)
            E[x].push_back(res), d[res]++;

        ll = x, rr = x + k - 1, res = 0;
        query(1,n,1);
        if (res)
            E[x].push_back(res), d[res]++;

        update(1,n,1);
    }

    for (int _=1;_<=n;_++) if (!d[_]) q.push(_);
    for (int _=1;_<=n;_++) {
        int x = q.top(); q.pop();
        a[x] = _;
        for (int i=0;i<(int)E[x].size();i++)
            if (--d[ E[x][i] ]==0) q.push(E[x][i]);
    }

    for (int _=1;_<=n;_++) printf("%d\n",a[_]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值