zoukankan      html  css  js  c++  java
  • [Atcoder Grand Contest 001] Tutorial

    Link:

    AGC001 传送门

    A:

    ……

    #include <bits/stdc++.h>
    
    using namespace std;
    long long res=0;
    int n,dat[500];
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=2*n;i++) scanf("%d",&dat[i]);
        sort(dat+1,dat+2*n+1);
        for(int i=1;i<=2*n;i+=2) res+=dat[i];
        printf("%lld",res);
        return 0;
    }
    Problem A

    B:

    结论题,然后我还推了个假结论……

    并不太知道能怎么提升,估计多做做就好了吧

    官方题解的图没怎么看懂……结论为$3*(n-gcd(n,k))$

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long ll;
    ll n,k;
    ll gcd(ll a,ll b){return (a%b==0)?b:gcd(b,a%b);}
    
    int main()
    {
        scanf("%lld%lld",&n,&k);
        printf("%lld",3*(n-gcd(n,k)));
        return 0;
    }
    Problem B

    C:

    求要删去多少个点才能保证树的直径小于$k$

    我一开始直接找树的重心,以为复杂度能把其他人的碾过去……

    但实际上,树的直径的中点与树的重心无直接关系(如扫把型的树)

    正解是树形$dp$或直接分类讨论

    设化简后的图为$G$,

    1、如果$k$为偶数,则$G$中必有一点$v$使得所有$dist(v,x)le k/2$

    2、如果$k$为奇数,则$G$中必有一边$e$使得所有$dist(e,x)/le (k-1)/2$

    这样的点$v$、边$e$才是树的直径的中心!

    接下来只要枚举每个点/每条边作为树的直径的中心,取包含点的最大值即可

    #include <bits/stdc++.h>
    
    using namespace std;
    const int MAXN=2005;
    struct edge{int to,nxt;}e[MAXN<<2];
    int n,k,x,y,head[MAXN],dist[MAXN],tot=0,tmp=0,res=0,S=0;
    
    void add_edge(int from,int to)
    {
        e[++tot].nxt=head[from];e[tot].to=to;head[from]=tot;
        e[++tot].nxt=head[to];e[tot].to=from;head[to]=tot;
    }
    
    int dfs(int x,int anc,int lmt)
    {
        int ret=1;if(x==S) dist[x]=0;
        for(int i=head[x];i;i=e[i].nxt)
        {
            if(e[i].to==anc) continue;
            dist[e[i].to]=dist[x]+1;
            if(dist[e[i].to]<=lmt) ret+=dfs(e[i].to,x,lmt);
        }
        return ret;
    }
    
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<n;i++)
            scanf("%d%d",&x,&y),add_edge(x,y);
            
        for(int i=1;i<=n;i++)
            res=max(res,dfs(S=i,0,k/2));
        if(!(k&1)) return printf("%d",n-res),0;
        
        for(int i=1;i<=n;i++)
            for(int j=head[i];j;j=e[j].nxt)
                res=max(res,dfs(S=i,e[j].to,(k-1)>>1)+dfs(S=e[j].to,i,(k-1)>>1));
        printf("%d",n-res);
        return 0;
    }
    Problem C

    注意,如果直径为奇数,其中心为一条边

    D:

    构造题+推结论

    可以将回文串中的对应点连边,目标是使得所有点连通

    如果A串中有$a$个奇数,B串中有$b$个奇数,为保证连通则至少要有$n-1$条边:

    $由(n-a)/2+(n-b)/2ge n-1 可得 a+ble 2$

    因此,只可能$a=1,b=1$或$a=2,b=0$或$a=0,b=2$,其它情况均无解

    最后只要对这两种情况找到一种普遍构造即可

    #include <bits/stdc++.h>
    
    using namespace std;
    const int MAXN=1e5+10;
    int n,m,dat[MAXN],o1,o2,st;
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++) scanf("%d",&dat[i]);
        for(int i=1;i<=m;i++)
            if(dat[i]&1)
                if(!o1) o1=i;
                else if(!o2) o2=i;
                else return puts("Impossible"),0;
        
        if(o1) swap(dat[1],dat[o1]);
        if(o2) swap(dat[m],dat[o2]);
        for(int i=1;i<=m;i++) printf("%d ",dat[i]);
        
        if(m==1) m++;
        dat[1]--;dat[m]++;st=dat[1]?1:2;
        printf("
    %d
    ",m-st+1);
        for(int i=st;i<=m;i++) printf("%d ",dat[i]);
        return 0;
    }
    Problem D 

    此题将字符回文串转化为图论问题的思路值得借鉴

    E:

     要求$sum C(a_i+b_i+a_j+b_j,a_i+a_j)$

    发现数的范围只有2000,将每个组合数映射至$(-a_i,-b_i)-(a_j,b_j)$的路径条数

     这样每个终点的值就是其它所有点到其的路径和,即组合数的和!最后减去自己出发的贡献即可

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long ll;
    const int MAXN=2e5+10,MAXM=4010<<1,MOD=1e9+7;
    ll res,fac[MAXM],inv[MAXM],dp[MAXM][MAXM],ret;
    int n,a[MAXN],b[MAXN],mx,mx_sum;
    
    ll quick_pow(ll a,ll b)
    {
        for(ret=1;b;b>>=1,(a*=a)%=MOD)
            if(b&1) (ret*=a)%=MOD;
        return ret;
    }
    
    ll C(ll a,ll b)
    {return fac[a]*inv[a-b]%MOD*inv[b]%MOD;}
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d%d",&a[i],&b[i]),
            mx=max(mx,max(a[i],b[i])),mx_sum=max(mx_sum,a[i]+b[i]);
        
        for(int i=1;i<=n;i++)
            dp[-a[i]+mx][-b[i]+mx]++;
        for(int i=0;i<=(mx<<1);i++)
            for(int j=0;j<=(mx<<1);j++)
                if(dp[i][j]) (dp[i+1][j]+=dp[i][j])%=MOD,(dp[i][j+1]+=dp[i][j])%=MOD;
        for(int i=1;i<=n;i++) (res+=dp[a[i]+mx][b[i]+mx])%=MOD;
        
        mx_sum<<=1;fac[0]=1;
        for(int i=1;i<=mx_sum;i++) fac[i]=fac[i-1]*i%MOD;
        inv[mx_sum]=quick_pow(fac[mx_sum],MOD-2);
        for(int i=mx_sum;i;i--) inv[i-1]=inv[i]*i%MOD;
        
        for(int i=1;i<=n;i++)
            res=(res-C((a[i]+b[i])<<1,a[i]<<1)+MOD)%MOD;
        res=(res*(MOD+1)>>1)%MOD;
        printf("%lld",res);
        return 0;
    }
    Problem E

    F:

    将模型转化,明显可转化为使得从1到n每个数位置的字典序最小

    也就是让$pos[a[i]]=i$,只能将相邻的差大于$k$的位置交换,求最小字典序

    对于该问题,将相对位置不会改变的$|pos_i-pos_j|<k$的$i,j$相连

    那么最终结果即为该$DAG$的最小拓扑序

    为了缩减建图的复杂度,发现这样的偏序关系具有传递性,不用将所有关系都直接相连,可只连最近边

    从后往前对于每个$i$,用线段树找到在$[pos[i]-k+1,pos[i]]$和$[pos[i],pos[i]+k-1]$中最近的$j$连边即可

    #include <bits/stdc++.h>
    
    using namespace std;
    #define X first
    #define Y second
    #define mid ((l+r)>>1)
    #define lc k<<1,l,mid
    #define rc k<<1|1,mid+1,r
    #define pb push_back
    typedef double db;
    typedef long long ll;
    typedef pair<int,int> P;
    const int MAXN=5e5+10,INF=1<<28;
    struct edge{int nxt,to;}e[MAXN<<2];
    int head[MAXN],tot;
    int n,k,in[MAXN],a[MAXN],pos[MAXN],seg[MAXN<<2];
    
    void add_edge(int x,int y)
    {e[++tot]=(edge){head[x],y};head[x]=tot;in[y]++;}
    void Update(int pos,int val,int k,int l,int r)
    {
        seg[k]=min(seg[k],val);
        if(l==r) return;//最后一层也要先更新再返回! 
        if(pos<=mid) Update(pos,val,lc);
        else Update(pos,val,rc);
    }
    void Query(int &mn,int a,int b,int k,int l,int r)
    {
        if(a<=l&&r<=b)
        {mn=min(mn,seg[k]);return;}
        if(a<=mid) Query(mn,a,b,lc);
        if(b>mid) Query(mn,a,b,rc);
    }
    
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) 
            scanf("%d",&a[i]),pos[a[i]]=i;
        for(int i=0;i<=4*n;i++) seg[i]=INF;
        for(int i=n;i>=1;i--)
        {
            int nxt=INF,pre=INF;
            //注意询问必须保证与[1,n]相交 
            Query(nxt,pos[i],pos[i]+k-1,1,1,n);
            Query(pre,pos[i]-k+1,pos[i],1,1,n);
            if(nxt!=INF) add_edge(pos[i],pos[nxt]);
            if(pre!=INF) add_edge(pos[i],pos[pre]);
            Update(pos[i],i,1,1,n);
        }
        
        priority_queue<int,vector<int>,greater<int> > q;
        for(int i=1;i<=n;i++)
            if(!in[i]) q.push(i);
        for(int i=1;i<=n;i++)
        {
            int t=q.top();q.pop();
            pos[i]=t;
            for(int j=head[t];j;j=e[j].nxt)
                if(!(--in[e[j].to])) q.push(e[j].to);
        }
        for(int i=1;i<=n;i++) a[pos[i]]=i;
        for(int i=1;i<=n;i++) printf("%d
    ",a[i]);
        return 0;
    }
    Problem F

     Tip:线段树都能写挂……

    1、注意最后一层的更新

    2、要保证询问区间和$[1,n]$有交!

  • 相关阅读:
    适配器
    JAVA对list集合进行排序Collections.sort()
    ORACLE 日期加减操作
    将TIMESTAMP类型的差值转化为秒的方法
    Oracle 日期加减运算
    legend3---阿里云配置cdn服务
    legend3---阿里云添加 CNAME 记录提示和 A 记录冲突如何解决
    legend3---laravel将静态资源转移到阿里云oss
    legend3---laravel配置文件(自定义配置文件)
    laravel 自定义常量方法
  • 原文地址:https://www.cnblogs.com/newera/p/9253349.html
Copyright © 2011-2022 走看看