AtCoder Grand Contest 001

AtCoder Grand Contest 001

A - BBQ Easy

翻译

给你$2n$个数,需要两两配对成$n$对,每对的权值定义为两个数的较小值,求最大权值和。

题解

排序即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 202
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 ans,n,a[MAX<<1];
int main()
{
    n=read();
    for(int i=1;i<=n+n;++i)a[i]=read();
    sort(&a[1],&a[n+n+1]);
    for(int i=1;i<=n+n;i+=2)ans+=a[i];
    printf("%d\n",ans);
    return 0;
}

B - Mysterious Light

翻译

见洛谷

题解

大概画一个图,发现其实每次都等价于把一个$60°$平行四边形分解成若干个变成等于短边的等边三角形。
如果多出来了一部分,发现在干的事情是等价的,所以直接递归做就行了。
我交了几遍一直只有部分分,发现递归的函数带的参定义为了$int$。以后这种问题还是要注意。

#include<iostream>
using namespace std;
#define ll long long
ll n,x,ans;
ll calc(ll n,ll x)
{
    if(!x)return -n;
    ll d=n%x;
    return calc(x,d)+(n/x)*2*x;
}
int main()
{
    cin>>n>>x;ans=n;
    ans+=calc(n-x,x);
    cout<<ans<<endl;
    return 0;
}

C - Shorten Diameter

翻译

见洛谷

题解

为什么这么傻逼的题目我都不会做。。。果然菜的不行啊。
一个简单的想法就是我们钦定一个点作为根节点,然后删掉所有深度大于$K/2$的点。
如果$K$是奇数的时候就不能直接这么做,我们就钦定一条边,然后把这条边连接的两个点当成两棵子树,同样不准有点的深度大于$K/2$就好了。
时间复杂度$O(n^2)$,为什么我就不会做呢?我现在真是蠢得不行啊。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
#define MAX 2002
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;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,K,dep[MAX],tot,ans=1e9;
void dfs(int u,int ff)
{
    dep[u]=dep[ff]+1;
    if(dep[u]>K/2)++tot;
    for(int i=h[u];i;i=e[i].next)
        if(e[i].v!=ff)dfs(e[i].v,u);
}
int main()
{
    n=read();K=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read();
        Add(u,v);Add(v,u);
    }
    dep[0]=-1;
    if(K%2==0)
        for(int i=1;i<=n;++i)tot=0,dfs(i,0),ans=min(ans,tot);
    else
        for(int u=1;u<=n;++u)
            for(int i=h[u];i;i=e[i].next)
                dep[e[i].v]=-1,tot=0,dfs(u,e[i].v),dep[u]=-1,dfs(e[i].v,u),ans=min(ans,tot);
    printf("%d\n",ans);
    return 0;
}

D - Arrays and Palindrome

翻译

有两个和为$N$的数列${a},{b}$。
对于任意一个满足以下两个条件的长度为$N$的串$S$:

  • 前$a_1$个字符组成的串是回文串,接下来的$a_2$个字符组成的串是回文串,接下来$a_3$个......
  • 前$b_1$个字符组成的串是回文串,接下来的$b_2$个字符组成的串是回文串,接下来$b_3$个......
    都满足$S$的所有字符都相等。
    给定一个长度为$M$的数列$A$,并且已知$a$是$A$的一个排列。构造一个满足条件的$b$。

题解

如果只考虑其中一个数列,那么能够得到的信息是一系列的相等关系,那么,再通过错位的相等显然就可以得到一系列的连等关系。换种说法,就是把所有相等关系连起来,那么这些变恰好能够让你一笔画。
一笔画的条件就很好判断了,奇度点的个数不能超过$2$,什么情况下会出现奇度点?一个点会被少连一次当且仅当恰好在某一个奇数回文串的正中间。而如果一个点在两个序列的限制条件中都是自己连向自己的话,显然不联通从而无解。
那么就很好办了,只需要把$A$中的奇数段找出来,数下个数,如果合法,一个丢前面一个丢后面,然后构造$B$的时候只需要让他们错位就好了。
可以看看题解里面画的图。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#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 a[MAX],n,m,S[MAX],top;
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        a[i]=read();
        if(a[i]&1)S[++top]=i;
    }
    if(top>2){puts("Impossible");return 0;}
    if(top)swap(a[1],a[S[1]]);
    if(top>1)swap(a[m],a[S[2]]);
    if(m==1)
    {
        if(a[1]==1)printf("1\n1\n1\n");
        else printf("%d\n2\n%d 1\n",a[1],a[1]-1);
        return 0;
    }
    for(int i=1;i<=m;++i)printf("%d ",a[i]);puts("");
    printf("%d\n",m-(a[m]==1));
    printf("%d ",a[1]+1);
    for(int i=2;i<m;++i)printf("%d ",a[i]);
    if(a[m]>1)printf("%d ",a[m]-1);
    puts("");return 0;
}

E - BBQ Hard

翻译

洛谷

翻译其实有点问题。

应该是

扫描二维码关注公众号,回复: 3129324 查看本文章

$$\sum_{i=1}^n\sum_{j=i+1}^nC_{a[i]+b[i]+a[j]+b[j]}^{a[i]+a[j]}$$

题解

这题可以说非常妙了。

我们可以把这个值看做在网格图上的一点$(-a[i],-b[i])$走到$(a[j],b[j])$的方案数。
而网格图走的方案数可以直接递推得到。
那么我们对于每个点把它的坐标取反到第三象限,然后对于整个坐标系计算走到每一个格子的总方案。
把所有$(a[i],b[i])$的答案累加,再减去自己到自己的方案数,最后除二就是答案了。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 200200
#define MOD 1000000007
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
const int py=2010;
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 a[MAX],b[MAX],n,ans;
int f[4500][4500];
int inv[9000],jc[9000],jv[9000];
int C(int n,int m){return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)a[i]=read(),b[i]=read();
    for(int i=1;i<=n;++i)f[py-a[i]][py-b[i]]+=1;
    for(int i=1;i<=py*2;++i)
        for(int j=1;j<=py*2;++j)
            add(f[i][j],f[i-1][j]),add(f[i][j],f[i][j-1]);
    inv[0]=inv[1]=jc[0]=jv[0]=1;
    for(int i=1;i<py<<2;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    for(int i=2;i<py<<2;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    for(int i=1;i<py<<2;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    for(int i=1;i<=n;++i)add(ans,f[a[i]+py][b[i]+py]);
    for(int i=1;i<=n;++i)add(ans,MOD-C(2*(a[i]+b[i]),2*a[i]));
    ans=1ll*ans*inv[2]%MOD;printf("%d\n",ans);
    return 0;
}

F - Wide Swap

翻译

有一个长度为$n$的排列$P$,对于满足$|i-j|\ge K$的$i,j$,如果$|P_i-P_j|=1$,那么可以交换$P_i,P_j$。

求可能的最小字典序排列。

题解

神仙题我都只会看题解.jpg

发现$K$是一个很蛋疼的东西,于是转化一下(反正我不会.jpg),令$a_{P_i}=i$,得到了一个排列$a_i$。这样子问题等价于变成了,每次可以交换相邻两个位置,并且他们的差的绝对值要大于等于$K$。这样子性质优秀很多,首先我们自己yy一下,认为$P$的字典序要最小,等价于$a$的字典序要最小(似乎字典序最小和最小的数尽可能在前面是一样的?)。接着再考虑一下$a$的交换关系,如果两个数不能交换,那么他们两个的相对位置永远不会变,当且这个数和后面的所有数的相对位置也不可能改变了。相对位置确定了,如果没有确定的显然就是没有限制,那么一遍拓扑排序就可以搞定。

然而这样子的复杂度在最坏情况下边数是$O(n^2)$的。大概是多了些什么边呢?比如说$x,y,z$三个数,$,x\rightarrow y,x\rightarrow z,y\rightarrow z$,那么显然$x\rightarrow z$边是没有意义的。显然,对于任意一个$a_i$,我们连边的范围一定是$[a_{i-k},a_{i+k}]$之间的,上面那个式子告诉我们,显然只需要连向最近的一个点就可以了(比如之前那个连边,显然顺序是$x-y-z$),这样子用一个线段树找最近的位置就好了?

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 500500
#define lson (now<<1)
#define rson (now<<1|1)
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,K;
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1,dg[MAX];
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;dg[v]++;}
int a[MAX],P[MAX],ans[MAX],tot;
int t[MAX<<2];
priority_queue<int,vector<int>,greater<int> >Q;
void Modify(int now,int l,int r,int p,int w)
{
    if(l==r){t[now]=w;return;}
    int mid=(l+r)>>1;
    if(p<=mid)Modify(lson,l,mid,p,w);
    else Modify(rson,mid+1,r,p,w);
    t[now]=min(t[lson],t[rson]);
}
int Query(int now,int l,int r,int L,int R)
{
    if(L>R)return t[0];
    if(l==L&&r==R)return t[now];
    int mid=(l+r)>>1;
    if(R<=mid)return Query(lson,l,mid,L,R);
    if(L>mid)return Query(rson,mid+1,r,L,R);
    return min(Query(lson,l,mid,L,mid),Query(rson,mid+1,r,mid+1,R));
}
void Topsort()
{
    for(int i=1;i<=n;++i)if(!dg[i])Q.push(i);
    while(!Q.empty())
    {
        int u=Q.top();Q.pop();ans[u]=++tot;
        for(int i=h[u];i;i=e[i].next)
            if(!--dg[e[i].v])Q.push(e[i].v);
    }
}
int main()
{
    n=read();K=read();
    for(int i=1;i<=n;++i)a[P[i]=read()]=i;
    memset(t,63,sizeof(t));
    for(int i=n;i;--i)
    {
        int x=Query(1,1,n,a[i]+1,min(a[i]+K-1,n));
        if(x<1e9)Add(a[i],a[x]);
        x=Query(1,1,n,max(1,a[i]-K+1),a[i]-1);
        if(x<1e9)Add(a[i],a[x]);
        Modify(1,1,n,a[i],i);
    }
    Topsort();
    for(int i=1;i<=n;++i)printf("%d\n",ans[i]);
    return 0;
}

猜你喜欢

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