AtCoder Grand Contest 018 做题记录

退役后的第一次补题
果然做题的时间拉长了好多
但是挺多题自己想出来了
也是蛮爽的

UPD:8.11 明早还要上课,能写多少写多少吧。
UPD:8.12 F留坑待填

A - Getting Difference

有点翻车,A想了好久才弄出来
题意:
N 个球,第i个球上写着 Ai 。进行如下操作:

  • 拿出两个球上面分别写着 x y,放回去三个球上面分别写着 x y |xy|

询问是否能在若干次操作后使得序列中存在球的值为 K
1N105 1Ai109 1K109
解答:
联系欧几里得算法,可以的得出进行若干次操作后,我们能得到的最小的数,是这些数的gcd。判断 K 是否小于A的最大值和差是否能被gcd整除即可。
时空复杂度: O(N)

#include <bits/stdc++.h>
#define N 1000500
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 n,k,mx,a[N];
int main() {
    n = rd(), k = rd();
    for (int _=1;_<=n;_++) a[_] = rd(), mx = max(mx, a[_]);
    int g = a[1];
    for (int _=2;_<=n;_++) g = __gcd(g, a[_]);
    if (k <= mx && (mx-k)%g == 0) puts("POSSIBLE"); else puts("IMPOSSIBLE");
    return 0;
}

B - Sports Festival

题意:
N 个人参加运动会,有M项运动提供选择。
每个人有一个 1 ~M的排列,表示对着 M 项运动的喜爱排名。
作为主办方,你现在要选取一些运动,举办这些运动的比赛。
运动员会参加被举办的运动中,自己最喜欢的那一个运动参加。
你需要选出一些运动,使得举办这些运动时,参加人数最多的那一项运动参赛人数最少,输出这个人数。
1N300
1M300
Ai1 , Ai2 , … , AiM 是一个 1 -M的排列。

解答:
不妨先选取所有的运动。
考虑目前参赛人数最多的运动 x ,当且仅当我们不选择该项运动,最后的答案会更优。
所以我们每次删去参赛人数最多的运动,然后对于每一个人分别统计答案。
时空复杂度:O(NM2)

#include <bits/stdc++.h>
#define N 305
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 n,m,a[N][N],vis[N],cnt[N];
int main() {
    n = rd(), m = rd();
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) a[i][j] = rd();
    for (int i=1;i<=m;i++) vis[i] = 1;
    int ans = n;
    for (int _=1;_<=m;_++) {
        memset(cnt,0,sizeof(cnt));
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++)
                if (vis[ a[i][j] ]) {
                    cnt[ a[i][j] ]++;
                    break;
                }
        int mx = 0, id = 0;
        for (int i=1;i<=m;i++)
            if (cnt[i] >= mx)
                mx = cnt[i], id = i;
        ans = min(ans, mx);
        vis[id] = 0;
    }
    printf("%d\n",ans);
    return 0;
}

C - Coins

题意:
现在有 X+Y+Z 个人,第 i 个人有Ai金币, Bi 银币, Ci 铜币。
每个人只能给你三种硬币中的一种,你需要指定 X 个人给金币,Y个人给银币, Z 个人给铜币。
求能得到硬币的最大值。
1X
1Y
1Z
X+Y+Z105
1Ai109
1Bi109
1Ci109

解答:
n=X+Y+Z
考虑 Z 等于零的情况
此时我们可以按照Ai- Bi 排序,前 X Ai Y Bi
Z 不为零的时候,相当于在排序后的序列上拿出Z个位置取 Ci ,剩余的前 X 个取Ai Y 个取Bi
那么序列中显然存在一个或多个位置,使得在这个位置之前的所有元素选择 Ai Ci ,在这个位置之后的所有元素选择 Bi Ci
爆枚这个位置,用堆维护元素的差值,算出在 [1,x] 区间以及 (x,n] 区间中最优的和,更新答案即可。
时间复杂度: O(Nlog2N) ,空间复杂度: O(N)

#include <bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long LL;
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;
}

struct HbFS{int a,b,c;}Q[N];
bool cmp(HbFS p1, HbFS p2) {return p1.a-p1.b > p2.a-p2.b;}
priority_queue<int, vector<int>, greater<int> > q1,q2;
int x,y,z,n;
LL L[N],R[N];

int main() {
    x = rd(), y = rd(), z = rd();
    n = x + y + z;
    for (int _=1;_<=n;_++)
        Q[_].a = rd(), Q[_].b = rd(), Q[_].c = rd();
    sort(Q+1,Q+n+1,cmp);

    for (int _=1;_<=x;_++)
        q1.push(Q[_].a - Q[_].c), L[_] = L[_-1] + Q[_].a;
    for (int _=x+1;_<=n;_++) {
        int cur = Q[_].a - Q[_].c;
        L[_] = L[_-1];
        if (cur > q1.top()) {
            int tmp = q1.top();
            q1.pop();
            q1.push(cur);
            L[_] -= tmp;
            L[_] += Q[_].a;
        } else
            L[_] += Q[_].c;
    }

    for (int _=n;_>=n-y+1;_--)
        q2.push(Q[_].b - Q[_].c), R[_] = R[_+1] + Q[_].b;
    for (int _=n-y;_>=1;_--) {
        int cur = Q[_].b - Q[_].c;
        R[_] = R[_+1];
        if (cur > q2.top()) {
            int tmp = q2.top();
            q2.pop();
            q2.push(cur);
            R[_] -= tmp;
            R[_] += Q[_].b;
        } else
            R[_] += Q[_].c;
    }

    LL ans = 0LL;
    for (int _=x;_<=n-y;_++) ans = max(ans, L[_]+R[_+1]);
    cout << ans << endl;
    return 0;
}

D - Tree and Hamilton Path

题意:
给定一个 N 个节点的带权树,定义图上x y 两点的距离是树上x y 两点的最短距离。求该图的最长一条哈密尔顿路径的长度。
2N105
1Ai<BiN
给定的图是一棵树
1Ci108
读入的数都是整数
解答:
一条边最后贡献答案的次数,至多是两倍拿掉该条边后的两个连通块中较小的那个连通块大小,记这个值为 S
由子树大小,联想到树的重心,分两种情况。
树的重心,即树上的一个点,使得拿掉这个点之后树上最大连通块的大小n2,一棵树至多存在两个重心(且这两个重心相邻),至少存在一个重心。

  • 树只存在一个重心。这条哈密尔顿路径的两端至少有一端不为该树的重心,则重心到连向该点所在子树的根这一条边是无法达到它所能经过最多次数的上限的。若令哈密尔顿路径起点为重心,终点为该子树的根,是可以构造一条合法的哈密尔顿路径达到另外所有边的上限的。(即在各个子树间来回跳)。选取一条连接重心权值最小的边的两端作为起点和终点即可。
    最终答案为 Smin( 连接重心的一条边的边权$)
  • 树存在两个重心。连接这两个重心的那一条边,至多可以经过 n1 次。(同样考虑哈密尔顿路径的起点和终点),同样我们以这条边的两个端点分别作为哈密尔顿路径的起点和终点,在两个子树间来回跳即可达到最大值。
    最终答案为 S( 连接两个重心那条边的权值 )

综上所述,即重心一定要作为哈密尔顿路径的起点或者终点。
通过树上dp求解这个问题,时空复杂度: O(N)

#include <bits/stdc++.h>
#define N 1000500
using namespace std;
typedef long long LL;
inline int rd() {int r;scanf("%d",&r);return r;}

struct Edge{int b,v,n;}e[2*N];
int siz[N],h[N],cnt,mark,g,n;
LL ans;

void link(int a,int b,int v) {
    e[++cnt] = (Edge){b,v,h[a]}, h[a] = cnt;
}

void dfs(int u,int f,int p) {
    siz[u] = 1;
    int mx = 0;
    for (int i=h[u];i;i=e[i].n) {
        int v = e[i].b, cp = e[i].v;
        if (v == f) continue;
        dfs(v, u, cp);
        siz[u] += siz[v];
        mx = max(mx, siz[v]);
    }
    mx = max(mx, n-siz[u]);
    if (siz[u]*2 == n) mark = p;
    if (mx*2 <= n) g = u;
    ans += 2LL * p * min(siz[u], n-siz[u]);
}

int main() {
    n = rd();
    for (int i=1;i<n;i++) {
        int a = rd(), b = rd(), c = rd();
        link(a, b, c);
        link(b, a, c);
    }

    dfs(1,1,0);

    if (!mark) {
        mark = 2147483647;
        for (int i=h[g];i;i=e[i].n)
            mark = min(e[i].v, mark);
    }
    cout << ans - mark << endl;
    return 0;
}

E - Sightseeing Plan

题意:
给定一个网格图,你需要制定一个旅行计划:

  1. 起点的横坐标在区间 [X1,X2] ,纵坐标在区间 [Y1,Y2]
  2. 途径一个特殊点,横坐标在区间 [X3,X4] , 纵坐标在区间 [Y3,Y4]
  3. 终点的横坐标在区间 [X5,X6] ,纵坐标在区间 [Y5,Y6]

求有多少条不同的最短路。
两条路径是不同的,当且仅当路径的起点不同,选择的特殊点不同,终点不同,或者途径的点不同。答案对 109+7 取模。
1X1X2<X3X4<X5X6106
1Y1Y2<Y3Y4<Y5Y6106

解答:
这题有点恶心,真的再也不想见到这种题。
定义 Fi,j 表示网格图上从点(1,1)走到点(i,j)的不同的最短路径个数
Fi,j=Cii+j
利用: Fi,j=Fi1,j+Fi,j1
可以得出: ix=1jy=1Fx,y=Fi+1,j+11
类比二维前缀和,可以将起点的区域转化成四个带权的单点
+1 : (x2+1,y2+1), (x1,y1)
-1 : (x1,y2+1), (x2+1,y1)
同理,也可以将终点的区域转化成四个带权的单点
原问题可以转化成求16个固定起点和终点,在固定区域 [x3,x4][y3,y4] 中选择一个中点,不同的最短路是多少
考虑这条路径进入固定区域的点以及离开固定区域的点,原问题路径按照进入固定区域的点和离开固定区域的点分类,最后答案乘上在固定区域内行走的路径长度,即为所求。
时空复杂度为 O(106) ,来源于组合数的预处理

#include <bits/stdc++.h>
#define mod 1000000007
#define N 2000500

using namespace std;
typedef long long LL;
LL Fac[N],Inv[N],Iac[N];
int x[7],y[7];
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(LL &x,LL y) {x=(x+y)%mod;}

inline LL C(int x,int y) {
    return 1LL * Fac[x+y] * Iac[x] % mod * Iac[y] % mod;
}

inline LL W(int x1,int y1,int x2,int y2) {
    x1 = abs(x1); y1=abs(y1); x2=abs(x2); y2=abs(y2);
    return (1LL *C(x2+1,y2+1) - C(x2+1,y1) - C(x1,y2+1) + C(x1,y1)+mod + mod) % mod;
}


void init() {
    for (int i=1;i<=6;i++) x[i] = rd();
    for (int i=1;i<=6;i++) y[i] = rd();
}

void prepare() {
    int n = 2000000;
    Fac[0] = Fac[1] = 1;
    for (int i=2;i<=n;i++) Fac[i] = 1LL * Fac[i-1] * i % mod;
    Inv[0] = Inv[1] = 1;
    for (int i=2;i<=n;i++) Inv[i] = 1LL * (mod - mod / i) * Inv[mod%i] % mod;
    Iac[0] = Iac[1] = 1;
    for (int i=2;i<=n;i++) Iac[i] = 1LL * Iac[i-1] * Inv[i] % mod;
}

void solve() {
    LL ans = 0LL;
    for (int _=x[3];_<=x[4];_++)
        inc(ans, 1LL*(mod-_-y[3])*W(_-x[2],y[3]-1-y[2],_-x[1],y[3]-1-y[1])%mod * W(_-x[5],y[3]-y[5],_-x[6],y[3]-y[6])%mod);

    for (int _=x[3];_<=x[4]; _++)
        inc(ans, 1LL*(_+y[4]+1)*W(_-x[2],y[4]-y[2],_-x[1],y[4]-y[1])%mod*W(_-x[5],y[4]-y[5]+1,_-x[6],y[4]-y[6]+1)%mod);

    for (int _=y[3];_<=y[4];_++)
        inc(ans, 1LL*(mod-_-x[3])*W(x[3]-1-x[2],_-y[2],x[3]-1-x[1],_-y[1])%mod *W(x[3]-x[5], _-y[5], x[3]-x[6], _-y[6])%mod);

    for (int _=y[3];_<=y[4];_++)
        inc(ans, 1LL*(_+x[4]+1)*W(x[4]-x[2],_-y[2],x[4]-x[1],_-y[1])%mod *W(x[4]-x[5]+1,_-y[5],x[4]-x[6]+1,_-y[6])%mod);

    cout<<ans<<endl;
}

int main() {
    init();
    prepare();
    solve();
    return 0;
}
F留坑待填
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值