AtCoder diverta 2019 Programming Contest 2

AtCoder diverta 2019 Programming Contest 2

看起来我也不知道是一个啥比赛。
然后就写写题解QWQ。

A - Ball Distribution

\(n\)个气球\(k\)个人,每个人至少要拿一个气球。问所有分配方案中拿走气球最多的那个人可以比最少的那个人多拿多少个。

打比赛的时候一开始没看到题,别人说输出\(n\% k\)就过了,然后我就过了。
现在重新一看题解。。。
因为是\(k=1\)时,答案是\(0\),否则答案是\(n-k\)

#include<iostream>
#include<cstdio>
using namespace std;
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int main()
{
    int n=read(),k=read();
    if(k==1)puts("0");
    else printf("%d\n",n-k);
    return 0;
}

B - Picking Up

平面上有若干个点,你可以任意指定两个数\(p,d\)
你现在要依次选定所有点,如果在选定某个点\((x,y)\)之前,存在点\((x-p,y-d)\)已经被选中了,那么代价就是\(0\),否则代价是\(1\)
求出选定所有点的最小代价。

不难想到\(p,d\)已经是某两个点的坐标的差,那么枚举一下,钦定\(p\)为正数,然后按照\(x\)从小往大排序,如果一样就按\(y\)排序,依次选中就行了。如果\(p=0\),就钦定\(d=0\)就行了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
#define MAX 55
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n;
struct Node{int x,y;}p[MAX];
bool operator<(Node a,Node b){if(a.x!=b.x)return a.x<b.x;return a.y<b.y;}
set<Node> M;
int ans=1e9;
int main()
{
    n=read();
    for(int i=1;i<=n;++i)p[i].x=read(),p[i].y=read();
    if(n==1){puts("1");return 0;}
    sort(&p[1],&p[n+1]);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
        {
            int P=p[j].x-p[i].x,Q=p[j].y-p[i].y;
            if(P<0)P=-P,Q=-Q;
            M.clear();int ret=0;
            for(int k=1;k<=n;++k)
            {
                if(!M.count((Node){p[k].x-P,p[k].y-Q}))++ret;
                M.insert(p[k]);
            }
                ans=min(ans,ret);
        }
    printf("%d\n",ans);
    return 0;
}

C - Successive Subtraction

\(n\)个数,执行下列操作\(n-1\)次:选定两个数\(a,b\),删去它们,并把\(a-b\)加入进来
问最后剩下的数的最大值,并且构造一种方案。

首先如果我们把操作画成树型结构,左儿子等价于\(+1\),右儿子等价于\(-1\),那么每个数的贡献就是到根节点的路径的乘积乘上自身的权值。
因为至少进行一次操作,所以至少一个数不能变号,至少一个数必须变号,其他数任意。
所以把所有数排序之后,最大值钦定不变号,最小值钦定变号,其他数如果是负数就变号,否则不变号。
构造方案的话,除了最大值和最小值之外,用最小值减去不变号的数,用最大值减去要变号的数,再用最大值减去最小值就行了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 100100
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,a[MAX],b[MAX],s;
vector<pair<int,int> > T;
int main()
{
    n=read();
    for(int i=1;i<=n;++i)a[i]=read();
    sort(&a[1],&a[n+1]);
    for(int i=2;i<n;++i)b[i]=a[i]<0;
    b[1]=1;b[n]=0;
    for(int i=2;i<n;++i)
        if(b[i])T.push_back(make_pair(a[n],a[i])),a[n]-=a[i];
        else T.push_back(make_pair(a[1],a[i])),a[1]-=a[i];
    T.push_back(make_pair(a[n],a[1]));
    printf("%d\n",a[n]-a[1]);
    for(auto a:T)printf("%d %d\n",a.first,a.second);
    return 0;
}

D - Squirrel Merchant

你一开始有\(n\)块钱,有两家商店\(A,B\)
你可以在两家商店把签换成金银铜或者把金银铜换成钱,并且在同一家商店用钱换一单位金银铜和一单位金银铜换钱的钱的数量是一样的。
现在你会先去\(A\)再去\(B\)再去\(A\)然后回家。
问回家后你最多有多少钱。
所有数字\(\le 5000\)

首先如果只先去\(A\)再去\(B\),那么做一个\(dp\)。设\(f[i]\)表示在\(A\)花了\(i\)块钱换东西,到了\(B\)之后最多能够卖的钱数。
这样子我们就知道了到了\(B\)之后我们有多少钱。
而这个钱数不会超过\(N*5000\),而\(5000*5000\)的数组是能够开下的,所以就再\(dp\)一次就做完了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
#define MAX 25000100
inline ll read()
{
    ll x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
ll f[MAX],n,gA,sA,bA,gB,sB,bB;
int main()
{
    n=read();
    gA=read();sA=read();bA=read();
    gB=read();sB=read();bB=read();
    for(int i=0;i<=n;++i)
    {
        if(i+gA<=n&&gA<gB)f[i+gA]=max(f[i+gA],f[i]+gB);
        if(i+sA<=n&&sA<sB)f[i+sA]=max(f[i+sA],f[i]+sB);
        if(i+bA<=n&&bA<bB)f[i+bA]=max(f[i+bA],f[i]+bB);
    }
    ll mx=0;
    for(int i=0;i<=n;++i)mx=max(mx,f[i]-i);
    n+=mx;
    memset(f,0,sizeof(f));
    for(int i=0;i<=n;++i)
    {
        if(i+gB<=n&&gA>gB)f[i+gB]=max(f[i+gB],f[i]+gA);
        if(i+sB<=n&&sA>sB)f[i+sB]=max(f[i+sB],f[i]+sA);
        if(i+bB<=n&&bA>bB)f[i+bB]=max(f[i+bB],f[i]+bA);
    }
    mx=0;
    for(int i=0;i<=n;++i)mx=max(mx,f[i]-i);
    printf("%lld\n",mx+n);
    return 0;
}

E - Balanced Piles

有若干个数,初始时都是\(0\)
加上当前的最小值为\(m\),最大值为\(M\),那么就可以随意选择一个值为\(m\)的数,把它变成\([M,M+D]\)中的一个任意整数。
问有多少种方法让所有数都变成\(H\)

很妙的一道题目。
如果我们要模拟这个过程的话,我们需要的是最大值、最小值。即使知道了,却也没有办法来计算答案,因为我们不知道这些最小值变化之后变成了哪些数。那么如果要模拟就必须知道所有的数。
继续考虑,如果我们知道最小值的个数,假设是\(k\)个,那么如果要改变最小值,一定要恰好\(k\)次操作,此时一共有\(k!\)种操作方案。而变成的最大值是什么、有多少个,显然是可以由我们任意确定的。
那么,假如我们知道了当前的最大值为\(M\),那么显然在操作之后可以任意的把最大值变为\([M,M+D]\)中的任意一个数。同时,我们从初始状态开始,可以任意的控制每次最小值的大小和个数。
那么这就很好办了,我们设\(f[i]\)表示最大值为\(i\)的方案数,它可以转移到\(f[i+1..i+D]\),系数为\(\sum_{i=1}^n i!\),这样子用前缀和就可以做到\(O(n)\)。这样做可行的原因是这些最大值在之后的操作中一定会变成最小值,并且个数也不会产生变化,而这个值的个数又是可以随意确定的,所以方案数恰好就是\(\sum_{i=1}^n i!\)
所以答案就是\(f[H]\frac{n!}{\sum_{i=1}^ni!}\),至于后面乘的系数,是因为在最小值为\(0\)的时候恰好有\(n\)个,所以方案数是\(n!\)而不是\(\sum_{i=1}^n i!\)

#include<iostream>
#include<cstdio>
using namespace std;
#define MOD 1000000007
#define MAX 2000200
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,H,D,jc[MAX],f[MAX],s[MAX];
int fpow(int a,int b){int s=1;while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}return s;}
int main()
{
    n=read();H=read();D=read();
    jc[0]=1;for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    int sj=0;for(int i=1;i<=n;++i)sj=(sj+jc[i])%MOD;
    f[0]=s[0]=1;
    for(int i=1;i<=H;++i)f[i]=1ll*sj*(s[i-1]+MOD-(i-D<=0?0:s[i-D-1]))%MOD,s[i]=(s[i-1]+f[i])%MOD;
    int ans=1ll*f[H]*jc[n]%MOD*fpow(sj,MOD-2)%MOD;
    printf("%d\n",ans);
    return 0;
}

F - Diverta City

有一张\(n\)个点的完全图,你需要确定任意两个点之间的边的边权,使得所有的、共\(\frac{n!}{2}\)条哈密顿回路的边权和都不相同。
要求所有边权都是正数,且边权和不超过\(10^{11}\)

什么神仙构造方法......
考虑增量法,每次加入一个点,定义每个点有一个权值\(f_i\),其中\(f=\{1,2,4,7,12,20,29,38,52,73\}\),这个数列满足任意两个数字都不相同、任意两个数字的和也都不相同。
每次连边的时候就是\(i\rightarrow j(i<j)\),边权为\((M+1)*a_i\),其中\(M\)是加入这个点之前的哈密顿路长度的最大值。
至于这样为啥是对的。。。。
因为每次新的哈密顿回路组成的两条边一定比之前的所有边都要大,所以互相不影响,且又全部不等。。。。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
int n,p[11],a[11]={0,1,2,4,7,12,20,29,38,52,73};
ll g[11][11],M;
int main()
{
    scanf("%d",&n);
    for(int i=2;i<=n;++i)
    {
        for(int j=1;j<i;++j)g[i][j]=g[j][i]=(M+1)*a[j];
        if(i==n)break;
        for(int j=1;j<=i;++j)p[j]=j;
        do
        {
            ll s=0;
            for(int j=1;j<i;++j)s+=g[p[j]][p[j+1]];
            M=max(M,s);
        }while(next_permutation(&p[1],&p[i+1]));
    }
    for(int i=1;i<=n;++i,puts(""))
        for(int j=1;j<=n;++j)
            printf("%lld ",g[i][j]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cjyyb/p/11041983.html