Atcoder AGC001

AGC001:

B:[AGC001B] Mysterious Light

题意:

给定一个光源,横着入射一个等边三角形,射出的光线形成新的三角形的边,在边上反射,求所有光线的长度。

分析:

我们根据给定样例的图,发现每个方向的长度其实是相同的。

而且画一些图,可以得出来一个结论:

每个方向边的总长 \(=n-gcd(n,x)\)

我们根据 \(gcd\) 的过程证明一下,还是比较容易理解的。

代码:

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,x,ans;
signed main(){
    cin>>n>>x;
    ans=3*(n-__gcd(n,x));
    cout<<ans<<endl;
    system("pause");
    return 0;
}

C: [AGC001C] Shorten Diameter

题意:

给定一个树,要求删除一些点使树的直径小于等于 \(k\) ,而且不影响连通性。

求删除的点最小值。

分析:

看数据范围,支持枚举每一个点。

考虑分析 \(k\) 奇偶性:

\(k\) 为偶数

\(x\) 为根的树,向左右两边拓展 \(k/2\) 的长度,统计节点个数 \(ans\)

\(k\) 为奇数

这种情况下,枚举点,左右两边的长度不同,因此考虑枚举边:

通过边的两点分别向下延申 \(k/2\) 的长度,统计左右两点节点个数加和 \(ans\)

最终需要删除的就是 \(n-ans\) 个节点。

代码:

#include<bits/stdc++.h>
const int N=1e5+5;
using namespace std;
int n,m;
int a[N],b[N],tot,num;
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++) scanf("%d",&a[i]);
    if(m==1){
        if(a[1]==1) printf("%d\n%d\n%d\n",1,1,1);
        else printf("%d\n%d\n%d %d\n",a[1],2,1,a[1]-1);
    }
    else{
        int cnt=0; for(int i=1;i<=m;i++) cnt+=a[i]%2;//最多出现两个奇数——奇数无法两边连边
        if(cnt>2){puts("Impossible"); return 0;}
        for(int i=1;i<=m;i++){
            if(a[i]%2&&num==0){swap(a[1],a[i]);num++;}
            else if(a[i]%2&&num==1){swap(a[m],a[i]);}
        }
        for(int i=1;i<=m;i++) printf("%d ",a[i]); puts("");
        if(a[1]>1) b[1]=a[1]-1,tot=1;
        for(int i=2;i<m;i++) b[++tot]=a[i]; b[++tot]=a[m]+1; 
        printf("%d\n",tot);
        for(int i=1;i<=tot;i++) printf("%d ",b[i]); puts("");
    }
    system("pause");

    return 0;
}

D: [AGC001D] Arrays and Palindrome

题意:

给定一个长度为 \(m\) 的序列 \(a\) ,\(\sum\limits_{i=1}^m a[i]=n\)。要求字符串 \(s\) 的区间: \([1-a_1],[a_1+1,a_1+a_2],[a_1+a_2+1,a_1+a_2+a_3]...\) 都为回文串。

要求求出来一个序列 \(b\),限制整个字符串必须只有一个字符。

分析:

这题简直是人类智慧。

一个回文串,我们可以连接左右两边相对应的字符,表示两个字符相同。

然后通过 \(a\) 序列就可以建出来一个图。

所有字符相同的情况就是: 整个图形成一个连通块。

然后通过一些神奇的思想(具体还是看洛谷的题解好了),我们可以通过 错位 来得到 \(b\) 数列。

注意整个图至少需要 \(n-1\) 条边才能互相连通,如果 \(a\) 形成的边数过少,也就是 \(a_i\) 是奇数的情况 \(>2\) ,绝对不可能形成连通图。

题解:

#include<bits/stdc++.h>
const int N=1e5+5;
using namespace std;
int n,m;
int a[N],b[N],tot,num;
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++) scanf("%d",&a[i]);
    if(m==1){
        if(a[1]==1) printf("%d\n%d\n%d\n",1,1,1);
        else printf("%d\n%d\n%d %d\n",a[1],2,1,a[1]-1);
    }
    else{
        int cnt=0; for(int i=1;i<=m;i++) cnt+=a[i]%2;//最多出现两个奇数——奇数无法两边连边
        if(cnt>2){puts("Impossible"); return 0;}
        for(int i=1;i<=m;i++){
            if(a[i]%2&&num==0){swap(a[1],a[i]);num++;}
            else if(a[i]%2&&num==1){swap(a[m],a[i]);}
        }
        for(int i=1;i<=m;i++) printf("%d ",a[i]); puts("");
        if(a[1]>1) b[1]=a[1]-1,tot=1;
        for(int i=2;i<m;i++) b[++tot]=a[i]; b[++tot]=a[m]+1; 
        printf("%d\n",tot);
        for(int i=1;i<=tot;i++) printf("%d ",b[i]); puts("");
    }
    system("pause");
    return 0;
}

F: [AGC001F] Wide Swap

题意:

给定排列 \(P\),当且仅当 \(i,j\) 满足 \(|p_i-p_j|=1\)\(|i-j|\geq k\) 是可以交换 \(p_i\)\(p_j\) ,求最终字典序最小的排列.

分析:

将下标和权值更换位置,整个序列就表示成:

\(q_{p_i}=i\),然后交换的标准替换为:\(|p_i-p_{i+1}|\geq k\)

根据题意,\(p\) 字典序最小相当于要求 \(q\) 字典序最小。
也就是说 \(q_i=1\)\(i\) 尽量小,然后 \(q_j=2\)\(j\) 尽量小...

理解一下就是:\(q_i\) 大的尽量往后放,这样值小的就会在能达到的最前面。

然后,问题就转化成: 给出一个排列 \(r\)\(q\) 的反序序列,要求最终排列的字典序最大。

其实,在排列中有一些数 \(|r_i,r_j|<k\),他们的相对顺序永远不会改变。

我们暴力地将 \((r_i,r_j)\) 连边,得到一张拓扑图,然后贪心跑以当前位置的数为关键字的拓扑序就好了。

但是,这样的连边会有很多无用的边,考虑优化:

从后往前遍历 \(r\), \(r_i\) 连向的数必然属于 \([r_i-k,r_i]\cup [r_i,r_i+k]\) .

发现这两个区间内所有的元素已经确定了相对顺序,所以我们只需要将 \(r_i\) 分别向两个区间内下标最小的元素连边就行了。可以用 线段树维护

接着,利用 优先队列,排序时先处理 \(r_i\) 大的情况,然后得到一个字典序最大的拓扑序。

倒转后就是字典序最小的拓扑序,然后我们再把位置转化成权值赋值即可。

代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk make_pair
using namespace std;
const int N=5e5+5,inf=0x3f3f3f3f;
int n,k;
int p[N],pos[N];
int val[N<<2],deg[N],ans[N],m;
int nxt[N<<1],ver[N<<1],head[N],tot;
void add(int x,int y){
    ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; deg[y]++;
}
void modify(int x,int l,int r,int pos,int z){
    val[x]=min(val[x],z); 
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) modify(x<<1,l,mid,pos,z);
    else modify(x<<1|1,mid+1,r,pos,z);
 
}
int query(int x,int l,int r,int L,int R){
    if(L>R) return inf;
    if(L<=l&&r<=R) return val[x];
    int res=inf,mid=l+r>>1;
    if(L<=mid) res=min(res,query(x<<1,l,mid,L,R));
    if(R>mid) res=min(res,query(x<<1|1,mid+1,r,L,R));
    return res;
}

priority_queue<int> q;
int main(){
    memset(val,0x3f3f3f3f,sizeof(val));
    cin>>n>>k;
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x); p[x]=i;
    }
    reverse(p+1,p+n+1);
    for(int x=n,y;x;x--){
        y=query(1,1,n,max(p[x]-k+1,1),(p[x]-1)); if(y!=inf) add(p[x],p[y]);
        y=query(1,1,n,(p[x]+1),min(p[x]+k-1,n)); if(y!=inf) add(p[x],p[y]);
        modify(1,1,n,p[x],x);//位置为 p[x] 的情况下插入 x
    }
    for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
    while(!q.empty()){
        int x=q.top(); q.pop();
        pos[++m]=x;//尽量字典序大的前面
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i]; if(--deg[y]==0) q.push(y);
        }
    }
    reverse(pos+1,pos+n+1);
    for(int i=1;i<=n;i++) ans[pos[i]]=i;//再回去
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    system("pause");
    return 0;
}
posted @ 2021-11-10 07:31  Evitagen  阅读(79)  评论(0编辑  收藏  举报